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