C# 中 DataTable 与 List<TSource> 的转换






4.88/5 (33投票s)
C# 中 DataTable 和 List 之间的转换
背景
- 我当时正在做一个基于 Entity Framework 的项目,其中使用了 `iTextSharp` 来生成 PDF 报告,而 `iTextSharp` 需要 `DataTable` 作为输入,但 Entity Framework 输出的是 `List<TSource>`。所以每次都需要手动将 `List<TSource>` 转换为 `DataTable`。
- 在另一个场景中,我使用了 `JavaScriptSerializer` 来为 Web 服务创建 JSON 响应。但 `JavaScriptSerializer` 类无法序列化 `DataTable`。所以我们又需要将 `DataTable` 转换为 `List<TSource>`。
我的想法是创建一个通用的解决方案,通过创建两个扩展方法来管理预期的转换过程。
模型
我们将使用这个 `Student` 类,其中
students:List<Student>
将被转换为studentTbl:DataTable
studentTbl:DataTable
将被转换为newStudents:List<Student>
public class Student
{
public long Id { get; set; }
public string Name { get; set; }
public short Age { get; set; }
public DateTime DateOfCreation { get; set; }
public bool? IsActive { get; set; }
}
版本 1.0.0
这是最初的解决方案。
List<TSource> 转换为 DataTable
这是将 `List<TSource>` 转换为 `DataTable` 的扩展方法
/*Converts List To DataTable*/
public static DataTable ToDataTable<TSource>(this IList<TSource> data)
{
DataTable dataTable = new DataTable(typeof(TSource).Name);
PropertyInfo[] props = typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo prop in props)
{
dataTable.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ??
prop.PropertyType);
}
foreach (TSource item in data)
{
var values = new object[props.Length];
for (int i = 0; i < props.Length; i++)
{
values[i] = props[i].GetValue(item, null);
}
dataTable.Rows.Add(values);
}
return dataTable;
}
使用如下的扩展方法,其中 students:List<Student>
被转换为 studentTbl:DataTable
/*List to DataTable conversion*/
List<Student> students = Data.GetStudents();
DataTable studentTbl = students.ToDataTable();
DataTable 转换为 List<TSource>
这是将 `DataTable` 转换为 `List<TSource>` 的扩展方法
/*Converts DataTable To List*/
public static List<TSource> ToList<TSource>(this DataTable dataTable) where TSource : new()
{
var dataList = new List<TSource>();
const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic;
var objFieldNames = (from PropertyInfo aProp in typeof(TSource).GetProperties(flags)
select new { Name = aProp.Name,
Type = Nullable.GetUnderlyingType(aProp.PropertyType) ??
aProp.PropertyType }).ToList();
var dataTblFieldNames = (from DataColumn aHeader in dataTable.Columns
select new { Name = aHeader.ColumnName,
Type = aHeader.DataType }).ToList();
var commonFields = objFieldNames.Intersect(dataTblFieldNames).ToList();
foreach (DataRow dataRow in dataTable.AsEnumerable().ToList())
{
var aTSource = new TSource();
foreach (var aField in commonFields)
{
PropertyInfo propertyInfos = aTSource.GetType().GetProperty(aField.Name);
var value = (dataRow[aField.Name] == DBNull.Value) ?
null : dataRow[aField.Name]; //if database field is nullable
propertyInfos.SetValue(aTSource, value, null);
}
dataList.Add(aTSource);
}
return dataList;
}
使用如下的扩展方法,其中 studentTbl:DataTable
被转换为 newStudents:List<Student>
/*DataTable to List conversion*/
List<Student> newStudents = studentTbl.ToList<Student>();
解决方案和项目
这是一个 **Visual Studio 2010** 解决方案和 **.NET Framework 3.5**
限制
- 它不能处理,有关系实例
- DataTable 列的名称和数据类型应与类属性相同
- 对于像
Student
这样的简单类,效果很好
下一步?
使用属性
- 忽略映射
- 映射列/属性名称
- 使用排序
2.0.0版本
在我们现有的解决方案中,我们将添加属性支持。让我们来看看这些属性及其用法。
属性
属性如下列所示
/*Base datatable helper attribute*/
[AttributeUsage(AttributeTargets.Field)]
public class DataTableHelperAttribute : Attribute
{
protected DataTableHelperAttribute()
{
}
}
/*DataTable name configuration attribute*/
[AttributeUsage(AttributeTargets.Class)]
public class ToDataTableAttribute : DataTableHelperAttribute
{
public String DataTableName { get; set; }
public ToDataTableAttribute(string dataTableName) : this()
{
DataTableName = dataTableName;
}
private ToDataTableAttribute() : base()
{
}
}
/*List to DataTable map configuration attribute*/
[AttributeUsage(AttributeTargets.Property)]
public class ToColumnAttribute : DataTableHelperAttribute
{
public int? ColumnOrder { get; set; }
public bool? IgnoreMapping { get; set; }
public String ColumnName { get; set; }
public ToColumnAttribute(int columnOrder) : this(true)
{
ColumnOrder = columnOrder;
}
public ToColumnAttribute(string columnName) : this(true)
{
ColumnName = columnName;
}
public ToColumnAttribute(string columnName, int columnOrder) : this(columnName)
{
ColumnOrder = columnOrder;
}
public ToColumnAttribute(bool shouldMapp) : this()
{
IgnoreMapping = !shouldMapp;
}
private ToColumnAttribute() : base()
{
}
}
/*DataTable to list map configuration attribute*/
[AttributeUsage(AttributeTargets.Property)]
public class FromColumnAttribute : DataTableHelperAttribute
{
public bool? IgnoreMapping { get; set; }
public string ColumnName { get; set; }
public FromColumnAttribute(string columnName) : this(true)
{
ColumnName = columnName;
}
public FromColumnAttribute(bool shouldMapp) : this()
{
IgnoreMapping = !shouldMapp;
}
private FromColumnAttribute() : base()
{
}
}
DataTableHelperAttribute
是基类属性,其他属性
ToDataTableAttribute
将类映射到DataTable
。在List<TSource>
到DataTable
的转换过程中使用- 设置 DataTable 的名称,默认名称为类名
ToColumnAttribute
将类属性映射到 DataTable 列。在List<TSource>
到DataTable
的转换过程中使用- 设置列名,默认名称为属性名
- 设置列的顺序,默认顺序为属性的顺序
- 忽略任何属性,不将其映射到列
FromColumnAttribute
将 DataTable 列映射到类属性。在DataTable
到List<TSource>
的转换过程中使用- 设置列名,默认名称为属性名
- 忽略任何属性,不将其从列映射
映射配置
这个帮助类读取属性值并创建映射配置。这些配置稍后将由扩展方法使用。
public class DataTableAttributeHelper
{
internal T First<T>(Type type) where T : DataTableHelperAttribute
{
T attribute = (T)type.GetCustomAttributes(typeof(T), false).FirstOrDefault();
return attribute;
}
internal T First<T>(PropertyInfo type) where T : DataTableHelperAttribute
{
T attribute = (T)type.GetCustomAttributes(typeof(T), false).FirstOrDefault();
return attribute;
}
internal string ToDataTableName(Type type)
{
var attribute = First<ToDataTableAttribute>(type);
string name = attribute == null
? type.Name
: String.IsNullOrEmpty(attribute.DataTableName)
? type.Name
: attribute.DataTableName;
return name;
}
internal string ToColumnName(PropertyInfo prop)
{
var attribute = First<ToColumnAttribute>(prop);
string name = attribute == null
? prop.Name
: String.IsNullOrEmpty(attribute.ColumnName)
? prop.Name
: attribute.ColumnName;
return name;
}
internal Type PropertyType(PropertyInfo prop)
{
var type = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;
return type;
}
internal int? ToColumnOrder(PropertyInfo prop)
{
var attribute = First<ToColumnAttribute>(prop);
int? order = attribute == null
? null
: attribute.ColumnOrder;
return order;
}
internal bool IsToColumnIgnored(PropertyInfo prop)
{
var attribute = First<ToColumnAttribute>(prop);
bool value = attribute == null
? false
: attribute.IgnoreMapping == null
? false
: (bool)attribute.IgnoreMapping;
return value;
}
internal bool IsFromColumnIgnored(PropertyInfo prop)
{
var attribute = First<FromColumnAttribute>(prop);
bool value = attribute == null
? false
: attribute.IgnoreMapping == null
? false
: (bool)attribute.IgnoreMapping;
return value;
}
internal string FromColumnName(PropertyInfo prop)
{
var attribute = First<FromColumnAttribute>(prop);
string name = attribute == null
? prop.Name
: String.IsNullOrEmpty(attribute.ColumnName)
? prop.Name
: attribute.ColumnName;
return name;
}
}
映射配置类
public interface IMappToDataTable
{
string FromProperty { get; set; }
string ToColumn { get; set; }
bool IgnoreMapp { get; set; }
Type ColumnType { get; set; }
int? ColumOrder { get; set; }
int PropertyPosition { get; set; }
}
public class MappToDataTable : IMappToDataTable
{
public string FromProperty { get; set; }
public string ToColumn { get; set; }
public bool IgnoreMapp { get; set; }
public Type ColumnType { get; set; }
public int? ColumOrder { get; set; }
public int PropertyPosition { get; set; }
}
public interface IMappFromDataTable
{
string FromColumn { get; set; }
string ToProperty { get; set; }
Type PropertyType { get; set; }
bool IgnoreMapp { get; set; }
}
public class MappFromDataTable : IMappFromDataTable
{
public string FromColumn { get; set; }
public string ToProperty { get; set; }
public Type PropertyType { get; set; }
public bool IgnoreMapp { get; set; }
}
MappToDataTable:
类属性到 DataTable 列的映射配置MappFromDataTable:
DataTable 列到类属性的映射配置
List<TSource> 转换为 DataTable
将属性应用于类及其属性
[ToDataTable("STUDENT_TABLE")]
public class Student
{
public long Id { get; set; }
public string Name { get; set; }
[ToColumn("Remarks")]
public string Description { get; set; }
[ToColumn(5)]
public bool? IsActive { get; set; }
[ToColumn("CreateDateTime", 4)]
public DateTime? DateOfCreation { get; set; }
[ToColumn(false)]
public Department Department { get; set; }
}
[ToDataTable("STUDENT_TABLE")]
设置 DataTable 名称[ToColumn("Remarks")]
设置 DataTable 列名[ToColumn(5)]
设置 DataTable 列的顺序[ToColumn("CreateDateTime", 4)]
设置 DataTable 列名和顺序[ToColumn(false)]
忽略属性,不将其映射到 DataTable 列
修改现有的扩展方法以包含属性映射
public static DataTable ToDataTable<TSource>(this IList<TSource> data)
{
List<MappToDataTable> mapps = new List<MappToDataTable>();
DataTableAttributeHelper helper = new DataTableAttributeHelper();
PropertyInfo[] props = typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance);
int propertyPosition = 0;
foreach (PropertyInfo prop in props)
{
bool ignore = helper.IsToColumnIgnored(prop);
if (ignore)
{
continue;
}
var mapp = new MappToDataTable
{
FromProperty = prop.Name,
ToColumn = helper.ToColumnName(prop),
ColumOrder = helper.ToColumnOrder(prop),
PropertyPosition = ++propertyPosition,
IgnoreMapp = ignore,
ColumnType = helper.PropertyType(prop),
};
mapps.Add(mapp);
}
mapps = mapps.OrderBy(x => x.ColumOrder ?? int.MaxValue).ThenBy(x => x.PropertyPosition).ToList();
DataTable dataTable = new DataTable(helper.ToDataTableName(typeof(TSource)));
foreach (var mapp in mapps)
{
dataTable.Columns.Add(mapp.ToColumn, mapp.ColumnType);
}
foreach (TSource item in data)
{
var values = new object[mapps.Count];
for (int i = 0; i < mapps.Count; i++)
{
values[i] = props.First(x => x.Name.Equals(mapps[i].FromProperty)).GetValue(item, null);
}
dataTable.Rows.Add(values);
}
return dataTable;
}
DataTable 转换为 List<TSource>
将属性应用于类及其属性
public class Teacher
{
public long Id { get; set; }
public string Name { get; set; }
[FromColumn("Remarks")]
public string Description { get; set; }
[FromColumn("CreateDateTime")]
public DateTime? DateOfCreation { get; set; }
[FromColumn(false)]
public Department Department { get; set; }
}
[FromColumn("Remarks")]
将 DataTable 列“Remarks”映射到类属性[FromColumn(false)]
忽略属性,不将其从 DataTable 列映射
修改现有的扩展方法以包含属性映射
/*Converts DataTable To List*/
public static List<TSource> ToList<TSource>(this DataTable dataTable) where TSource : new()
{
/*check, one column mapped to only one prop*/
List<MappFromDataTable> mapps = new List<MappFromDataTable>();
DataTableAttributeHelper helper = new DataTableAttributeHelper();
PropertyInfo[] props = typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo prop in props)
{
bool ignore = helper.IsFromColumnIgnored(prop);
if (ignore)
{
continue;
}
var mapp = new MappFromDataTable
{
ToProperty = prop.Name,
FromColumn = helper.FromColumnName(prop),
IgnoreMapp = ignore,
PropertyType = helper.PropertyType(prop),
};
mapps.Add(mapp);
}
var objFieldNames = (from x in mapps select new { ColumnName = x.FromColumn, Type = x.PropertyType }).ToList();
var dataTblFieldNames = (from DataColumn x in dataTable.Columns select new { ColumnName = x.ColumnName, Type = x.DataType }).ToList();
var commonFields = objFieldNames.Intersect(dataTblFieldNames).ToList();
var dataList = new List<TSource>();
foreach (DataRow dataRow in dataTable.AsEnumerable().ToList())
{
var aTSource = new TSource();
foreach (var aField in commonFields)
{
string propName = mapps.First(x => x.FromColumn.Equals(aField.ColumnName)).ToProperty;
PropertyInfo propertyInfos = aTSource.GetType().GetProperty(propName);
var columnValue = dataRow[aField.ColumnName];
var value = (columnValue == DBNull.Value) ? null : columnValue; //if database field is nullable
propertyInfos.SetValue(aTSource, value, null);
}
dataList.Add(aTSource);
}
return dataList;
}
使用扩展方法
用法将和以前一样。
/*List to DataTable conversion*/
List<Student> students = Data.GetStudents();
DataTable studentTbl = students.ToDataTable();
/*DataTable to List conversion*/
List<Student> newStudents = studentTbl.ToList<Student>();
解决方案和项目
这是一个 **Visual Studio 2017** 解决方案和 **.NET Framework 4**
- DataTableHelper: 辅助项目,包含扩展方法、属性和其他类
- Test.Unit: 单元测试项目
- DataTableAndList: 一个用于测试扩展方法的控制台项目
要恢复 NuGet 包,请使用命令
Update-Package -Reinstall
限制
- 它不映射,有关系实例
- DataTable 列的数据类型应与类属性相同。
下一步?
- 修复性能问题
- 使用属性来
- 将一列映射到多个属性
- 将一个属性映射到多个列
- 映射复杂的有关系实例的对象
该代码对于未经测试的输入可能会抛出意外错误。如果有,请告诉我。
历史
- 2020 年 6 月 10 日:初始版本,1.0.0
- 2020 年 5 月 2 日:第二个版本,2.0.0