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

Я могу получить эти данные из моей базы данных следующим образом:

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

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

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

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

Ответ 1

Я решил предложить другой ответ, который фактически расширяет ответ, предоставленный Алексом (так что все ему кредиты), но он вводит атрибуты для сопоставления имен столбцов и имен 2. p >

Прежде всего нужен настраиваемый атрибут для хранения имени столбца:

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

Ответ 2

Вам нужно либо отобразить свойства самостоятельно, либо использовать 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-запросы, что также может быть полезно.

Ответ 3

Если вы не хотите использовать структуру ORM (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-инфраструктуру.

Ответ 4

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

Ответ 5

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

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

  • После получения записи из базы данных создайте объект класса Product и перечислите свойства. Для каждого свойства, имеющего пользовательский атрибут, используйте SetValue of PropertyInfo для изменения значения в соответствии с именем столбца, определенным в пользовательском атрибуте.

Учитывайте следующее:

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

Ответ 7

Затем вы должны использовать 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

Ответ 8

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