Преобразование DataTable в общий список в С#
Возможный дубликат:
DataTable в общий список (утечка памяти?)
Возможный дубликат:
Преобразование DataTable в список < gt;
Возможный дубликат:
Самый быстрый способ преобразования данных в общий список
Отказ от ответственности: я знаю, что его спросили во многих местах в SO.
Мой запрос немного отличается.
Язык кодирования: С# 3.5
У меня есть DataTable с именем cardsTable, который извлекает данные из БД, и у меня есть карты классов, которые имеют только некоторые свойства (без конструктора)
public class Cards
{
public Int64 CardID { get; set; }
public string CardName { get; set; }
public Int64 ProjectID { get; set; }
public Double CardWidth { get; set; }
public Double CardHeight { get; set; }
public string Orientation { get; set; }
public string BackgroundImage { get; set; }
public string Background { get; set; }
}
Я хочу вставить cardsTable данные в объект типа List.
Мои данные будут иметь в нем нулевые поля, поэтому при преобразовании данных метод не должен ошибаться. Лучше ли метод ниже?
DataTable dt = GetDataFromDB();
List<Cards> target = dt.AsEnumerable().ToList().ConvertAll(x => new Cards { CardID = (Int64)x.ItemArray[0] });
Ответы
Ответ 1
Вы могли бы значительно сократить его. Вы можете придумать метод расширения Select()
в качестве преобразователя типов. После этого преобразование можно записать следующим образом:
List<Cards> target = dt.AsEnumerable()
.Select(row => new Cards
{
// assuming column 0 type is Nullable<long>
CardID = row.Field<long?>(0).GetValueOrDefault(),
CardName = String.IsNullOrEmpty(row.Field<string>(1))
? "not found"
: row.Field<string>(1),
}).ToList();
Ответ 2
Я думаю, что все решения могут быть улучшены и сделать метод более общим, если вы используете некоторые соглашения и размышления. Скажем, вы называете свои столбцы в имени данных такими же именами, как и свойства в вашем объекте, тогда вы можете написать что-то, что будет смотреть на все ваши свойства вашего объекта, а затем найти этот столбец в datatable для сопоставления значения.
Я сделал противоположное, то есть... от IList до datatable, и код, который я написал, можно увидеть по адресу: http://blog.tomasjansson.com/convert-datatable-to-generic-list-extension/
Не должно быть так сложно, и вам должно быть сложно перегрузить функции, чтобы вы могли предоставить информацию о том, какие свойства вы хотите включить или исключить.
EDIT:
Таким образом, код для его работы:
public static class DataTableExtensions
{
private static Dictionary<Type,IList<PropertyInfo>> typeDictionary = new Dictionary<Type, IList<PropertyInfo>>();
public static IList<PropertyInfo> GetPropertiesForType<T>()
{
var type = typeof(T);
if(!typeDictionary.ContainsKey(typeof(T)))
{
typeDictionary.Add(type, type.GetProperties().ToList());
}
return typeDictionary[type];
}
public static IList<T> ToList<T>(this DataTable table) where T : new()
{
IList<PropertyInfo> properties = GetPropertiesForType<T>();
IList<T> result = new List<T>();
foreach (var row in table.Rows)
{
var item = CreateItemFromRow<T>((DataRow)row, properties);
result.Add(item);
}
return result;
}
private static T CreateItemFromRow<T>(DataRow row, IList<PropertyInfo> properties) where T : new()
{
T item = new T();
foreach (var property in properties)
{
property.SetValue(item, row[property.Name], null);
}
return item;
}
}
Если у вас есть DataTable, вы можете просто написать yourTable.ToList<YourType>()
, и он создаст список для вас. Если у вас более сложный тип с вложенными объектами, вам необходимо обновить код. Одно из предложений - просто перегрузить метод ToList
, чтобы принять params string[] excludeProperties
, который содержит все ваши свойства, которые не должны отображаться. Конечно, вы можете добавить некоторую нулевую проверку в цикле foreach
метода CreateItemForRow
.
UPDATE: Добавлен статический словарь для хранения результата операции отражения, чтобы сделать его немного быстрее. Я не скомпилировал код, но он должен работать:).
Ответ 3
.ToList() не в том месте, и если некоторые поля могут быть пустыми, вам придется иметь дело с ними, поскольку они не конвертируются в Int64, если они имеют значение null
DataTable dt = GetDataFromDB();
List<Cards> target = dt.AsEnumerable().Select(
x => new Cards { CardID = (Int64)(x.ItemArray[0] ?? 0) }).ToList();
Ответ 4
Просто небольшое упрощение. Я не использую ItemArray:
List<Person> list = tbl.AsEnumerable().Select(x => new Person
{
Id = (Int32) (x["Id"]),
Name = (string) (x["Name"] ?? ""),
LastName = (string) (x["LastName"] ?? "")
}).ToList();
Ответ 5
хорошо его однострочное решение
это зависит от того, знаете ли вы, что данные в базе данных являются действительными и не будут содержать ничего, что сломает выше
например, поле с нулевым значением, когда вы его не ожидаете - возможно, из-за левого соединения int eh sql, которое генерирует данные.
Итак, если вы подтвердили данные до этого, да, я был goign, чтобы предложить какой-то linq, но вы ушли вниз.
Если вам нужна какая-то проверка, вы, вероятно, должны просто прокрутить datarows, сгенерировать свой объект, как указано выше, и добавить его в коллекцию... это также позволит вам обрабатывать ошибки в одной строке и все еще обрабатывать остальные.
То, как я все это вижу
(черт возьми, я подошел, чтобы понизить, чтобы мой рев был 1024)
Ответ 6
Вы можете сопоставить таблицу данных с классом модели, используя класс Generic, как показано ниже.
Общий класс
public static class DataTableMappingtoModel
{
/// <summary>
/// Maps Data Table values to coresponded model propertise
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="dt"></param>
/// <returns></returns>
public static List<T> MappingToEntity<T>(this DataTable dt)
{
try
{
var lst = new List<T>();
var tClass = typeof (T);
PropertyInfo[] proInModel = tClass.GetProperties();
List<DataColumn> proInDataColumns = dt.Columns.Cast<DataColumn>().ToList();
T cn;
foreach (DataRow item in dt.Rows)
{
cn = (T) Activator.CreateInstance(tClass);
foreach (var pc in proInModel)
{
var d = proInDataColumns.Find(c => string.Equals(c.ColumnName.ToLower().Trim(), pc.Name.ToLower().Trim(), StringComparison.CurrentCultureIgnoreCase));
if (d != null)
pc.SetValue(cn, item[pc.Name], null);
}
lst.Add(cn);
}
return lst;
}
catch (Exception e)
{
throw e;
}
}
}
Класс модели
public class Item
{
public string ItemCode { get; set; }
public string Cost { get; set; }
public override string ToString()
{
return "ItemCode : " + ItemCode + ", Cost : " + Cost;
}
}
Создать DataTable
public DataTable getTable()
{
DataTable dt = new DataTable();
dt.Columns.Add(new DataColumn("ItemCode", typeof(string)));
dt.Columns.Add(new DataColumn("Cost", typeof(string)));
DataRow dr;
for (int i = 0; i < 10; i++)
{
dr = dt.NewRow();
dr[0] = "ItemCode" + (i + 1);
dr[1] = "Cost" + (i + 1);
dt.Rows.Add(dr);
}
return dt;
}
Теперь мы можем преобразовать этот DataTable в List, как показано ниже:
DataTable dt = getTable();
List<Item> lst = dt.ToCollection<Item>();
foreach (Item cn in lst)
{
Response.Write(cn.ToString() + "<BR/>");
}
Надежда поможет вам
Ответ 7
Я построил на вершине логики Томаса Янсона, чтобы включить атрибут "Игнорировать". Это позволяет мне добавить другой атрибут к загружаемому классу, не нарушая загрузку DataTable-To-Class.
В качестве альтернативы я также рассмотрел возможность добавления отдельного параметра, который содержит фактическое имя столбца для чтения из DataTable. В этом случае вместо использования "row [property.Name]" вы должны использовать строку [attribute.Name] "или что-то подобное для этого конкретного свойства.
public static class DataTableExtensions
{
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
public sealed class IgnoreAttribute : Attribute { public IgnoreAttribute() { } }
private static Dictionary<Type, IList<PropertyInfo>> typeDictionary = new Dictionary<Type, IList<PropertyInfo>>();
public static IList<PropertyInfo> GetPropertiesForType<T>()
{
var type = typeof(T);
if (!typeDictionary.ContainsKey(typeof(T)))
typeDictionary.Add(type, type.GetProperties().ToList());
return typeDictionary[type];
}
public static IList<T> ToList<T>(this DataTable table) where T : new()
{
IList<PropertyInfo> properties = GetPropertiesForType<T>();
IList<T> result = new List<T>();
foreach (var row in table.Rows)
result.Add(CreateItemFromRow<T>((DataRow)row, properties));
return result;
}
private static T CreateItemFromRow<T>(DataRow row, IList<PropertyInfo> properties) where T : new()
{
T item = new T();
foreach (var property in properties)
{
// Only load those attributes NOT tagged with the Ignore Attribute
var atr = property.GetCustomAttribute(typeof(IgnoreAttribute));
if (atr == null)
property.SetValue(item, row[property.Name], null);
}
return item;
}
}
Ответ 8
Поздно, но это может быть полезно. Можно вызвать с помощью:
table.Map();
или вызов с помощью Func для фильтрации значений.
Вы даже можете изменить имя сопоставления между свойством type и заголовком DataColumn, установив атрибуты в свойстве.
[AttributeUsage(AttributeTargets.Property)]
public class SimppleMapperAttribute: Attribute
{
public string HeaderName { get; set; }
}
public static class SimpleMapper
{
#region properties
public static bool UseDeferredExecution { get; set; } = true;
#endregion
#region public_interface
public static IEnumerable<T> MapWhere<T>(this DataTable table, Func<T, bool> sortExpression) where T:new()
{
var result = table.Select().Select(row => ConvertRow<T>(row, table.Columns, typeof(T).GetProperties())).Where((t)=>sortExpression(t));
return UseDeferredExecution ? result : result.ToArray();
}
public static IEnumerable<T> Map<T>(this DataTable table) where T : new()
{
var result = table.Select().Select(row => ConvertRow<T>(row, table.Columns, typeof(T).GetProperties()));
return UseDeferredExecution ? result : result.ToArray();
}
#endregion
#region implementation_details
private static T ConvertRow<T>(DataRow row, DataColumnCollection columns, System.Reflection.PropertyInfo[] p_info) where T : new()
{
var instance = new T();
foreach (var info in p_info)
{
if (columns.Contains(GetMappingName(info))) SetProperty(row, instance, info);
}
return instance;
}
private static void SetProperty<T>(DataRow row, T instance, System.Reflection.PropertyInfo info) where T : new()
{
string mp_name = GetMappingName(info);
object value = row[mp_name];
info.SetValue(instance, value);
}
private static string GetMappingName(System.Reflection.PropertyInfo info)
{
SimppleMapperAttribute attribute = info.GetCustomAttributes(typeof(SimppleMapperAttribute),true).Select((o) => o as SimppleMapperAttribute).FirstOrDefault();
return attribute == null ? info.Name : attribute.HeaderName;
}
#endregion
}