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