Как я могу сопоставить результаты запроса sql на объектах?
В настоящее время я использую что-то вроде этого:
try
{
dr = SQL.Execute(sql);
if(dr != null) {
while(dr.Read()) {
CustomObject c = new CustomObject();
c.Key = dr[0].ToString();
c.Value = dr[1].ToString();
c.Meta = dr[2].ToString();
customerInfo.CustomerList.Add(c);
}
}
else
{
customerInfo.ErrorDetails="No records found";
}
Вместо того, чтобы вручную делать привязки, есть ли способ сделать это сопоставление напрямую (предположим, что имена столбцов совпадают с именами полей).
Одно из требований, однако, заключается в том, что я хочу сделать это с помощью моего нынешнего подхода к использованию SQL-запросов, а не с использованием простых подходов, основанных на LINQ. Во-первых, SQL-запросы достаточно велики, связаны с сложными JOIN и тщательно протестированы, поэтому я не хочу вводить больше ошибок на данный момент. Любые предложения?
Ответы
Ответ 1
Одним простым решением было бы сделать конструктор для вашего CustomObject
, который принимает DataRow
(из примера, поэтому, если это другой класс, пожалуйста, исправьте меня).
И в вашем новом конструкторе сделайте так, как в своем собственном примере.
public CustomObject(DataRow row)
{
Key = row[0].ToString();
// And so on...
}
Другим способом будет введение дженериков и создание новой функции в вашем классе SQL
Пример (Взял код Передача аргументов в С# generic new() шаблонного типа):
// This function should reside in your SQL-class.
public IEnumerable<T> ExecuteObject<T>(string sql)
{
List<T> items = new List<T>();
var data = ExecuteDataTable(sql); // You probably need to build a ExecuteDataTable for your SQL-class.
foreach(var row in data.Rows)
{
T item = (T)Activator.CreateInstance(typeof(T), row);
items.Add(item);
}
return items;
}
Пример использования:
public IEnumerable<CustomObject> GetCustomObjects()
{
return SQL.ExecuteObject<CustomObject>("SELECT * FROM CustomObject");
}
Я тестировал этот код в LinqPad, он должен работать.
Ответ 2
Вы можете достичь, создав общий метод для вашего требования. Также вы можете сделать свой новый метод расширением для таблицы данных.
public static List<T> ToList<T>(this DataTable table) where T : class, new()
{
try
{
List<T> list = new List<T>();
foreach (var row in table.AsEnumerable())
{
T obj = new T();
foreach (var prop in obj.GetType().GetProperties())
{
try
{
PropertyInfo propertyInfo = obj.GetType().GetProperty(prop.Name);
propertyInfo.SetValue(obj, Convert.ChangeType(row[prop.Name], propertyInfo.PropertyType), null);
}
catch
{
continue;
}
}
list.Add(obj);
}
return list;
}
catch
{
return null;
}
}
}
Использование:
DataTable dtCustomer = GetCustomers();
List<CustomObject> CustomObjectList = dtCustomer.ToList<CustomObject>();
Ответ 3
Вы должны посмотреть на MicroORMs. В отличие от обычных ORM, которые предоставляют SDL, который вы должны использовать, MicroORMs позволяют вам использовать ваши собственные запросы SQL и обеспечивают только сопоставление наборов результатов SQL с объектами С# и из объектов С# с параметрами SQL.
Моим любимым является PetaPoco, который также предоставляет построитель запросов, который использует ваш собственный SQL, но выполняет некоторые аккуратные манипуляции с номерами параметров.
Ответ 4
Предположение:, если вам нужны объекты только для сериализации или простого ad-hoc-вывода.
Вы можете использовать ExpandoObject
и SqlDataReader.GetSchemaTable()
следующим образом:
private IEnumerable<dynamic> ReaderToAnonymmous(SqlCommand comm) {
using (var reader = comm.ExecuteReader()) {
var schemaTable = reader.GetSchemaTable();
List<string> colnames = new List<string>();
foreach (DataRow row in schemaTable.Rows) {
colnames.Add(row["ColumnName"].ToString());
}
while (reader.Read()) {
var data = new ExpandoObject() as IDictionary<string, Object>;
foreach (string colname in colnames) {
var val = reader[colname];
data.Add(colname, Convert.IsDBNull(val) ? null : val);
}
yield return (ExpandoObject)data;
}
}
}
Хотя есть более быстрые решения (я опубликовал это как альтернативный ленивый подход для специальных/SQL-результатов/результатов SQL).
Ответ 5
Следующая функция принимает строку SQL и объект, она требует, чтобы у объекта было свойство для каждого столбца в операторе select. Объект должен быть создан.
public object SqlToSingleObject(string sSql, object o)
{
MySql.Data.MySqlClient.MySqlDataReader oRead;
using (ConnectionHelper oDb = new ConnectionHelper())
{
oRead = oDb.Execute(sSql);
if (oRead.Read())
{
for (int i = 0; i < oRead.FieldCount; i++)
{
System.Reflection.PropertyInfo propertyInfo = o.GetType().GetProperty(oRead.GetName(i));
propertyInfo.SetValue(o, Convert.ChangeType(oRead[i], propertyInfo.PropertyType), null);
}
return o;
}
else
{
return null;
}
}
}
Ответ 6
При поиске этого ответа я обнаружил, что вы можете использовать библиотеку Dapper: https://dapper-tutorial.net/knowledge-base/44980945/querying-into-a-complex-object-with-dapper
Вы можете использовать что-то вроде этого:
using (var connection = new SqlConnection(ConnectionString))
{
connection.Open();
IList<CustomObject> result = connection.Query<CustomObject>(sql, commandType: CommandType.Text).ToList();
}
Ответ 7
@user1553525 Ответ отличный, однако, если имена столбцов не совпадают точно с именами свойств, он не работает.
Итак, сначала вы захотите создать собственный атрибут. Затем используйте атрибут в своем классе, который вы пытаетесь десериализовать, и, наконец, вы хотите десериализовать DataTable.
Пользовательский атрибут
Мы создаем пользовательский атрибут, который будет применяться к свойствам внутри нашего класса. Мы создаем класс, чтобы иметь свойство Name
, которое мы будем использовать позже, чтобы получить правильный столбец из нашей таблицы данных.
[AttributeUsage(AttributeTargets.Property, Inherited = false)]
public class MySqlColName : Attribute
{
private string _name = "";
public string Name { get => _name; set => _name = value; }
public MySqlColName(string name)
{
_name = name;
}
}
Класс для десериализации
Затем, в классе, который мы собираемся заполнить, мы объявим имена столбцов, которые будут ссылаться на свойства в классе, используя атрибут [MySqlColName]
, который мы только что создали.
Однако если имя свойства совпадает со столбцом базы данных, нам не нужно указывать имя столбца в атрибуте, поскольку функция .ToList<>()
примет имя столбца из имени свойства.
public class EventInfo
{
[MySqlColName("ID")]
public int EventID { get; set; }
//Notice there is no attribute on this property?
public string Name { get; set; }
[MySqlColName("State")]
public string State { get; set; }
[MySqlColName("Start_Date")]
public DateTime StartDate { get; set; }
[MySqlColName("End_Date")]
public DateTime EndDate { get; set; }
}
Метод расширения DataTable ToList
Наконец, мы изменим @user1553525 ответ, добавив проверку, чтобы проверить, был ли предоставлен наш пользовательский атрибут. Если это так, мы устанавливаем имя столбца в соответствии с указанным именем, в противном случае мы используем имя свойства (см. код внутри блока try).
public static List<T> ToList<T>(this DataTable table) where T : class, new()
{
try
{
List<T> list = new List<T>();
foreach (var row in table.AsEnumerable())
{
T obj = new T();
foreach (var prop in obj.GetType().GetProperties())
{
try
{
//Set the column name to be the name of the property
string ColumnName = prop.Name;
//Get a list of all of the attributes on the property
object[] attrs = prop.GetCustomAttributes(true);
foreach (object attr in attrs)
{
//Check if there is a custom property name
if (attr is MySqlColName colName)
{
//If the custom column name is specified overwrite property name
if (!colName.Name.IsNullOrWhiteSpace())
ColumnName = colName.Name;
}
}
PropertyInfo propertyInfo = obj.GetType().GetProperty(prop.Name);
//GET THE COLUMN NAME OFF THE ATTRIBUTE OR THE NAME OF THE PROPERTY
propertyInfo.SetValue(obj, Convert.ChangeType(row[ColumnName], propertyInfo.PropertyType), null);
}
catch
{
continue;
}
}
list.Add(obj);
}
return list;
}
catch
{
return null;
}
}//END METHOD
Usage
UsageНаконец, мы можем вызвать метод .ToList<>()
и получить список сериализованных объектов
List<EventInfo> CustomObjectList;
using (DataTable dtCustomer = GetDataTable("SELECT * FROM EventIndex"))
{
CustomObjectList = dtCustomer.ToList<EventInfo>();
}
Примечание: У меня есть несколько пользовательских методов, которые я использовал
public static bool IsNullOrWhiteSpace(this string x)
{
return string.IsNullOrWhiteSpace(x);
}
public static DataTable GetDataTable(string Query)
{
MySqlConnection connection = new MySqlConnection("<Connection_String>");
try
{
DataTable data = new DataTable();
connection.Open();
using (MySqlCommand command = new MySqlCommand(Query, connection))
{
data.Load(command.ExecuteReader());
}
return data;
}
catch (Exception ex)
{
// handle exception here
Console.WriteLine(ex);
throw ex;
}
finally
{
connection.Close();
}
}