【问题标题】:How can I map the results of a sql query onto objects?如何将 sql 查询的结果映射到对象上?
【发布时间】:2012-06-30 02:38:23
【问题描述】:

目前,我正在使用这样的东西:

    try
    {
      dr = SQL.Execute(sql);

      if(dr != null) {
         while(dr.Read()) {
           CustomObject c = new CustomObject();
           c.Key = dr[0].ToString();
           c.Value = dr[1].ToString();
           c.Meta = dr[2].ToString();
           customerInfo.CustomerList.Add(c);
         }
      }
      else
      {
          customerInfo.ErrorDetails="No records found";
      } 

有没有一种方法可以直接进行此映射,而不是我手动进行分配(假设列名与字段名匹配)。

然而,一个要求是我想通过我目前使用 sql 查询的方法而不是使用纯基于 LINQ 的方法来做到这一点。一方面,SQL 查询足够大,涉及复杂的 JOIN 并且已经过彻底的测试,所以我现在不想引入更多的错误。有什么建议吗?

【问题讨论】:

  • 请注意,您可以将 LINQ 与自定义 SQL 查询一起使用,它会自动将查询结果中的字段映射到您提供的通用对象类型。见DataContext.ExecuteQuery
  • @mellamokb:你能告诉我怎么做吗?或者至少给我指出一些资源或者我应该搜索什么?
  • 看一下我添加的链接,这是您需要的具体方法。该页面上也有示例。
  • @mellamokb:哇...请将此添加为答案。它将帮助其他人寻找类似的东西。
  • 看看 Sam Saffron 写的 Dapper.NET(他在 Stackoverflow 工作时)

标签: c# sql


【解决方案1】:

一个简单的解决方案是为您的CustomObject 创建一个构造函数,该构造函数采用DataRow(来自示例,所以如果它是另一个类,请纠正我)。

在你的新构造函数中,按照你自己的例子做。

public CustomObject(DataRow row)
{
    Key = row[0].ToString();
    // And so on...
}

另一种方法是引入泛型,并在您的 SQL 类中创建一个新函数

示例(从Passing arguments to C# generic new() of templated type获取代码):

// This function should reside in your SQL-class.
public IEnumerable<T> ExecuteObject<T>(string sql)
{
    List<T> items = new List<T>();
    var data = ExecuteDataTable(sql); // You probably need to build a ExecuteDataTable for your SQL-class.
    foreach(var row in data.Rows)
    {
        T item = (T)Activator.CreateInstance(typeof(T), row);
        items.Add(item);
    }
    return items;
}

示例用法:

public IEnumerable<CustomObject> GetCustomObjects()
{
    return SQL.ExecuteObject<CustomObject>("SELECT * FROM CustomObject");
}

我已经在 LinqPad 中测试过这段代码,它应该可以工作。

【讨论】:

    【解决方案2】:

    您可以通过为您的要求创建一个通用方法来实现。您还可以将新方法作为数据表的扩展。

        public static List<T> ToList<T>(this DataTable table) where T : class, new()
    {
        try
        {
            List<T> list = new List<T>();
    
            foreach (var row in table.AsEnumerable())
            {
                T obj = new T();
    
                foreach (var prop in obj.GetType().GetProperties())
                {
                    try
                    {
                        PropertyInfo propertyInfo = obj.GetType().GetProperty(prop.Name);
                        propertyInfo.SetValue(obj, Convert.ChangeType(row[prop.Name], propertyInfo.PropertyType), null);
                    }
                    catch
                    {
                        continue;
                    }
                }
    
                list.Add(obj);
            }
    
            return list;
        }
        catch
        {
            return null;
        }
    }
    

    }

    用法:

        DataTable dtCustomer = GetCustomers();
        List<CustomObject> CustomObjectList = dtCustomer.ToList<CustomObject>();
    

    【讨论】:

    • GetCustomers 是什么样的?
    • 我很害怕 try, catch {continue;} 的答案有赞成票...
    • @A.Rowan 公平地说,一个属性设置失败并不是问题,而是当我们尝试设置一个属性时未能跟踪到出现问题的事实。跨度>
    【解决方案3】:

    您应该研究一下 MicroORM。与提供您必须使用的 SDL 的常规 ORM 不同,MicroORM 允许您使用自己的 SQL 查询,并且只提供从 SQL 结果集到 C# 对象以及从 C# 对象到 SQL 参数的映射。

    我最喜欢的是PetaPoco,它还提供了一个查询构建器,它使用您自己的 SQL,但对参数编号进行了一些巧妙的操作。

    【讨论】:

      【解决方案4】:

      @user1553525 的回答很好,但是,如果您的列名与您的属性名不完全匹配,则它不起作用。

      所以首先你要创建一个自定义属性。然后在你的类中使用你试图反序列化的属性,最后,你想要反序列化 DataTable。

      自定义属性

      我们创建了一个自定义属性,该属性将应用于我们类内部的属性。我们创建的类具有属性 Name,稍后我们将使用该属性从 DataTable 中获取正确的列。

      [AttributeUsage(AttributeTargets.Property, Inherited = false)]
      public class MySqlColName : Attribute
      {
          private string _name = "";
          public string Name { get => _name; set => _name = value; }
      
          public MySqlColName(string name)
          {
              _name = name;
          }
      }
      

      要反序列化的类

      接下来,在我们要填充的类中,我们将使用我们刚刚创建的属性[MySqlColName] 声明将链接到类中的属性的列名。

      但是,如果属性名称与数据库列相同,我们不需要在属性中指定列名称,因为.ToList&lt;&gt;() 函数将假定属性名称中的列名称。

      public class EventInfo
      {
          [MySqlColName("ID")]
          public int EventID { get; set; }
      
          //Notice there is no attribute on this property? 
          public string Name { get; set; }
      
          [MySqlColName("State")]
          public string State { get; set; }
      
          [MySqlColName("Start_Date")]
          public DateTime StartDate { get; set; }
      
          [MySqlColName("End_Date")]
          public DateTime EndDate { get; set; }
      
      }
      

      DataTable ToList 扩展方法

      最后,我们修改@user1553525 的答案,添加检查以查看是否提供了我们的自定义属性。如果是,那么我们将列的名称设置为提供的名称,否则,我们使用属性名称(参见 try 块内的代码)。

      public static List<T> ToList<T>(this DataTable table) where T : class, new()
      {
          try
          {
              List<T> list = new List<T>();
      
              foreach (var row in table.AsEnumerable())
              {
                  T obj = new T();
      
                  foreach (var prop in obj.GetType().GetProperties())
                  {
                      try
                      {
                          //Set the column name to be the name of the property
                          string ColumnName = prop.Name;
      
                          //Get a list of all of the attributes on the property
                          object[] attrs = prop.GetCustomAttributes(true);
                          foreach (object attr in attrs)
                          {
                              //Check if there is a custom property name
                              if (attr is MySqlColName colName)
                              {
                                  //If the custom column name is specified overwrite property name
                                  if (!colName.Name.IsNullOrWhiteSpace())                                        
                                      ColumnName = colName.Name;
                              }
                          }
      
                          PropertyInfo propertyInfo = obj.GetType().GetProperty(prop.Name);
      
                          //GET THE COLUMN NAME OFF THE ATTRIBUTE OR THE NAME OF THE PROPERTY
                          propertyInfo.SetValue(obj, Convert.ChangeType(row[ColumnName], propertyInfo.PropertyType), null);
                      }
                      catch
                      {
                          continue;
                      }
                  }
      
                  list.Add(obj);
              }
      
              return list;
          }
          catch
          {
              return null;
          }
      }//END METHOD
      

      用法

      最后,我们可以调用.ToList&lt;&gt;()方法,获取序列化对象列表

      List<EventInfo> CustomObjectList;
      
      using (DataTable dtCustomer = GetDataTable("SELECT * FROM EventIndex"))
      {
          CustomObjectList = dtCustomer.ToList<EventInfo>();
      }
      

      旁注:我使用了一些自定义方法

      public static bool IsNullOrWhiteSpace(this string x)
      {
          return string.IsNullOrWhiteSpace(x);
      }
      
      public static DataTable GetDataTable(string Query)
      {
          MySqlConnection connection = new MySqlConnection("<Connection_String>");
          try
          {            
              DataTable data = new DataTable();
              connection.Open();
              using (MySqlCommand command = new MySqlCommand(Query, connection))
              {
                  data.Load(command.ExecuteReader());
              }
              return data;
      
          }
          catch (Exception ex)
          {
              // handle exception here
              Console.WriteLine(ex);
              throw ex;
          }
          finally
          {
              connection.Close();
          }            
      }
      

      【讨论】:

      • 很棒的分享。考虑到我们中的许多人无法控制数据库,或者有人可能用下划线标记了其中的某些内容,我不想在我的属性名称中添加这些内容。
      【解决方案5】:

      假设:如果您只需要用于序列化或简单的临时输出的对象。

      您可以像这样使用ExpandoObjectSqlDataReader.GetSchemaTable()

          private IEnumerable<dynamic> ReaderToAnonymmous(SqlCommand comm) {
              using (var reader = comm.ExecuteReader()) {
                  var schemaTable = reader.GetSchemaTable();
      
                  List<string> colnames = new List<string>();
                  foreach (DataRow row in schemaTable.Rows) {
                      colnames.Add(row["ColumnName"].ToString());
                  }
      
                  while (reader.Read()) {
                      var data = new ExpandoObject() as IDictionary<string, Object>;
                      foreach (string colname in colnames) {
                          var val = reader[colname];
                          data.Add(colname, Convert.IsDBNull(val) ? null : val);
                      }
      
                      yield return (ExpandoObject)data;
                  }
              }
          }
      

      尽管发布了更快的解决方案(我将其发布为临时 SQL/Reader 结果/输出的替代惰性方法)。

      【讨论】:

      • 这太棒了,正是我一直在寻找的。我是控制台应用程序,它从存储过程生成 excel 报告,使用这段代码,我不必在应用程序中映射过程。谢谢。
      【解决方案6】:

      以下函数接受一个 SQL 字符串和一个对象,它要求对象在 select 语句中的每一列都有一个属性。该对象必须被实例化。

      public object SqlToSingleObject(string sSql, object o)
      {
          MySql.Data.MySqlClient.MySqlDataReader oRead;
          using (ConnectionHelper oDb = new ConnectionHelper())
          {
              oRead = oDb.Execute(sSql);
              if (oRead.Read())
              {
                  for (int i = 0; i < oRead.FieldCount; i++)
                  {
                      System.Reflection.PropertyInfo propertyInfo = o.GetType().GetProperty(oRead.GetName(i));
                      propertyInfo.SetValue(o, Convert.ChangeType(oRead[i], propertyInfo.PropertyType), null);
                  }
      
                  return o;
              }
              else
              {
                  return null;
              }
          }
      }
      

      【讨论】:

      • 这有什么意义?如果您要创建所有属性,为什么不自己构建对象。
      【解决方案7】:

      在搜索这个答案时,我发现你可以使用 Dapper 库:https://dapper-tutorial.net/knowledge-base/44980945/querying-into-a-complex-object-with-dapper

      你可以这样使用:

              using (var connection = new SqlConnection(ConnectionString))
              {
                  connection.Open();
                  IList<CustomObject> result = connection.Query<CustomObject>(sql, commandType: CommandType.Text).ToList();
              }
      

      【讨论】:

        【解决方案8】:

        虽然这个问题一直存在,但我找不到一个干净的解决方案。出于我的目的,我想出了以下在我的情况下效果很好的方法。

            using System.Dynamic;
        
            private IEnumerable<ExpandoObject> GetQueryToList()
            {
                try
                {
                    using (var conn = new SqlConnection(ConnectionString))
                    using (var cmd = new SqlCommand(MyQuery, conn))
                    {
                        var list = new List<ExpandoObject>();
                        conn.Open();
                        var reader = cmd.ExecuteReader();
        
                        while (reader.Read())
                        {
                            var expandoObject = new ExpandoObject();
                            for (var i = 0; i < reader.FieldCount; i++)
                            {
                                ((IDictionary<string, object>) expandoObject).Add(
                                    reader.GetName(i), reader[i]);
                            }
                            list.Add(expandoObject);
                        }
        
                        reader.Close();
                        return list;
                    }
                }
                catch (Exception ex)
                {
                    var m = MethodBase.GetCurrentMethod();
                    Console.WriteLine(ex.Message + " " + m.Name);
                }
        
                return null;
            }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-02-11
          • 1970-01-01
          • 2020-04-16
          • 2020-12-08
          • 1970-01-01
          • 1970-01-01
          • 2018-05-04
          相关资源
          最近更新 更多