重新访问 IEnumerable 到 DataTable 扩展方法——字符串问题
Revisiting IEnumerable to DataTable extension method -- Issue with strings
我正在开发可以在 IEnumerable 上使用的 "ToDataTable()"。很多这样的例子,比如这里:Convert IEnumerable to DataTable。 DataTables 对我来说是可取的,因为我来自我们习惯游标的 Foxpro 领域。所以,我喜欢 DataTables 因为它们相对简单,易于使用,并且可以容纳像样的列 meta-data。我已经有一堆代码可以很好地显示它们(使用排序和过滤网格 headers),导出到 Excel,等等
有人对我上面链接的线程发表了非常好的评论:“如果类型是字符串,这将不起作用,因为属性是字符和长度,但获取值然后会尝试将将完整的字符串放入 char 列。"
问题是,某些东西适合扩展方法调用(如 one-dimensional 字符串数组),但在转换发生时会抛出错误。我写了一个扩展方法,将 IsValueType() 添加到混合中,但问题是字符串的 returns false。最后,我加了两个kludges:一个参数,强制把序列中的item当作一个值,如果发现序列中的item是"String".[=类型,就把它当成一个值。 12=]
是否有更好的方法来做到这一点,或者字符串是否由于其独特的类型和反射结果而变得奇怪?下面的代码适用于我能想到的每个 IEnumerable,它适用于所有 DataTable 列类型的 one-dimensional 数组,字符串除外(好吧,它适用于带有 hard-coded 的字符串,但会出错否则)。 hard-code 字符串场景没什么大不了的,我只是希望有一种更优雅的方式来做到这一点。有什么想法吗?
public static DataTable ToDataTable<T>(this IEnumerable<T> items, string tableName = "", bool treatItemAsValue = false)
{
// We want a single extension method that can take in an enumerable sequence (such as a LINQ query)
// and return the result as a DataTable. We want this to be a one stop shop for converting
// various objects into DataTable format, as DataTables are a nice parallel to Foxpro cursors.
if (items == null) { return null; }
Type itemType = typeof(T);
bool typeIsNullable = itemType.IsGenericType && typeof(T).GetGenericTypeDefinition().Equals(typeof(Nullable<>));
string itemTypeName = "";
bool typeIsValue = false;
Type itemUnderlyingType = itemType;
if (typeIsNullable)
{
// Type of enumerable item is nullable, so we need to find its base type.
itemUnderlyingType = Nullable.GetUnderlyingType(itemType);
}
typeIsValue = itemUnderlyingType.IsValueType;
itemTypeName = itemUnderlyingType.Name;
DataTable dt = new DataTable();
DataColumn col = null;
if ((treatItemAsValue) || (itemTypeName == "String"))
{
// We have been asked to treat the item in the sequence as a value, of the items
// in the sequence are strings. Strings are NOT considered a value type in regards
// to IsValueType(), but when item values are assessed, it will be the value
// of the string that tries to pull in.
typeIsValue = true;
}
if (itemTypeName == "DataRow")
{
// Special case. If our enumerable type is DataRow, then we can utilize a more appropriate
// (built-in) extension method to convert enumerable DataRows to a DataTable.
dt = ((IEnumerable<DataRow>)items).CopyToDataTable();
}
else
{
// We must have an enumerable sequence/collection of some other type, possibly anonymous.
// Get properties of the enumerable to add as columns to the data table.
if (typeIsValue)
{
// Our enumerable items are of a value type (e.g. integers in a one-dimensional array).
col = dt.Columns.Add();
col.AllowDBNull = typeIsNullable;
col.ColumnName = itemTypeName;
col.DataType = itemUnderlyingType;
// Now walk through the enumeration and add rows to our data table (single values).
foreach (var item in items)
{
dt.Rows.Add(item);
}
}
else
{
// The type should be something we can walk through the properties of in order to
// generate properly named and typed columns of our DataTable.
PropertyInfo[] props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in props)
{
Type propType = prop.PropertyType;
// Is it a nullable type? Get the underlying type.
if (propType.IsGenericType && propType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
{
propType = new NullableConverter(propType).UnderlyingType;
}
dt.Columns.Add(prop.Name, propType);
}
// Now walk through the enumeration and add rows to our data table.
foreach (var item in items)
{
var values = new object[props.Length];
for (int i = 0; i < props.Length; i++)
{
values[i] = props[i].GetValue(item, null);
}
dt.Rows.Add(values);
}
}
}
// Give the DataTable a reasonable name.
if (tableName.Length == 0)
{
if (typeof(T).IsAnonymous())
{
// Anonymous types have really goofy names, so there is no use using that as table name.
tableName = "Anonymous";
}
else
{
// This is NOT an anonymous type, so we can use the type name as table name.
tableName = typeof(T).Name;
}
}
return dt;
}
好吧,除了一条几乎没有抓住要点的评论外,没有任何回应,所以我只 post 我最后的内容。这个最终版本还考虑了没有属性的项目(例如 "Object" 本身),并且更好地处理了 null items/values:
public static DataTable ToDataTable<T>(this IEnumerable<T> items, string tableName = "", bool treatItemAsValue = false)
{
// We want a single extension method that can take in an enumerable sequence (such as a LINQ query)
// and return the result as a DataTable. We want this to be a one stop shop for converting
// various objects into DataTable format, as DataTables are a nice parallel to Foxpro cursors.
if (items == null) { return null; }
Type itemType = typeof(T);
bool typeIsNullable = itemType.IsGenericType && typeof(T).GetGenericTypeDefinition().Equals(typeof(Nullable<>));
string itemTypeName = "";
bool typeIsValue = false;
Type itemUnderlyingType = itemType;
if (typeIsNullable)
{
// Type of enumerable item is nullable, so we need to find its base type.
itemUnderlyingType = Nullable.GetUnderlyingType(itemType);
}
typeIsValue = itemUnderlyingType.IsValueType;
itemTypeName = itemUnderlyingType.Name;
DataTable dt = new DataTable();
DataColumn col = null;
PropertyInfo[] props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
if ((treatItemAsValue) || (itemTypeName == "String") || (props.Length == 0))
{
// We have been asked to treat the item in the sequence as a value, or the items
// in the sequence is a string which cannot be "flattened" properly by analyzing properties.
// OR, the type has no properties to put on display, so we should just use the item directly.
// (like the base "Object" type).
typeIsValue = true;
}
if (itemTypeName == "DataRow")
{
// Special case. If our enumerable type is DataRow, then we can utilize a more appropriate
// (built-in) extension method to convert enumerable DataRows to a DataTable.
dt = ((IEnumerable<DataRow>)items).CopyToDataTable();
}
else
{
// We must have an enumerable sequence/collection of some other type, possibly anonymous.
// Get properties of the enumerable to add as columns to the data table.
if (typeIsValue)
{
// Our enumerable items are of a value type (e.g. integers in a one-dimensional array).
col = dt.Columns.Add();
// Whether or not the type is nullable, the value might be null (e.g. for type "Object").
col.AllowDBNull = true;
col.ColumnName = itemTypeName;
col.DataType = itemUnderlyingType;
// Now walk through the enumeration and add rows to our data table (single values).
foreach (var item in items)
{
dt.Rows.Add(item);
}
}
else
{
// The type should be something we can walk through the properties of in order to
// generate properly named and typed columns of our DataTable.
foreach (var prop in props)
{
Type propType = prop.PropertyType;
// Is it a nullable type? Get the underlying type.
if (propType.IsGenericType && propType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
{
propType = new NullableConverter(propType).UnderlyingType;
}
dt.Columns.Add(prop.Name, propType);
}
// Now walk through the enumeration and add rows to our data table.
foreach (var item in items)
{
if (item != null)
{
// Can only add an item as a row if it is not null.
var values = new object[props.Length];
for (int i = 0; i < props.Length; i++)
{
values[i] = props[i].GetValue(item, null);
}
dt.Rows.Add(values);
}
}
}
}
// Give the DataTable a reasonable name.
if (tableName.Length == 0)
{
if (typeof(T).IsAnonymous())
{
// Anonymous types have really goofy names, so there is no use using that as table name.
tableName = "Anonymous";
}
else
{
// This is NOT an anonymous type, so we can use the type name as table name.
tableName = typeof(T).Name;
}
}
return dt;
}
希望有人觉得这有用...
我正在开发可以在 IEnumerable 上使用的 "ToDataTable()"。很多这样的例子,比如这里:Convert IEnumerable to DataTable。 DataTables 对我来说是可取的,因为我来自我们习惯游标的 Foxpro 领域。所以,我喜欢 DataTables 因为它们相对简单,易于使用,并且可以容纳像样的列 meta-data。我已经有一堆代码可以很好地显示它们(使用排序和过滤网格 headers),导出到 Excel,等等
有人对我上面链接的线程发表了非常好的评论:“如果类型是字符串,这将不起作用,因为属性是字符和长度,但获取值然后会尝试将将完整的字符串放入 char 列。"
问题是,某些东西适合扩展方法调用(如 one-dimensional 字符串数组),但在转换发生时会抛出错误。我写了一个扩展方法,将 IsValueType() 添加到混合中,但问题是字符串的 returns false。最后,我加了两个kludges:一个参数,强制把序列中的item当作一个值,如果发现序列中的item是"String".[=类型,就把它当成一个值。 12=]
是否有更好的方法来做到这一点,或者字符串是否由于其独特的类型和反射结果而变得奇怪?下面的代码适用于我能想到的每个 IEnumerable,它适用于所有 DataTable 列类型的 one-dimensional 数组,字符串除外(好吧,它适用于带有 hard-coded 的字符串,但会出错否则)。 hard-code 字符串场景没什么大不了的,我只是希望有一种更优雅的方式来做到这一点。有什么想法吗?
public static DataTable ToDataTable<T>(this IEnumerable<T> items, string tableName = "", bool treatItemAsValue = false)
{
// We want a single extension method that can take in an enumerable sequence (such as a LINQ query)
// and return the result as a DataTable. We want this to be a one stop shop for converting
// various objects into DataTable format, as DataTables are a nice parallel to Foxpro cursors.
if (items == null) { return null; }
Type itemType = typeof(T);
bool typeIsNullable = itemType.IsGenericType && typeof(T).GetGenericTypeDefinition().Equals(typeof(Nullable<>));
string itemTypeName = "";
bool typeIsValue = false;
Type itemUnderlyingType = itemType;
if (typeIsNullable)
{
// Type of enumerable item is nullable, so we need to find its base type.
itemUnderlyingType = Nullable.GetUnderlyingType(itemType);
}
typeIsValue = itemUnderlyingType.IsValueType;
itemTypeName = itemUnderlyingType.Name;
DataTable dt = new DataTable();
DataColumn col = null;
if ((treatItemAsValue) || (itemTypeName == "String"))
{
// We have been asked to treat the item in the sequence as a value, of the items
// in the sequence are strings. Strings are NOT considered a value type in regards
// to IsValueType(), but when item values are assessed, it will be the value
// of the string that tries to pull in.
typeIsValue = true;
}
if (itemTypeName == "DataRow")
{
// Special case. If our enumerable type is DataRow, then we can utilize a more appropriate
// (built-in) extension method to convert enumerable DataRows to a DataTable.
dt = ((IEnumerable<DataRow>)items).CopyToDataTable();
}
else
{
// We must have an enumerable sequence/collection of some other type, possibly anonymous.
// Get properties of the enumerable to add as columns to the data table.
if (typeIsValue)
{
// Our enumerable items are of a value type (e.g. integers in a one-dimensional array).
col = dt.Columns.Add();
col.AllowDBNull = typeIsNullable;
col.ColumnName = itemTypeName;
col.DataType = itemUnderlyingType;
// Now walk through the enumeration and add rows to our data table (single values).
foreach (var item in items)
{
dt.Rows.Add(item);
}
}
else
{
// The type should be something we can walk through the properties of in order to
// generate properly named and typed columns of our DataTable.
PropertyInfo[] props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in props)
{
Type propType = prop.PropertyType;
// Is it a nullable type? Get the underlying type.
if (propType.IsGenericType && propType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
{
propType = new NullableConverter(propType).UnderlyingType;
}
dt.Columns.Add(prop.Name, propType);
}
// Now walk through the enumeration and add rows to our data table.
foreach (var item in items)
{
var values = new object[props.Length];
for (int i = 0; i < props.Length; i++)
{
values[i] = props[i].GetValue(item, null);
}
dt.Rows.Add(values);
}
}
}
// Give the DataTable a reasonable name.
if (tableName.Length == 0)
{
if (typeof(T).IsAnonymous())
{
// Anonymous types have really goofy names, so there is no use using that as table name.
tableName = "Anonymous";
}
else
{
// This is NOT an anonymous type, so we can use the type name as table name.
tableName = typeof(T).Name;
}
}
return dt;
}
好吧,除了一条几乎没有抓住要点的评论外,没有任何回应,所以我只 post 我最后的内容。这个最终版本还考虑了没有属性的项目(例如 "Object" 本身),并且更好地处理了 null items/values:
public static DataTable ToDataTable<T>(this IEnumerable<T> items, string tableName = "", bool treatItemAsValue = false)
{
// We want a single extension method that can take in an enumerable sequence (such as a LINQ query)
// and return the result as a DataTable. We want this to be a one stop shop for converting
// various objects into DataTable format, as DataTables are a nice parallel to Foxpro cursors.
if (items == null) { return null; }
Type itemType = typeof(T);
bool typeIsNullable = itemType.IsGenericType && typeof(T).GetGenericTypeDefinition().Equals(typeof(Nullable<>));
string itemTypeName = "";
bool typeIsValue = false;
Type itemUnderlyingType = itemType;
if (typeIsNullable)
{
// Type of enumerable item is nullable, so we need to find its base type.
itemUnderlyingType = Nullable.GetUnderlyingType(itemType);
}
typeIsValue = itemUnderlyingType.IsValueType;
itemTypeName = itemUnderlyingType.Name;
DataTable dt = new DataTable();
DataColumn col = null;
PropertyInfo[] props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
if ((treatItemAsValue) || (itemTypeName == "String") || (props.Length == 0))
{
// We have been asked to treat the item in the sequence as a value, or the items
// in the sequence is a string which cannot be "flattened" properly by analyzing properties.
// OR, the type has no properties to put on display, so we should just use the item directly.
// (like the base "Object" type).
typeIsValue = true;
}
if (itemTypeName == "DataRow")
{
// Special case. If our enumerable type is DataRow, then we can utilize a more appropriate
// (built-in) extension method to convert enumerable DataRows to a DataTable.
dt = ((IEnumerable<DataRow>)items).CopyToDataTable();
}
else
{
// We must have an enumerable sequence/collection of some other type, possibly anonymous.
// Get properties of the enumerable to add as columns to the data table.
if (typeIsValue)
{
// Our enumerable items are of a value type (e.g. integers in a one-dimensional array).
col = dt.Columns.Add();
// Whether or not the type is nullable, the value might be null (e.g. for type "Object").
col.AllowDBNull = true;
col.ColumnName = itemTypeName;
col.DataType = itemUnderlyingType;
// Now walk through the enumeration and add rows to our data table (single values).
foreach (var item in items)
{
dt.Rows.Add(item);
}
}
else
{
// The type should be something we can walk through the properties of in order to
// generate properly named and typed columns of our DataTable.
foreach (var prop in props)
{
Type propType = prop.PropertyType;
// Is it a nullable type? Get the underlying type.
if (propType.IsGenericType && propType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
{
propType = new NullableConverter(propType).UnderlyingType;
}
dt.Columns.Add(prop.Name, propType);
}
// Now walk through the enumeration and add rows to our data table.
foreach (var item in items)
{
if (item != null)
{
// Can only add an item as a row if it is not null.
var values = new object[props.Length];
for (int i = 0; i < props.Length; i++)
{
values[i] = props[i].GetValue(item, null);
}
dt.Rows.Add(values);
}
}
}
}
// Give the DataTable a reasonable name.
if (tableName.Length == 0)
{
if (typeof(T).IsAnonymous())
{
// Anonymous types have really goofy names, so there is no use using that as table name.
tableName = "Anonymous";
}
else
{
// This is NOT an anonymous type, so we can use the type name as table name.
tableName = typeof(T).Name;
}
}
return dt;
}
希望有人觉得这有用...