Я использую стороннюю библиотеку, которая возвращает средство чтения данных. Мне нужен простой и как можно более общий способ преобразовать его в список объектов.
Например, скажем, у меня есть класс «Сотрудник» с 2 свойствами EmployeeId и Name, мне нужен считыватель данных (который содержит список сотрудников), который нужно преобразовать в List ‹Employee>.
Думаю, у меня нет другого выбора, кроме как перебирать строки средства чтения данных и для каждой из них преобразовывать их в объект Employee, который я добавлю в Список. Есть ли лучшее решение? Я использую C # 3.5, и в идеале я хотел бы, чтобы он был как можно более универсальным, чтобы он работал с любыми классами (имена полей в DataReader совпадают с именами свойств различных объектов).
Преобразование строк из средства чтения данных в типизированные результаты
Ответы (10)
Вам действительно нужен список, или 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()
для результатов. Я полагаю, вы также можете использовать дженерики + делегат, чтобы сделать код для этого шаблона более пригодным для повторного использования.
Обновление. Сегодня я снова увидел это, и мне захотелось написать общий код:
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);
IEnumerable
, а затем, возможно, вызова ToList
.
- person Noldorin; 30.07.2009
Dispose()
ed после того, как ToList () вызывается правильно? Действительны ли IDataRecords в моем списке ‹IDataRecord›? Возможно, они мешают правильному освобождению памяти? Они ставят бомбы замедленного действия, ожидая, когда придет сборщик мусора и сотрет их? Может, именно по этому поводу стоит задать отдельный вопрос. Спасибо.
- person user1325179; 26.09.2014
ToList()
, а не из-за того, что без него код дает тот же объект, который только видоизменяется с каждой итерацией. Память для объекта в списке будет по-прежнему доступна после удаления (удаление касается неуправляемых ресурсов, а не управляемой памяти), но в списке будет один и тот же объект для каждой записи. BuildObject()
позаботится об этом.
- person Joel Coehoorn; 26.09.2014
yield
, и оператор using завершится, удалив SqlDataReader. Но я думаю, что ключевым моментом является то, что данные SqlDataReader все еще доступны даже после того, как они были удалены. Я никогда не знал этого, и я просто подтвердил это в другом месте. Спасибо. Итак, вызов reader.Cast<System.Data.IDataRecord>().ToList()
- хорошая идея, или есть лучший способ преобразовать данные SqlDataReader в List ‹IDataRecord›?
- person user1325179; 26.09.2014
GetEnumerator()
метод, который сделал это в значительной степени устаревшим. В большинстве случаев вы можете просто использовать это вместо этого.
- person Joel Coehoorn; 26.09.2014
Вы можете создать такой метод расширения, как:
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();
Хотя я бы не рекомендовал это для производственного кода, но вы можете сделать это автоматически, используя отражение и дженерики:
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();
Мы реализовали следующее решение и считаем, что оно работает очень хорошо. Это довольно просто и требует немного большего количества подключений, чем то, что сделал бы картограф. Однако иногда приятно иметь ручное управление, и, честно говоря, вы подключаетесь один раз, и все готово.
В двух словах: наши модели предметной области реализуют интерфейс, в котором есть метод, который принимает 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 */)
new()
в качестве общего ограничения. Спасибо за то, что вы также выразили это любезно и без язв и отрицательных голосов!
- person Airn5475; 26.09.2016
ПРИМЕЧАНИЕ. Это код .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>());
}
}
Самое простое решение:
var dt=new DataTable();
dt.Load(myDataReader);
list<DataRow> dr=dt.AsEnumerable().ToList();
Затем выберите их, чтобы сопоставить с любым типом.
Для .NET Core 2.0:
Вот метод расширения, который работает с .NET CORE 2.0 для выполнения RAW SQL и отображения результатов в СПИСОК произвольных типов:
ИСПОЛЬЗОВАНИЕ:
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;
}
}
}
}
Как Magic
Лично я НЕНАВИЖУ делать отображение вручную в конструкторах, я также не фанат создания собственного отражения. Итак, вот еще одно решение, любезно предоставленное замечательной (и довольно повсеместной) библиотекой 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);
Я нашел это решение.
var cmd = ctx.Connection.CreateCommand();
T result = DbDataReaderdHelper.Fill<T>(cmd)
public static class DbDataReaderdHelper
{
public static List<T> Fill<T>(DbCommand dbCommand) where T : new()
{
List<T> result = new List<T>();
var reader = dbCommand.ExecuteReader();
if (reader.HasRows)
{
while (reader.Read())
{
Type type = typeof(T);
T obj = (T)Activator.CreateInstance(type);
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
var value = reader[property.Name];
try
{
if (value != null)
{
var convertedValue = TypeDescriptor.GetConverter(property.PropertyType).ConvertFromInvariantString(value.ToString());
property.SetValue(obj, convertedValue);
}
}
catch {}
}
result.Add(obj);
}
}
reader.Close();
return result;
}
}
Моя версия
Использование:
var Q = await Reader.GetTable<DbRoom>("SELECT id, name FROM rooms");
PgRoom - это
public class DbRoom
{
[Column("id")]
public int Id { get; set; }
[Column("name")]
public string Name { get; set; }
}
Reader.GetTable содержит:
using (var R = await Q.ExecuteReaderAsync())
{
List<T> Result = new List<T>();
Dictionary<int, PropertyInfo> Props = new Dictionary<int, PropertyInfo>();
foreach (var p in typeof(T).GetProperties())
{
for (int i = 0; i < R.FieldCount; i++)
{
if (p.GetCustomAttributes<ColumnAttribute>().FirstOrDefault(t => t.Name == R.GetName(i)) != null
&& p.PropertyType == R.GetFieldType(i))
{
Props.Add(i, p);
}
}
}
while (await R.ReadAsync())
{
T row = new T();
foreach (var kvp in Props)
{
kvp.Value.SetValue(row, R[kvp.Key]);
}
Result.Add(row);
}
return Result;
}