重新访问 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;
    }

希望有人觉得这有用...