【问题标题】:Convert Datatable to generic IEnumerable<T>将 Datatable 转换为通用 IEnumerable<T>
【发布时间】:2019-12-06 11:58:52
【问题描述】:

我正在尝试从 DataTable 转换为 IEnumerable&lt;T&gt;。但我得到的是IEnumerable&lt;DataRow&gt;:

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&lt;T&gt;

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

【问题讨论】:

  • DataTable 是 DataRow 对象的集合,因此 IEnumerable 是唯一有用的转换。您是否正在寻找将关系数据映射到对象的方法?这是 ORM 的工作。如果你想要具体的对象,请使用 EF 或像 Dapper 这样的 microORM
  • 使用Dapper,您可以编写,例如var results=con.Query&lt;SomeType&gt;("select .....");,Dapper 将根据属性名称将结果行转换为对象。它也不会将结果加载到 DataTable 中,它会在从 DbDataReader 读取时发出映射的对象

标签: c# generics .net-core datatable ado.net


【解决方案1】:

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

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");

【讨论】:

  • 感谢您的回答!但是,我看到了以下错误: 1. Error CS1061 'PropertyInfo' does not contain a definition for 'HasSetter' and no accessible extension method 'HasSetter' accepting a first argument of type 'PropertyInfo' could be found (are you missing a using directive or an assembly reference?
  • 2. T' does not contain a definition for 'Name' and no accessible extension method 'Name' accepting a first argument of type 'T' could be found (are you missing a using directive or an assembly reference?) 3. 'T' does not contain a definition for 'SetValue' and no accessible extension method 'SetValue' accepting a first argument of type 'T' could be found (are you missing a using directive or an assembly reference?) 你能告诉他们如何解决吗
  • 是的,你是对的。获取可以接收某些值的属性的正确方法是CanWrite property。
【解决方案2】:

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

首先,将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&lt;anon&gt;,其中anon 是一个匿名对象(或模拟),但这是有问题的,因为它需要在运行时创建一个只能使用的匿名对象通过反射。

【讨论】:

    猜你喜欢
    • 2011-03-24
    • 2010-10-08
    • 1970-01-01
    • 2011-09-30
    • 2018-02-04
    • 1970-01-01
    • 1970-01-01
    • 2012-02-24
    • 1970-01-01
    相关资源
    最近更新 更多