【问题标题】:Convert rows from a data reader into typed results将数据读取器中的行转换为类型化结果
【发布时间】:2010-11-15 05:36:16
【问题描述】:

我正在使用返回数据阅读器的第三方库。我想要一种简单且尽可能通用的方法将其转换为对象列表。
例如,假设我有一个具有 2 个属性 EmployeeId 和 Name 的类“Employee”,我希望将数据读取器(包含员工列表)转换为 List
我想我别无选择,只能遍历数据读取器的行并将它们中的每一个转换为我将添加到列表中的 Employee 对象。有更好的解决方案吗?我正在使用 C# 3.5,理想情况下我希望它尽可能通用,以便它适用于任何类(DataReader 中的字段名称与各种对象的属性名称匹配)。

【问题讨论】:

  • 该死,真希望我现在就在编译器附近,我很想写这段代码!如果没有其他人把我吹走,我明天就会破解它。 +1 个问题。
  • @MattHowells 您仍然可以编写它,个人很想看看它是否有所不同。

标签: c# list datareader


【解决方案1】:

请检查这个答案

使用反射 + lambda 表达式进行简单、快速的 IDataReaderIEnumerable<T> 转换

https://stackoverflow.com/a/70321210/3343007

【讨论】:

    【解决方案2】:

    我的版本

    用法:

    var Q = await Reader.GetTable<DbRoom>("SELECT id, name FROM rooms");
    

    PgRoom 是

    public class DbRoom
    {
        [Column("id")]
        public int Id { get; set; }
    
        [Column("name")]
        public string Name { get; set; }
    }
    

    Reader.GetTable 包含:

                using (var R = await Q.ExecuteReaderAsync())
                {
                    List<T> Result = new List<T>();
    
                    Dictionary<int, PropertyInfo> Props = new Dictionary<int, PropertyInfo>();
    
                    foreach (var p in typeof(T).GetProperties())
                    {
                        for (int i = 0; i < R.FieldCount; i++)
                        {
                            if (p.GetCustomAttributes<ColumnAttribute>().FirstOrDefault(t => t.Name == R.GetName(i)) != null
                                && p.PropertyType == R.GetFieldType(i))
                            {
                                Props.Add(i, p);
                            }
                        }
                    }
    
                    while (await R.ReadAsync())
                    {
                        T row = new T();
    
                        foreach (var kvp in Props)
                        {
                            kvp.Value.SetValue(row, R[kvp.Key]);
                        }
    
                        Result.Add(row);
                    }
    
                    return Result;
                }
    

    【讨论】:

      【解决方案3】:

      我找到了这个解决方案。

      var cmd = ctx.Connection.CreateCommand();
      
      T result = DbDataReaderdHelper.Fill<T>(cmd)
      
      public static class DbDataReaderdHelper
      {
          public static List<T> Fill<T>(DbCommand dbCommand) where T : new()
          {
              List<T> result = new List<T>();
              var reader = dbCommand.ExecuteReader();
      
              if (reader.HasRows)
              {
                  while (reader.Read())
                  {
                      Type type = typeof(T);
                      T obj = (T)Activator.CreateInstance(type);
                      PropertyInfo[] properties = type.GetProperties();
      
                      foreach (PropertyInfo property in properties)
                      {
                          var value = reader[property.Name];
      
                          try
                          {
                              if (value != null)
                              {
                                  var convertedValue = TypeDescriptor.GetConverter(property.PropertyType).ConvertFromInvariantString(value.ToString());
      
                                  property.SetValue(obj, convertedValue);
                              }
                          }
                          catch {}
                      }
                      result.Add(obj);
                  }
              }
      
              reader.Close();
      
              return result;
          }
      }
      

      【讨论】:

        【解决方案4】:

        像魔法一样

        我个人讨厌在构造函数中进行手动映射,我也不喜欢自己进行反思。所以这里有另一个解决方案,由美妙的(而且相当普遍的)Newtonsoft JSON 库提供。

        仅当您的属性名称与 datareader 列名称完全匹配时它才会起作用,但它对我们来说效果很好。

        ...假设您有一个数据读取器名称“yourDataReader”...

                var dt = new DataTable();
                dt.Load(yourDataReader);
                // creates a json array of objects
                string json = Newtonsoft.Json.JsonConvert.SerializeObject(dt);
                // this is what you're looking for right??
                List<YourEntityType> list = 
        Newtonsoft.Json.JsonConvert
        .DeserializeObject<List<YourEntityType>>(json);
        

        【讨论】:

          【解决方案5】:

          对于 .NET Core 2.0:

          这是一个与 .NET CORE 2.0 一起使用的扩展方法,用于执行 RAW SQL 并将结果映射到任意类型的 LIST:

          用法:

           var theViewModel = new List();
           string theQuery = @"SELECT * FROM dbo.Something";
           theViewModel = DataSQLHelper.ExecSQL(theQuery,_context);
          
           using Microsoft.EntityFrameworkCore;
           using System.Data;
           using System.Data.SqlClient;
           using System.Reflection;
          
          public static List ExecSQL(string query, myDBcontext context)
           {
           using (context)
           {
           using (var command = context.Database.GetDbConnection().CreateCommand())
           {
           command.CommandText = query;
           command.CommandType = CommandType.Text;
           context.Database.OpenConnection();
                          using (var result = command.ExecuteReader())
                          {
                              List<T> list = new List<T>();
                              T obj = default(T);
                              while (result.Read())
                              {
                                  obj = Activator.CreateInstance<T>();
                                  foreach (PropertyInfo prop in obj.GetType().GetProperties())
                                  {
                                      if (!object.Equals(result[prop.Name], DBNull.Value))
                                      {
                                          prop.SetValue(obj, result[prop.Name], null);
                                      }
                                  }
                                  list.Add(obj);
                              }
                              return list;
          
                          }
                      }
                  }
              }
          

          【讨论】:

            【解决方案6】:

            最简单的解决方案:

            var dt=new DataTable();
            dt.Load(myDataReader);
            list<DataRow> dr=dt.AsEnumerable().ToList();
            

            然后选择它们以便将它们映射到任何类型。

            【讨论】:

              【解决方案7】:

              注意:这是 .NET Core 代码

              如果您不介意外部依赖项(惊人的Fast Member nuget 包),这是一个愚蠢的性能选项:

              public static T ConvertToObject<T>(this SqlDataReader rd) where T : class, new()
              {
              
                  Type type = typeof(T);
                  var accessor = TypeAccessor.Create(type);
                  var members = accessor.GetMembers();
                  var t = new T();
              
                  for (int i = 0; i < rd.FieldCount; i++)
                  {
                      if (!rd.IsDBNull(i))
                      {
                          string fieldName = rd.GetName(i);
              
                          if (members.Any(m => string.Equals(m.Name, fieldName, StringComparison.OrdinalIgnoreCase)))
                          {
                              accessor[t, fieldName] = rd.GetValue(i);
                          }
                      }
                  }
              
                  return t;
              }
              

              使用方法:

              public IEnumerable<T> GetResults<T>(SqlDataReader dr) where T : class, new()
              {
                  while (dr.Read())
                  {
                      yield return dr.ConvertToObject<T>());
                  }
              }
              

              【讨论】:

                【解决方案8】:

                我们已经实施了以下解决方案,感觉效果很好。它非常简单,并且需要比映射器做的更多的接线。但是,有时手动控制也不错,老实说,您只需连接一次就完成了。

                简而言之:我们的域模型实现了一个接口,该接口具有一个方法,该方法接受IDataReader 并从中填充模型属性。然后我们使用泛型和反射来创建模型的实例并在其上调用Parse 方法。

                我们考虑使用构造函数并将IDataReader 传递给它,但我们所做的基本 性能检查似乎表明接口始终更快(如果只是一点点)。此外,接口路由通过编译错误提供即时反馈。

                我喜欢的一件事是,您可以将private set 用于下面示例中的Age 等属性,并直接从数据库中设置它们。

                public interface IDataReaderParser
                {
                    void Parse(IDataReader reader);
                }
                
                public class Foo : IDataReaderParser
                {
                    public string Name { get; set; }
                    public int Age { get; private set; }
                
                    public void Parse(IDataReader reader)
                    {
                        Name = reader["Name"] as string;
                        Age = Convert.ToInt32(reader["Age"]);
                    }
                }
                
                public class DataLoader
                {
                    public static IEnumerable<TEntity> GetRecords<TEntity>(string connectionStringName, string storedProcedureName, IEnumerable<SqlParameter> parameters = null)
                                where TEntity : IDataReaderParser, new()
                    {
                        using (var sqlCommand = new SqlCommand(storedProcedureName, Connections.GetSqlConnection(connectionStringName)))
                        {
                            using (sqlCommand.Connection)
                            {
                                sqlCommand.CommandType = CommandType.StoredProcedure;
                                AssignParameters(parameters, sqlCommand);
                                sqlCommand.Connection.Open();
                
                                using (var sqlDataReader = sqlCommand.ExecuteReader())
                                {
                                    while (sqlDataReader.Read())
                                    {
                                        //Create an instance and parse the reader to set the properties
                                        var entity = new TEntity();
                                        entity.Parse(sqlDataReader);
                                        yield return entity;
                                    }
                                }
                            }
                        }
                    }
                }
                

                要调用它,您只需提供类型参数

                IEnumerable<Foo> foos = DataLoader.GetRecords<Foo>(/* params */)
                

                【讨论】:

                • 我喜欢Interface的解决方案。也许使用 new() 作为 T 的 where 子句会很好。所以你不需要像 Activator 类这样的反射。
                • @Sebi 谢谢你的好建议!不知何故,我忘记了泛型的能力!请注意,您需要添加 new() 作为通用约束。感谢您也善意地表达它,没有反对和反对的投票!
                • @Aim5475 谢谢你的回答,但不是很像这个吗? stackoverflow.com/a/1202973/15928
                • @Anthony 在再次仔细查看“更新”后,我想我同意你的看法,所以没有不尊重该解决方案。 IMO,我觉得我的更干净一点,因为我不必将两个函数传递给“GetRecords”方法,只需一个类型参数。 #moreThanOneWayToSkinACat
                • 这个解决方案仍然有效!刚刚尝试使用 .NET Core 3.1,与使用两个函数的第一个答案相比,首先为 DB 和代码分离和重用要简单得多
                【解决方案9】:

                你真的需要一个列表,还是 IEnumerable 足够好?

                我知道您希望它是通用的,但更常见的模式是在接受数据行(或 IDataRecord)的目标对象类型上有一个静态工厂方法。看起来像这样:

                public class Employee
                {
                    public int Id { get; set; }
                    public string Name { get; set; }
                
                    public static Employee Create(IDataRecord record)
                    {
                        return new Employee
                        {
                           Id = record["id"],
                           Name = record["name"]
                        };
                    }
                }
                

                .

                public IEnumerable<Employee> GetEmployees()
                {
                    using (var reader = YourLibraryFunction())
                    {
                       while (reader.Read())
                       {
                           yield return Employee.Create(reader);
                       }
                    }
                }
                

                如果你真的需要一个列表而不是 IEnumerable,你可以在结果上调用.ToList()。我想您也可以使用泛型 + 委托来使此模式的代码也更可重用。

                更新:我今天又看到了,想写通用代码:

                public IEnumerable<T> GetData<T>(IDataReader reader, Func<IDataRecord, T> BuildObject)
                {
                    try
                    {
                        while (reader.Read())
                        {
                            yield return BuildObject(reader);
                        }
                    }
                    finally
                    {
                         reader.Dispose();
                    }
                }
                
                //call it like this:
                var result = GetData(YourLibraryFunction(), Employee.Create);
                

                【讨论】:

                • 对于返回IEnumerable,然后可能会调用ToList,我肯定有同样的看法。
                • 这比我当时想的还要容易:在很多情况下,泛型方法可以根据你传递给它的委托来推断类型。
                • 由于“yield return”推迟了执行,更新的版本对我不起作用,因为当它执行时,DataReader 已经关闭。不过,这仍然是一个很好的例子。
                • @Korey 我在 Stack Overflow 上的其他几个地方使用过这种模式,不同之处在于,我不接受数据读取器作为函数的参数,而是在首次创建 datareader 以返回 IEnumerable 而不是 datareader 或其他内容的位置。
                • @JoelCoehoorn,我认为,Id = record["id"],应该从对象转换为 int 吗?否则会有例外!
                【解决方案10】:

                虽然我不建议将此用于生产代码,但您可以使用反射和泛型自动执行此操作:

                public static class DataRecordHelper
                {
                    public static void CreateRecord<T>(IDataRecord record, T myClass)
                    {
                        PropertyInfo[] propertyInfos = typeof(T).GetProperties();
                
                        for (int i = 0; i < record.FieldCount; i++)
                        {
                            foreach (PropertyInfo propertyInfo in propertyInfos)
                            {
                                if (propertyInfo.Name == record.GetName(i))
                                {
                                    propertyInfo.SetValue(myClass, Convert.ChangeType(record.GetValue(i), record.GetFieldType(i)), null);
                                    break;
                                }
                            }
                        }
                    }
                }
                
                public class Employee
                {
                    public int Id { get; set; }
                    public string LastName { get; set; }
                    public DateTime? BirthDate { get; set; }
                
                    public static IDataReader GetEmployeesReader()
                    {
                        SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["NorthwindConnectionString"].ConnectionString);
                
                        conn.Open();
                        using (SqlCommand cmd = new SqlCommand("SELECT EmployeeID As Id, LastName, BirthDate FROM Employees"))
                        {
                            cmd.Connection = conn;
                            return cmd.ExecuteReader(CommandBehavior.CloseConnection);
                        }
                    }
                
                    public static IEnumerable GetEmployees()
                    {
                        IDataReader rdr = GetEmployeesReader();
                        while (rdr.Read())
                        {
                            Employee emp = new Employee();
                            DataRecordHelper.CreateRecord<Employee>(rdr, emp);
                
                            yield return emp;
                        }
                    }
                }
                

                然后您可以使用CreateRecord&lt;T&gt;() 从数据读取器的字段中实例化任何类。

                <asp:GridView ID="GvEmps" runat="server" AutoGenerateColumns="true"></asp:GridView>
                
                GvEmps.DataSource = Employee.GetEmployees();
                GvEmps.DataBind();
                

                【讨论】:

                • 在生产环境中你不推荐什么?
                • 因为它“感觉”错了。从数据读取器自动设置属性意味着您对错误检查的控制较少,并且使用反射很昂贵。我不觉得它很强大,尽管它应该可以工作。但是,如果您认真使用这种技术,那么您最好寻找合适的 ORM 映射解决方案,例如 LinqToSql、Entity Framwork、nHibernate 等。
                • @DanDiplo 完全正确!这是大多数映射器喜欢做的事情,只有我们程序员知道它有多脆弱。我们应该考虑领域对象,元编程应该是最后的手段!
                • 使用一些缓存来提高反射性能,如here 所示,基本思想是在循环内不使用反射..
                • 我不确定我是否同意建议的反射脆性。我已经在许多生产环境中将它用于此应用程序,并取得了巨大成功。反射当然会带来很多开销,但是缓存 grunt 工作的结果可以消除很多开销。在自动设置属性方面,我在这里也不同意。假设您使用泛型并传递一个对象,您可以使用反射来调用特定的构造函数等。
                【解决方案11】:

                您可以构建一个扩展方法,例如:

                public static List<T> ReadList<T>(this IDataReader reader, 
                                                  Func<IDataRecord, T> generator) {
                     var list = new List<T>();
                     while (reader.Read())
                         list.Add(generator(reader));
                     return list;
                }
                

                并像这样使用它:

                var employeeList = reader.ReadList(x => new Employee {
                                                               Name = x.GetString(0),
                                                               Age = x.GetInt32(1)
                                                        });
                

                乔尔的建议很好。您可以选择返回IEnumerable&lt;T&gt;。上面的代码很容易转换:

                public static IEnumerable<T> GetEnumerator<T>(this IDataReader reader, 
                                                              Func<IDataRecord, T> generator) {
                     while (reader.Read())
                         yield return generator(reader);
                }
                

                如果要自动将列映射到属性,代码思路是一样的。您可以将上述代码中的generator 函数替换为一个函数,该函数询问typeof(T) 并通过读取匹配的列使用反射设置对象的属性。但是,我个人更喜欢定义一个工厂方法(就像 Joel 的回答中提到的那样)并将它的委托传递给这个函数:

                 var list = dataReader.GetEnumerator(Employee.Create).ToList();
                

                【讨论】:

                • 0 否决 非常好的答案。 Linq 太酷了。因为终于可以读到这样的代码并欣赏它,所以自我感觉良好是不是很自大?
                • “0 票否决”不属于!我对此投了赞成票。复制和粘贴魔鬼让我来到这里。
                • 当我复制并粘贴这个时,我得到'找不到类型或命名空间名称'T''。我可能遗漏了一些明显的东西。想法?
                • 为什么是发电机?问题指定属性映射到列。
                • 安东尼:哎呀。我错过了一般论点。固定。
                猜你喜欢
                • 2016-06-02
                • 2020-11-20
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2015-10-17
                • 1970-01-01
                • 2018-01-21
                • 2018-09-01
                相关资源
                最近更新 更多