Использование Reflection для создания DataTable из класса?
Я только что узнал об Generics, и мне интересно, смогу ли я использовать его для динамического создания таблиц данных из моих классов.
Или я мог упустить суть здесь. Вот мой код, я пытаюсь создать таблицу данных из моего существующего класса и заполнить ее. Однако я застреваю в своем мыслительном процессе.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Data;
namespace Generics
{
public class Dog
{
public string Breed { get; set; }
public string Name { get; set; }
public int legs { get; set; }
public bool tail { get; set; }
}
class Program
{
public static DataTable CreateDataTable(Type animaltype)
{
DataTable return_Datatable = new DataTable();
foreach (PropertyInfo info in animaltype.GetProperties())
{
return_Datatable.Columns.Add(new DataColumn(info.Name, info.PropertyType));
}
return return_Datatable;
}
static void Main(string[] args)
{
Dog Killer = new Dog();
Killer.Breed = "Maltese Poodle";
Killer.legs = 3;
Killer.tail = false;
Killer.Name = "Killer";
DataTable dogTable = new DataTable();
dogTable = CreateDataTable(Dog);
//How do I continue from here?
}
}
}
Сейчас на DataTable
указать ошибки. Кроме того, будучи новичком в рефлексии и обобщениях, как я буду на самом деле заполнять данные классом Killer?
8 ответов
Основываясь на всех предыдущих ответах, вот версия, которая создает DataTable из любой коллекции:
public static DataTable CreateDataTable<T>(IEnumerable<T> list)
{
Type type = typeof(T);
var properties = type.GetProperties();
DataTable dataTable = new DataTable();
foreach (PropertyInfo info in properties)
{
dataTable.Columns.Add(new DataColumn(info.Name, Nullable.GetUnderlyingType(info.PropertyType) ?? info.PropertyType));
}
foreach (T entity in list)
{
object[] values = new object[properties.Length];
for (int i = 0; i < properties.Length; i++)
{
values[i] = properties[i].GetValue(entity);
}
dataTable.Rows.Add(values);
}
return dataTable;
}
Вот более компактная версия ответа Дэвида, который также является функцией расширения. Я разместил код в проекте C# на Github.
public static class Extensions
{
public static DataTable ToDataTable<T>(this IEnumerable<T> self)
{
var properties = typeof(T).GetProperties();
var dataTable = new DataTable();
foreach (var info in properties)
dataTable.Columns.Add(info.Name, Nullable.GetUnderlyingType(info.PropertyType)
?? info.PropertyType);
foreach (var entity in self)
dataTable.Rows.Add(properties.Select(p => p.GetValue(entity)).ToArray());
return dataTable;
}
}
Я обнаружил, что это очень хорошо работает в сочетании с кодом для записи DataTable в CSV.
Моя любимая домашняя функция. это создает и заполняет все одновременно. бросить любой предмет.
public static DataTable ObjectToData(object o)
{
DataTable dt = new DataTable("OutputData");
DataRow dr = dt.NewRow();
dt.Rows.Add(dr);
o.GetType().GetProperties().ToList().ForEach(f =>
{
try
{
f.GetValue(o, null);
dt.Columns.Add(f.Name, f.PropertyType);
dt.Rows[0][f.Name] = f.GetValue(o, null);
}
catch { }
});
return dt;
}
Ошибка может быть исправлена путем изменения этого:
dogTable = CreateDataTable(Dog);
к этому:
dogTable = CreateDataTable(typeof(Dog));
Но есть некоторые предостережения относительно того, что вы пытаетесь сделать. Первый DataTable
не может хранить сложные типы, поэтому если Dog
имеет экземпляр Cat
на нем вы не сможете добавить это как столбец. Это зависит от вас, что вы хотите сделать в этом случае, но имейте это в виду.
Во-вторых, я бы порекомендовал единственный раз, когда вы используете DataTable
это когда вы создаете код, который ничего не знает о данных, которые он потребляет. Для этого есть допустимые варианты использования (например, управляемый пользователем инструмент анализа данных). Если у вас уже есть данные в Dog
Например, просто используйте его.
Еще один маленький кусочек, это:
DataTable dogTable = new DataTable();
dogTable = CreateDataTable(Dog);
можно сжать до этого:
DataTable dogTable = CreateDataTable(Dog);
Вот немного измененный код, который исправил проблему часового пояса для полей времени данных:
public static DataTable ToDataTable<T>(this IList<T> data)
{
PropertyDescriptorCollection props =
TypeDescriptor.GetProperties(typeof(T));
DataTable table = new DataTable();
for (int i = 0; i < props.Count; i++)
{
PropertyDescriptor prop = props[i];
table.Columns.Add(prop.Name, prop.PropertyType);
}
object[] values = new object[props.Count];
foreach (T item in data)
{
for (int i = 0; i < values.Length; i++)
{
if (props[i].PropertyType == typeof(DateTime))
{
DateTime currDT = (DateTime)props[i].GetValue(item);
values[i] = currDT.ToUniversalTime();
}
else
{
values[i] = props[i].GetValue(item);
}
}
table.Rows.Add(values);
}
return table;
}
Вот версия VB.Net, которая создает таблицу данных из общего списка, переданного функции как объект. Существует также вспомогательная функция (ObjectToDataTable), которая создает таблицу данных из объекта.
Система импорта. Отражение
Public Shared Function ListToDataTable(ByVal _List As Object) As DataTable
Dim dt As New DataTable
If _List.Count = 0 Then
MsgBox("The list cannot be empty. This is a requirement of the ListToDataTable function.")
Return dt
End If
Dim obj As Object = _List(0)
dt = ObjectToDataTable(obj)
Dim dr As DataRow = dt.NewRow
For Each obj In _List
dr = dt.NewRow
For Each p as PropertyInfo In obj.GetType.GetProperties
dr.Item(p.Name) = p.GetValue(obj, p.GetIndexParameters)
Next
dt.Rows.Add(dr)
Next
Return dt
End Function
Public Shared Function ObjectToDataTable(ByVal o As Object) As DataTable
Dim dt As New DataTable
Dim properties As List(Of PropertyInfo) = o.GetType.GetProperties.ToList()
For Each prop As PropertyInfo In properties
dt.Columns.Add(prop.Name, prop.PropertyType)
Next
dt.TableName = o.GetType.Name
Return dt
End Function
Вы можете преобразовать объект в xml, затем загрузить документ xml в набор данных, а затем извлечь первую таблицу из набора данных. Однако я не понимаю, насколько это практично, так как это подразумевает создание потоков, наборов данных и таблиц данных и использование диалогов для создания XML-документа.
Я думаю, для доказательства концепции я могу понять, почему. Вот пример, но несколько не решается его использовать.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Data;
using System.Xml.Serialization;
namespace Generics
{
public class Dog
{
public string Breed { get; set; }
public string Name { get; set; }
public int legs { get; set; }
public bool tail { get; set; }
}
class Program
{
public static DataTable CreateDataTable(Object[] arr)
{
XmlSerializer serializer = new XmlSerializer(arr.GetType());
System.IO.StringWriter sw = new System.IO.StringWriter();
serializer.Serialize(sw, arr);
System.Data.DataSet ds = new System.Data.DataSet();
System.Data.DataTable dt = new System.Data.DataTable();
System.IO.StringReader reader = new System.IO.StringReader(sw.ToString());
ds.ReadXml(reader);
return ds.Tables[0];
}
static void Main(string[] args)
{
Dog Killer = new Dog();
Killer.Breed = "Maltese Poodle";
Killer.legs = 3;
Killer.tail = false;
Killer.Name = "Killer";
Dog [] array_dog = new Dog[5];
Dog [0] = killer;
Dog [1] = killer;
Dog [2] = killer;
Dog [3] = killer;
Dog [4] = killer;
DataTable dogTable = new DataTable();
dogTable = CreateDataTable(array_dog);
// continue here
}
}
}
посмотрите следующий пример здесь
Если вы хотите установить порядок столбцов / включить только некоторые столбцы / исключить некоторые столбцы, попробуйте следующее:
private static DataTable ConvertToDataTable<T>(IList<T> data, string[] fieldsToInclude = null,
string[] fieldsToExclude = null)
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
DataTable table = new DataTable();
foreach (PropertyDescriptor prop in properties)
{
if ((fieldsToInclude != null && !fieldsToInclude.Contains(prop.Name)) ||
(fieldsToExclude != null && fieldsToExclude.Contains(prop.Name)))
continue;
table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
}
foreach (T item in data)
{
var atLeastOnePropertyExists = false;
DataRow row = table.NewRow();
foreach (PropertyDescriptor prop in properties)
{
if ((fieldsToInclude != null && !fieldsToInclude.Contains(prop.Name)) ||
(fieldsToExclude != null && fieldsToExclude.Contains(prop.Name)))
continue;
row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
atLeastOnePropertyExists = true;
}
if(atLeastOnePropertyExists) table.Rows.Add(row);
}
if (fieldsToInclude != null)
SetColumnsOrder(table, fieldsToInclude);
return table;
}
private static void SetColumnsOrder(DataTable table, params String[] columnNames)
{
int columnIndex = 0;
foreach (var columnName in columnNames)
{
table.Columns[columnName].SetOrdinal(columnIndex);
columnIndex++;
}
}
Используя ответ, предоставленный @neoistheone, я изменил следующие разделы. Работает нормально сейчас.
DataTable dogTable = new DataTable();
dogTable = CreateDataTable(typeof(Dog));
dogTable.Rows.Add(Killer.Breed, Killer.Name,Killer.legs,Killer.tail);
foreach (DataRow row in dogTable.Rows)
{
Console.WriteLine(row.Field<string>("Name") + " " + row.Field<string>("Breed"));
Console.ReadLine();
}