Как я могу заполнить класс из результатов запроса 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
- и вы хотите написать один код для автоматической инициализации всех них.
- отражение - это накладные расходы - поэтому в конечном итоге потребуется некоторое кэширование
Ответ 6
Одно из возможных решений:
В запросе SQL вы можете использовать PATH Mode with FOR XML.
Результатом запроса будет XML, который вы можете десериализовать непосредственно на объекты С#.
Он также отлично работает при больших вложенных SQL-запросах.
Ответ 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