Подтвердить что ты не робот

Преобразование строк из устройства чтения данных в типизированные результаты

Я использую стороннюю библиотеку, которая возвращает устройство чтения данных. Я хотел бы простой способ и как можно более общий, чтобы преобразовать его в Список объектов.
Например, скажем, у меня есть класс "Сотрудник" с 2 свойствами EmployeeId и Name, я бы хотел, чтобы считыватель данных (который содержит список сотрудников) был преобразован в List < Служащий > .
Думаю, у меня нет выбора, кроме как итерации строк строк данных, и для каждого из них они преобразуют их в объект Employee, который я добавлю в список. Любое лучшее решение? Я использую С# 3.5, и в идеале я бы хотел, чтобы он был как можно более общим, чтобы он работал с любыми классами (имена полей в DataReader соответствуют именам свойств различных объектов).

4b9b3361

Ответ 1

Вам действительно нужен список, или IEnumerable будет достаточно хорошим?

Я знаю, что вы хотите, чтобы он был общим, но гораздо более распространенный шаблон - иметь статический метод Factory для целевого типа объекта, который принимает datarow (или IDataRecord). Это будет выглядеть примерно так:

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }

    public static Employee Create(IDataRecord record)
    {
        return new Employee
        {
           Id = record["id"],
           Name = record["name"]
        };
    }
}

.

public IEnumerable<Employee> GetEmployees()
{
    using (var reader = YourLibraryFunction())
    {
       while (reader.Read())
       {
           yield return Employee.Create(reader);
       }
    }
}

Тогда, если вам действительно нужен список, а не IEnumerable, вы можете вызвать .ToList() по результатам. Я полагаю, вы также можете использовать generics + делегат, чтобы сделать код для этого шаблона более пригодным для повторного использования.

Обновление: Я видел это снова сегодня и хотел написать общий код:

public IEnumerable<T> GetData<T>(IDataReader reader, Func<IDataRecord, T> BuildObject)
{
    try
    {
        while (reader.Read())
        {
            yield return BuildObject(reader);
        }
    }
    finally
    {
         reader.Dispose();
    }
}

//call it like this:
var result = GetData(YourLibraryFunction(), Employee.Create);

Ответ 2

Вы можете создать метод расширения, например:

public static List<T> ReadList<T>(this IDataReader reader, 
                                  Func<IDataRecord, T> generator) {
     var list = new List<T>();
     while (reader.Read())
         list.Add(generator(reader));
     return list;
}

и используйте его как:

var employeeList = reader.ReadList(x => new Employee {
                                               Name = x.GetString(0),
                                               Age = x.GetInt32(1)
                                        });

Предложение Джоэля - хорошее. Вы можете выбрать IEnumerable<T>. Легко преобразовать приведенный выше код:

public static IEnumerable<T> GetEnumerator<T>(this IDataReader reader, 
                                              Func<IDataRecord, T> generator) {
     while (reader.Read())
         yield return generator(reader);
}

Если вы хотите автоматически сопоставить столбцы с свойствами, идея кода будет такой же. Вы можете просто заменить функцию generator в приведенном выше коде функцией, которая запрашивает typeof(T) и устанавливает свойства объекта с помощью отражения, читая согласованный столбец. Тем не менее, я лично предпочитаю определять метод factory (например, тот, который упоминается в ответе Джоэля) и передать его делегату в эту функцию:

 var list = dataReader.GetEnumerator(Employee.Create).ToList();

Ответ 3

Хотя я бы не рекомендовал это для производственного кода, но вы можете сделать это автоматически, используя отражение и дженерики:

public static class DataRecordHelper
{
    public static void CreateRecord<T>(IDataRecord record, T myClass)
    {
        PropertyInfo[] propertyInfos = typeof(T).GetProperties();

        for (int i = 0; i < record.FieldCount; i++)
        {
            foreach (PropertyInfo propertyInfo in propertyInfos)
            {
                if (propertyInfo.Name == record.GetName(i))
                {
                    propertyInfo.SetValue(myClass, Convert.ChangeType(record.GetValue(i), record.GetFieldType(i)), null);
                    break;
                }
            }
        }
    }
}

public class Employee
{
    public int Id { get; set; }
    public string LastName { get; set; }
    public DateTime? BirthDate { get; set; }

    public static IDataReader GetEmployeesReader()
    {
        SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["NorthwindConnectionString"].ConnectionString);

        conn.Open();
        using (SqlCommand cmd = new SqlCommand("SELECT EmployeeID As Id, LastName, BirthDate FROM Employees"))
        {
            cmd.Connection = conn;
            return cmd.ExecuteReader(CommandBehavior.CloseConnection);
        }
    }

    public static IEnumerable GetEmployees()
    {
        IDataReader rdr = GetEmployeesReader();
        while (rdr.Read())
        {
            Employee emp = new Employee();
            DataRecordHelper.CreateRecord<Employee>(rdr, emp);

            yield return emp;
        }
    }
}

Затем вы можете использовать CreateRecord<T>() для создания экземпляра любого класса из полей в считывателе данных.

<asp:GridView ID="GvEmps" runat="server" AutoGenerateColumns="true"></asp:GridView>

GvEmps.DataSource = Employee.GetEmployees();
GvEmps.DataBind();

Ответ 4

Мы реализовали следующее решение и считаем, что он работает очень хорошо. Это довольно просто и требует немного более проводной связи, чем то, что сделает картограф. Однако иногда приятно иметь ручное управление и, честно говоря, вы подключаетесь один раз, и все готово.

