.NET – 将通用集合转换为DataTable

我正在尝试将generics集合(List)转换为DataTable。 我发现下面的代码来帮助我做到这一点:

// Sorry about indentation public class CollectionHelper { private CollectionHelper() { } // this is the method I have been using public static DataTable ConvertTo<T>(IList<T> list) { DataTable table = CreateTable<T>(); Type entityType = typeof(T); PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(entityType); foreach (T item in list) { DataRow row = table.NewRow(); foreach (PropertyDescriptor prop in properties) { row[prop.Name] = prop.GetValue(item); } table.Rows.Add(row); } return table; } public static DataTable CreateTable<T>() { Type entityType = typeof(T); DataTable table = new DataTable(entityType.Name); PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(entityType); foreach (PropertyDescriptor prop in properties) { // HERE IS WHERE THE ERROR IS THROWN FOR NULLABLE TYPES table.Columns.Add(prop.Name, prop.PropertyType); } return table; } } 

我的问题是,当我将MySimpleClass的某个属性更改为可空types时,出现以下错误:

DataSet does not support System.Nullable<>.

我怎样才能做到这一点与我的class的可空属性/领域?

那么大概你需要使用Nullable.GetUnderlyingType将它们提升到不可为空的forms,也许可以将一些null值更改为DbNull.Value

更改分配为:

 row[prop.Name] = prop.GetValue(item) ?? DBNull.Value; 

并添加列时:

 table.Columns.Add(prop.Name, Nullable.GetUnderlyingType( prop.PropertyType) ?? prop.PropertyType); 

它工作。 ( ??是空合并运算符;如果它是非空的,则使用第一个操作数,否则计算并使用第二个操作数)

好。 由于DataSet不支持可为空的types,因此您必须检查该属性是否为genericstypes,获取该types的generics定义,然后使用Nullable.GetUnderlyingType参数(实际types)。 如果值为null,则只需在DataSet中使用DBNull.Value

如果Nullable.GetUnderlyingType()给出您的prop.PropertyType返回一个非空值,使用它作为一个列的types。 否则,请使用prop.PropertyType本身。

这里有一个版本,有一些修改,允许空值和'\ 0'字符,而不会炸毁DataTable。

 using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Data; namespace SomeNamespace { public static class Extenders { public static DataTable ToDataTable<T>(this IEnumerable<T> collection, string tableName) { DataTable tbl = ToDataTable(collection); tbl.TableName = tableName; return tbl; } public static DataTable ToDataTable<T>(this IEnumerable<T> collection) { DataTable dt = new DataTable(); Type t = typeof(T); PropertyInfo[] pia = t.GetProperties(); object temp; DataRow dr; for (int i = 0; i < pia.Length; i++ ) { dt.Columns.Add(pia[i].Name, Nullable.GetUnderlyingType(pia[i].PropertyType) ?? pia[i].PropertyType); dt.Columns[i].AllowDBNull = true; } //Populate the table foreach (T item in collection) { dr = dt.NewRow(); dr.BeginEdit(); for (int i = 0; i < pia.Length; i++) { temp = pia[i].GetValue(item, null); if (temp == null || (temp.GetType().Name == "Char" && ((char)temp).Equals('\0'))) { dr[pia[i].Name] = (object)DBNull.Value; } else { dr[pia[i].Name] = temp; } } dr.EndEdit(); dt.Rows.Add(dr); } return dt; } } } 

我知道这个问题是旧的,但我对我做的扩展方法有同样的问题。 使用Marc Gravell的回复,我可以修改我的代码。 这个扩展方法将处理原始types,string,枚举和具有原始属性的对象列表。

 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Linq; using System.Text; /// <summary> /// Converts a List&lt;T&gt; to a DataTable. /// </summary> /// <typeparam name="T">The type of the list collection.</typeparam> /// <param name="list">List instance reference.</param> /// <returns>A DataTable of the converted list collection.</returns> public static DataTable ToDataTable<T>(this List<T> list) { var entityType = typeof (T); // Lists of type System.String and System.Enum (which includes enumerations and structs) must be handled differently // than primitives and custom objects (eg an object that is not type System.Object). if (entityType == typeof (String)) { var dataTable = new DataTable(entityType.Name); dataTable.Columns.Add(entityType.Name); // Iterate through each item in the list. There is only one cell, so use index 0 to set the value. foreach (T item in list) { var row = dataTable.NewRow(); row[0] = item; dataTable.Rows.Add(row); } return dataTable; } else if (entityType.BaseType == typeof (Enum)) { var dataTable = new DataTable(entityType.Name); dataTable.Columns.Add(entityType.Name); // Iterate through each item in the list. There is only one cell, so use index 0 to set the value. foreach (string namedConstant in Enum.GetNames(entityType)) { var row = dataTable.NewRow(); row[0] = namedConstant; dataTable.Rows.Add(row); } return dataTable; } // Check if the type of the list is a primitive type or not. Note that if the type of the list is a custom // object (eg an object that is not type System.Object), the underlying type will be null. var underlyingType = Nullable.GetUnderlyingType(entityType); var primitiveTypes = new List<Type> { typeof (Byte), typeof (Char), typeof (Decimal), typeof (Double), typeof (Int16), typeof (Int32), typeof (Int64), typeof (SByte), typeof (Single), typeof (UInt16), typeof (UInt32), typeof (UInt64), }; var typeIsPrimitive = primitiveTypes.Contains(underlyingType); // If the type of the list is a primitive, perform a simple conversion. // Otherwise, map the object's properties to columns and fill the cells with the properties' values. if (typeIsPrimitive) { var dataTable = new DataTable(underlyingType.Name); dataTable.Columns.Add(underlyingType.Name); // Iterate through each item in the list. There is only one cell, so use index 0 to set the value. foreach (T item in list) { var row = dataTable.NewRow(); row[0] = item; dataTable.Rows.Add(row); } return dataTable; } else { // TODO: // 1. Convert lists of type System.Object to a data table. // 2. Handle objects with nested objects (make the column name the name of the object and print "system.object" as the value). var dataTable = new DataTable(entityType.Name); var propertyDescriptorCollection = TypeDescriptor.GetProperties(entityType); // Iterate through each property in the object and add that property name as a new column in the data table. foreach (PropertyDescriptor propertyDescriptor in propertyDescriptorCollection) { // Data tables cannot have nullable columns. The cells can have null values, but the actual columns themselves cannot be nullable. // Therefore, if the current property type is nullable, use the underlying type (eg if the type is a nullable int, use int). var propertyType = Nullable.GetUnderlyingType(propertyDescriptor.PropertyType) ?? propertyDescriptor.PropertyType; dataTable.Columns.Add(propertyDescriptor.Name, propertyType); } // Iterate through each object in the list adn add a new row in the data table. // Then iterate through each property in the object and add the property's value to the current cell. // Once all properties in the current object have been used, add the row to the data table. foreach (T item in list) { var row = dataTable.NewRow(); foreach (PropertyDescriptor propertyDescriptor in propertyDescriptorCollection) { var value = propertyDescriptor.GetValue(item); row[propertyDescriptor.Name] = value ?? DBNull.Value; } dataTable.Rows.Add(row); } return dataTable; } }