簡體   English   中英

如何將 sql 查詢的結果映射到對象上?

[英]How can I map the results of a sql query onto objects?

目前,我正在使用這樣的東西:

    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 並且已經過徹底測試,所以我現在不想引入更多錯誤。 有什么建議么?

一個簡單的解決方案是為您的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 中測試了這段代碼,它應該可以工作。

您可以通過為您的要求創建通用方法來實現。 您也可以將新方法作為數據表的擴展。

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

您應該研究 MicroORM。 與提供必須使用的 SDL 的常規 ORM 不同,MicroORM 允許您使用自己的 SQL 查詢,並且僅提供從 SQL 結果集到 C# 對象以及從 C# 對象到 SQL 參數的映射。

我最喜歡的是PetaPoco ,它也提供了一個查詢構建器,它使用您自己的 SQL 但對參數編號進行了一些巧妙的操作。

@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<>()函數將從屬性名稱中假定列的名稱。

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<>()方法並獲取序列化對象的列表

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

假設:如果您只需要用於序列化或簡單臨時輸出的對象。

您可以像這樣使用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 結果/輸出的替代懶惰方法)。

下面的函數接受一個 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;
        }
    }
}

在搜索此答案時,我發現您可以使用 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();
        }

雖然這個問題一直存在,但我找不到一個干凈的解決方案。 為了我的目的,我想出了以下在我的情況下效果很好的方法。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM