Как я могу заполнить класс из результатов SQL-запроса в 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 = ?

Я не хочу вручную запускать через DataSet или DataTable для установки значений.

Я уверен, что есть способ заполнить класс, используя какой-то механизм привязки / сопоставления, но единственное, что я мог найти, это привязка к компонентам winforms или использование XAML.

есть ли какой-то атрибут, я могу применить к моей свойства / класс, чтобы класс автоматически заполнялся из строки запроса?

8 ответов


я решил предложить другой ответ, который на самом деле является расширением ответа, предоставленного Алексом (так что все кредиты ему), но он вводит атрибуты ради сопоставления столбцов-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; }
}

и остальное идет как Алекс предложил, за исключением того, что атрибут используется для получения имени столбца:

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 (Object relational mapper).

Microsoft предоставляет Entity Framework, а щеголеватый требует меньше ресурсов и может быть жизнеспособным вариантом в зависимости от ваших потребностей.

в вашем случае код 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 framework (Entity Framework и т. д.) вы можете сделать это вручную:

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 framework.


Я бы использовал 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;
    }
}

в raw .NET Framework по умолчанию такой функции нет. Вы можете использовать Entity Framework, но если это не хорошее решение для вас, альтернативой будет механизм отражения.

  1. создайте пользовательский класс атрибутов, который может содержать имя столбца для каждого общедоступного свойства класса.

  2. после извлечения записи из базы данных экземпляр объекта Product class и перечислить свойства. Для каждого свойства, которое у вас есть пользовательский атрибут-use SetValue of PropertyInfo изменить значение в соответствии с именем столбца, определенным в пользовательском атрибуте.

примите во внимание следующее:

  • решение довольно и накладные расходы на простые задания; это имеет смысл, только если у вас есть много таблиц и много классов, таких как Product - и хотите написать один код, чтобы автоматически инициализировать их все
  • отражение-это накладные расходы, поэтому некоторое кэширование требуется в долгосрочной перспективе

одно из возможных решений:

в SQL query вы можете использовать "режим пути С для XML" предложения.

результатом запроса будет XML, который вы можете десериализовать непосредственно в объекты C#.

Он также очень хорошо работает на больших вложенных SQL-запросах.


тогда вы должны использовать Entity Framework или Linq to SQL, и если вы не хотите использовать это, то вам нужно сопоставить / заполнить его yr self

дополнительная информация о Entity Framework http://msdn.microsoft.com/en-us/data/ef.aspx

дополнительная информация о Linq to SQL http://msdn.microsoft.com/en-us/library/bb386976.aspx


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