13

我有这样的课:

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。

是否有某种属性可以应用于我的属性/类以使该类从查询行中自动填充?

4

8 回答 8

16

我决定提出另一个答案,它实际上是对 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;
}
于 2012-07-27T12:27:07.957 回答
14

您需要自己映射属性或使用 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 语句,这也可能很有用。

于 2012-07-27T11:13:03.433 回答
6

如果您不想利用 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 框架会好得多。

于 2012-07-27T11:30:43.597 回答
2

我会使用 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;
    }
}
于 2012-07-27T11:11:40.003 回答
1

一种可能的解决方案:

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

查询的结果将是一个 XML,您可以将其直接反序列化为 C# 对象

它也适用于大型嵌套 SQL 查询。

于 2013-06-10T08:54:41.567 回答
1

默认情况下,原始 .NET Framework 中没有此类功能。您可以使用实体框架,但如果它对您来说不是一个好的解决方案,那么另一种选择是反射机制。

  1. 创建一些自定义属性类,该类可以为类的每个公共属性保存一个列名。

  2. 从数据库中检索记录后,实例化一个Product类对象并枚举属性。对于具有您自定义属性的每个属性 - 用于SetValue根据PropertyInfo自定义属性中定义的列名称更改值。

考虑以下因素:

  • 解决方案对于简单的任务来说是相当且开销的;仅当您有许多表和许多类时才有意义Product-并且希望编写一个代码来自动初始化所​​有这些
  • 反射本身就是一种开销 - 所以从长远来看需要一些缓存
于 2012-07-27T11:23:42.740 回答
0

然后你应该使用实体框架或 Linq To SQL,如果你不想使用它,那么你需要自己映射/填充它

有关实体框架的更多信息 http://msdn.microsoft.com/en-us/data/ef.aspx

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

于 2012-07-27T11:07:29.723 回答
-1
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
于 2016-07-29T12:31:39.483 回答