Конвертировать строки из считывателя данных в типизированные результаты - PullRequest
27 голосов
/ 30 июля 2009

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

Ответы [ 7 ]

61 голосов
/ 30 июля 2009

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

Я знаю, что вы хотите, чтобы он был универсальным, но гораздо более распространенным является использование статического метода Factory для целевого типа объекта, который принимает данные (или 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);
24 голосов
/ 30 июля 2009

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

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) и устанавливает свойства объекта с помощью отражения, читая соответствующий столбец. Тем не менее, я лично предпочитаю определить фабричный метод (например, тот, который упомянут в ответе Джоэла) и передать его делегат в эту функцию:

 var list = dataReader.GetEnumerator(Employee.Create).ToList();
3 голосов
/ 30 июля 2009

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

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();
1 голос
/ 19 июня 2017

ПРИМЕЧАНИЕ. Это код .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>());
    }
}
1 голос
/ 13 сентября 2016

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

В двух словах: Наши доменные модели реализуют интерфейс, который имеет метод, который принимает 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 */)
0 голосов
/ 21 сентября 2017

Для .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;

                }
            }
        }
    }
0 голосов
/ 10 сентября 2017

Самое простое решение:

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...