簡體   English   中英

如何從 C# 中的 SQL 查詢結果填充類?

[英]How can I populate a class from the results of a SQL query in C#?

我有這樣的課:

public class Product
{
    public int ProductId { get; private set; }
    public int SupplierId { get; private set; }

    public string Name { get; private set; }
    public decimal Price { get; private set; }
    public int Stock { get; private set; }
    public int PendingStock { get; private set; }
}

我可以像這樣從我的數據庫中獲取這些詳細信息:

SELECT product_id, supplier_id, name, price, total_stock, pending_stock 
FROM products
WHERE product_id = ?

我不想手動運行DataSetDataTable來設置值。

我確信有一種方法可以使用某種綁定/映射機制來填充類,但我能找到的唯一東西是綁定到 winforms 組件或使用 XAML。

是否有某種屬性可以應用於我的屬性/類以使該類從查詢行中自動填充?

我決定提出另一個答案,它實際上是對 Alex 提供的答案的擴展(所以所有的功勞都歸於他),但是為了 column-name-2-property-name 映射,它引入了屬性。

首先需要自定義屬性來保存列名:

[AttributeUsage(AttributeTargets.Property, Inherited = true)]
[Serializable]
public class MappingAttribute : Attribute
{
    public string ColumnName = null;
}

該屬性必須應用於要從數據庫行填充的類的那些屬性:

public class Product
{
    [Mapping(ColumnName = "product_id")]
    public int ProductId { get; private set; }

    [Mapping(ColumnName = "supplier_id")]
    public int SupplierId { get; private set; }

    [Mapping(ColumnName = "name")]
    public string Name { get; private set; }
    [Mapping(ColumnName = "price")]
    public decimal Price { get; private set; }
    [Mapping(ColumnName = "total_stock")]
    public int Stock { get; private set; }
    [Mapping(ColumnName = "pending_stock")]
    public int PendingStock { get; private set; }
}

其余的都按照 Alex 的建議進行,只是該屬性用於檢索列名:

T MapToClass<T>(SqlDataReader reader) where T : class
{
        T returnedObject = Activator.CreateInstance<T>();
        PropertyInfo[] modelProperties = returnedObject.GetType().GetProperties();
        for (int i = 0; i < modelProperties.Length; i++)
        {
            MappingAttribute[] attributes = modelProperties[i].GetCustomAttributes<MappingAttribute>(true).ToArray();

            if (attributes.Length > 0 && attributes[0].ColumnName != null)
                modelProperties[i].SetValue(returnedObject, Convert.ChangeType(reader[attributes[0].ColumnName], modelProperties[i].PropertyType), null);
        }
        return returnedObject;
}

您需要自己映射屬性或使用 ORM(對象關系映射器)。

Microsoft 提供Entity Framework ,但Dapper需要較少的開銷,並且根據您的要求可能是一個可行的選擇。

在您的情況下,Dapper 代碼如下所示:

var query = @"SELECT product_id, supplier_id, name, price, total_stock, pending_stock 
FROM products
WHERE product_id = @id";

var product = connection.Query<Product>(query, new { id = 23 });

為了完整起見,重要的是要指出我在這里談論的是 Dapper,因為問題涉及將 SQL 結果映射到對象。 EF 和 Linq to SQL 也會這樣做,但它們也會做一些額外的事情,比如將 Linq 查詢轉換為 SQL 語句,這也可能很有用。

如果您不想利用 ORM 框架(實體框架等),您可以手動完成:

T MapToClass<T>(SqlDataReader reader) where T : class
{
        T returnedObject = Activator.CreateInstance<T>();
        List<PropertyInfo> modelProperties = returnedObject.GetType().GetProperties().OrderBy(p => p.MetadataToken).ToList();
        for (int i = 0; i < modelProperties.Count; i++)
            modelProperties[i].SetValue(returnedObject, Convert.ChangeType(reader.GetValue(i), modelProperties[i].PropertyType), null);
        return returnedObject;
}

你像這樣使用它:

Product P = new Product(); // as per your example
using(SqlDataReader reader = ...)
{
while(reader.Read()) { P = MapToClass<Product(reader); /* then you use P */ }
}

唯一需要注意的是查詢中字段的順序(它必須與您的類中定義的屬性的順序相匹配)。

您需要做的就是構建類,編寫查詢,然后它將處理“映射”。

警告我經常使用這種方法,從來沒有遇到過任何問題,但它不適用於部分類 如果您使用部分模型,那么無論如何使用 ORM 框架會好得多。

我會使用 Linq to SQL 並按如下方式執行:

public class Product
{
    public int ProductId { get; private set; }
    public int SupplierId { get; private set; }
    public string Name { get; private set; }
    public decimal Price { get; private set; }
    public int Stock { get; private set; }
    public int PendingStock { get; private set; }

    public Product(int id)
    {
        using(var db = new MainContext())
        {
            var q = (from c in product where c.ProductID = id select c).SingleOrDefault();
            if(q!=null)
                LoadByRec(q);           
        }
    }
    public Product(product rec)
    {
        LoadByRec(q);
    }
    public void LoadByRec(product rec)
    {
        ProductId = rec.product_id;
        SupplierID = rec.supplier_id;
        Name = rec.name;
        Price = rec.price;
        Stock = rec.total_stock;
        PendingStock = rec.pending_stock;
    }
}

默認情況下,原始 .NET Framework 中沒有此類功能。 您可以使用實體框架,但如果它對您來說不是一個好的解決方案,那么另一種選擇是反射機制。

  1. 創建一些自定義屬性類,該類可以為類的每個公共屬性保存一個列名。

  2. 從數據庫中檢索記錄后,實例化Product類的對象並枚舉屬性。 對於具有自定義屬性的每個屬性 - 使用PropertyInfoSetValue根據自定義屬性中定義的列名稱更改值。

考慮以下因素:

  • 解決方案對於簡單的任務來說是相當且開銷的; 僅當您有許多表和許多類(如Product )時才有意義 - 並且希望編寫一個代碼來自動初始化所​​有這些
  • 反射本身就是一種開銷 - 所以從長遠來看需要一些緩存

一種可能的解決方案:

在 SQL 查詢中,您可以使用“PATH Mode with FOR XML”子句。

查詢的結果將是一個 XML,您可以將其直接反序列化為 C# 對象

它也適用於大型嵌套 SQL 查詢。

然后你應該使用實體框架或 Linq To SQL,如果你不想使用它,那么你需要自己映射/填充它

有關實體框架的更多信息http://msdn.microsoft.com/en-us/data/ef.aspx

有關 Linq to SQL 的更多信息http://msdn.microsoft.com/en-us/library/bb386976.aspx

使用實體框架,您可以使用 SqlQuery 方法來填充不是表或視圖的類: context.Database.SqlQuery(sql, parameterList)

當然你需要使用EF。 :-)

select 'public ' + case DATA_TYPE  
when 'varchar' then 'string'
when 'nvarchar' then 'string'
when 'DateTime' then 'DateTime'
when 'bigint' then 'long' 
else DATA_TYPE end +' '+ COLUMN_NAME + ' {get; set;}' 
from INFORMATION_SCHEMA.COLUMNS  WHERE TABLE_NAME ='YOUR TABLE NAME'  ORDER BY ORDINAL_POSITION

暫無
暫無

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

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