В двух словах: Наши модели домена реализуют интерфейс, который имеет метод, который принимает IDataReader и заполняет свойства модели. Затем мы используем Generics и Reflection для создания экземпляра модели и вызова метода Parse на нем.

Мы рассмотрели использование конструктора и передали ему IDataReader, но основные проверки производительности, которые мы делали, казалось, предполагали, что интерфейс был последовательно быстрее (хотя бы немного). Кроме того, маршрут интерфейса обеспечивает мгновенную обратную связь с ошибками компиляции.

Одна из вещей, которые мне нравятся, заключается в том, что вы можете использовать private set для свойств типа Age в приведенном ниже примере и установить их прямо из базы данных.

public interface IDataReaderParser
{
    void Parse(IDataReader reader);
}

public class Foo : IDataReaderParser
{
    public string Name { get; set; }
    public int Age { get; private set; }

    public void Parse(IDataReader reader)
    {
        Name = reader["Name"] as string;
        Age = Convert.ToInt32(reader["Age"]);
    }
}

public class DataLoader
{
    public static IEnumerable<TEntity> GetRecords<TEntity>(string connectionStringName, string storedProcedureName, IEnumerable<SqlParameter> parameters = null)
                where TEntity : IDataReaderParser, new()
    {
        using (var sqlCommand = new SqlCommand(storedProcedureName, Connections.GetSqlConnection(connectionStringName)))
        {
            using (sqlCommand.Connection)
            {
                sqlCommand.CommandType = CommandType.StoredProcedure;
                AssignParameters(parameters, sqlCommand);
                sqlCommand.Connection.Open();

                using (var sqlDataReader = sqlCommand.ExecuteReader())
                {
                    while (sqlDataReader.Read())
                    {
                        //Create an instance and parse the reader to set the properties
                        var entity = new TEntity();
                        entity.Parse(sqlDataReader);
                        yield return entity;
                    }
                }
            }
        }
    }
}

Чтобы вызвать его, вы просто предоставляете параметр типа

IEnumerable<Foo> foos = DataLoader.GetRecords<Foo>(/* params */)

Ответ 5

ПРИМЕЧАНИЕ. Это код .NET Core

Неверный вариант исполнения, если вы не возражаете против внешней зависимости (удивительный пакет Fast Member nuget):

public static T ConvertToObject<T>(this SqlDataReader rd) where T : class, new()
{

    Type type = typeof(T);
    var accessor = TypeAccessor.Create(type);
    var members = accessor.GetMembers();
    var t = new T();

    for (int i = 0; i < rd.FieldCount; i++)
    {
        if (!rd.IsDBNull(i))
        {
            string fieldName = rd.GetName(i);

            if (members.Any(m => string.Equals(m.Name, fieldName, StringComparison.OrdinalIgnoreCase)))
            {
                accessor[t, fieldName] = rd.GetValue(i);
            }
        }
    }

    return t;
}

Для использования:

public IEnumerable<T> GetResults<T>(SqlDataReader dr) where T : class, new()
{
    while (dr.Read())
    {
        yield return dr.ConvertToObject<T>());
    }
}

Ответ 6

Простейшее решение:

var dt=new DataTable();
dt.Load(myDataReader);
list<DataRow> dr=dt.AsEnumerable().ToList();

Затем выберите их, чтобы сопоставить их с любым типом.

Ответ 7

Для .NET Core 2.0:

Вот метод расширения, который работает с .NET CORE 2.0 для выполнения RAW SQL и отображения результатов в LIST произвольных типов:

ПРИМЕНЕНИЕ:

 var theViewModel = new List();
 string theQuery = @"SELECT * FROM dbo.Something";
 theViewModel = DataSQLHelper.ExecSQL(theQuery,_context);

 using Microsoft.EntityFrameworkCore;
 using System.Data;
 using System.Data.SqlClient;
 using System.Reflection;

public static List ExecSQL(string query, myDBcontext context)
 {
 using (context)
 {
 using (var command = context.Database.GetDbConnection().CreateCommand())
 {
 command.CommandText = query;
 command.CommandType = CommandType.Text;
 context.Database.OpenConnection();
                using (var result = command.ExecuteReader())
                {
                    List<T> list = new List<T>();
                    T obj = default(T);
                    while (result.Read())
                    {
                        obj = Activator.CreateInstance<T>();
                        foreach (PropertyInfo prop in obj.GetType().GetProperties())
                        {
                            if (!object.Equals(result[prop.Name], DBNull.Value))
                            {
                                prop.SetValue(obj, result[prop.Name], null);
                            }
                        }
                        list.Add(obj);
                    }
                    return list;

                }
            }
        }
    }

Ответ 8

Как магия

Лично я ненавижу делать ручное отображение в конструкторах, я также не фанат собственных размышлений. Так что здесь другое решение любезно предоставлено замечательной (и довольно вездесущей) библиотекой Newtonsoft JSON.

Он будет работать только в том случае, если имена ваших свойств точно совпадают с именами столбцов устройства чтения данных, но у нас это хорошо сработало.

... предполагается, что у вас есть имя читателя данных "yourDataReader"...

        var dt = new DataTable();
        dt.Load(yourDataReader);
        // creates a json array of objects
        string json = Newtonsoft.Json.JsonConvert.SerializeObject(dt);
        // this is what you're looking for right??
        List<YourEntityType> list = 
Newtonsoft.Json.JsonConvert
.DeserializeObject<List<YourEntityType>>(json);