重新审视IEnumerable到DataTable扩展方法 - 字符串问题

时间:2016-03-09 19:56:57

标签: c# string datatable ienumerable extension-methods

我正在研究一个可以在IEnumerable上使用的“ToDataTable()”。很多例子,例如:Convert IEnumerable to DataTable。 DataTables对我来说是可取的,因为我来自Foxpro领域,我们习惯于游标。因此,我挖掘DataTables是因为它们相对简单,易于使用,并且可以容纳不错的列元数据。而且我已经有了很多代码可以很好地显示它们(使用排序和过滤网格标题),导出到Excel等等。

有人对我上面链接的帖子做了一个非常好的评论:“如果类型是字符串,这不起作用,因为属性是字符和长度但是get值然后尝试将完整的字符串放入char列。

问题是,某些东西适合扩展方法调用(如一维字符串数组),但在转换发生时会抛出错误。我编写了一个扩展方法,它将IsValueType()添加到混合中,但问题是对字符串返回false。最后,我添加了两个kludges:一个参数来强制序列中的项被视为一个值,并且如果发现它是“String”类型,则将序列中的项目视为一个值。

是否有更好的方法可以做到这一点,或者由于他们独特的打字和反射结果,弦乐只是一个奇怪的人?下面的代码适用于我能想到的每个IEnumerable,它适用于除字符串之外的所有DataTable列类型的一维数组(好吧,它适用于带有硬编码kludge的字符串,但否则会出错)。对字符串场景进行硬编码并不是什么大不了的事,我只是希望有更优雅的方法来做到这一点。有什么想法吗?

    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;
    }

1 个答案:

答案 0 :(得分:1)

好吧,没有回复,除了评论几乎错过了这一点,所以我只会发布我最终的结果。最终版本还会考虑没有属性的项目(例如“对象”本身),并且可以更好地处理空项/值:

    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;
    }

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