将 Datatable 转换为通用 IEnumerable<T>

Convert Datatable to generic IEnumerable<T>

我正在尝试从 DataTable 转换为 IEnumerable<T>。但是我得到的是 IEnumerable<DataRow>:

using (var con = new SqlConnection(Connection.ToString()))
{
    con.Open();

    using (var cmd = con.CreateCommand())
    {
        cmd.CommandTimeout = int.MaxValue;
        cmd.CommandText = sqlCommand;

        var reader = cmd.ExecuteReader();
        DataTable tbl = new DataTable();
        tbl.Load(reader);

        var res = tbl.AsEnumerable().ToList();  // IEnumerable<DataRow>:
    }
}

我想做的是:

protected async Task<IEnumerable<T>> QuerySqlCmdReadRows<T>(string sqlCommand)
{
    using (var con = new SqlConnection(Connection.ToString()))
    {
        con.Open();

        using (var cmd = con.CreateCommand())
        {
            cmd.CommandTimeout = int.MaxValue;
            cmd.CommandText = sqlCommand;

            var reader = cmd.ExecuteReader();
            DataTable tbl = new DataTable();
            tbl.Load(reader);

            return tbl.AsEnumerable().ToList(); 
        }
    }
}

是否可以像我在 Entity Framework 中那样获得 IEnumerable<T>

var studentList = ctx.SqlQuery("Select * from Students")
                     .ToList<Student>();

您可以创建一个扩展方法来为您转换它。鉴于您的查询属性与通用 T 类型的属性相匹配,您可以使用反射来执行它!示例(见代码注释):

public static class DataTableExtensions
{
    public static IEnumerable<T> ToGenericList<T>(this DataTable dataTable)
    {
        var properties = typeof(T).GetProperties().Where(x => x.CanWrite).ToList();

        var result = new List<T>();

        // loop on rows
        foreach (DataRow row in dataTable.Rows)
        {
            // create an instance of T generic type.
            var item = Activator.CreateInstance<T>();

            // loop on properties and columns that matches properties
            foreach (var prop in properties)
                foreach (DataColumn column in dataTable.Columns)                    
                    if (prop.Name == column.ColumnName)
                    {
                        // Get the value from the datatable cell
                        object value = row[column.ColumnName];

                        // Set the value into the object
                        prop.SetValue(item, value);
                        break;
                    }


            result.Add(item);
        }

        return result;
    }
}

假设您有这样的模型:

public class Student 
{
    public int Id { get; set; } 
    public string Code { get; set; } 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
    // other possible properties
}

您可以像这样使用扩展方法:

protected async Task<IEnumerable<T>> QuerySqlCmdReadRows<T>(string sqlCommand)
{
    using (var con = new SqlConnection(Connection.ToString()))
    {
        con.Open();
        using (var cmd = con.CreateCommand())
        {
            cmd.CommandText = sqlCommand;

            DataTable dtResult = new DataTable();

            using (var reader = await cmd.ExecuteReaderAsync())
               dtResult.Load(reader);

            return dtResult.ToGenericList<T>();
        }
    }
}

// just and sample
var students = QuerySqlCmdReadRows<Students>("select Id, Code, FirstName, LastName from Students");

这是我将 DataTable 转换为 IEnumerable<T> 的实现。它复制任何具有匹配 属性 或字段名称的列。

首先,一些扩展用于处理 MemberInfo 作为 PropertyInfoFieldInfo 的父级:

public class MemberInfoExt {
    public static bool GetCanWrite(this MemberInfo member) {
        switch (member) {
            case FieldInfo mfi:
                return true;
            case PropertyInfo mpi:
                return mpi.CanWrite;
            default:
                throw new ArgumentException("MemberInfo must be if type FieldInfo or PropertyInfo", nameof(member));
        }
    }
    public static void SetValue(this MemberInfo member, object destObject, object value) {
        switch (member) {
            case FieldInfo mfi:
                mfi.SetValue(destObject, value);
                break;
            case PropertyInfo mpi:
                mpi.SetValue(destObject, value);
                break;
            case MethodInfo mi:
                mi.Invoke(destObject, new object[] { value });
                break;
            default:
                throw new ArgumentException("MemberInfo must be of type FieldInfo, PropertyInfo or MethodInfo", nameof(member));
        }
    }
}

利用这些,你可以写一个DataTable扩展方法:

public class DataTableExt {
    public static IEnumerable<T> ToIEnumerable<T>(this DataTable dt) where T : new() {
        var props = typeof(T).GetPropertiesOrFields()
                             .Where(mi => dt.Columns.Contains(mi.Name) && mi.GetCanWrite())
                             .ToList();
        foreach (var row in dt.AsEnumerable()) {
            var aT = new T();
            foreach (var p in props)
                p.SetValue(aT, row[p.Name]);
            yield return aT;
        }
    }
}

并使用它将 DataTable 转换为特定类型。

也可以将 DataTable 转换为 IEnumerable<anon>,其中 anon 是一个匿名对象(或模拟),但它的价值值得怀疑,因为它需要创建一个匿名对象只能通过反射使用的运行时对象。