65.9K
CodeProject 正在变化。 阅读更多。
Home

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

2014 年 6 月 9 日

CPOL

3分钟阅读

viewsIcon

145416

downloadIcon

2522

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**

限制

  1. 它不能处理,有关系实例
  2. DataTable 列的名称和数据类型应与类属性相同
  3. 对于像 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 列映射到类属性。在 DataTableList<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

限制

  1. 它不映射,有关系实例
  2. DataTable 列的数据类型应与类属性相同。

下一步?

  • 修复性能问题
  • 使用属性来
    • 将一列映射到多个属性
    • 将一个属性映射到多个列
    • 映射复杂的有关系实例的对象

 

该代码对于未经测试的输入可能会抛出意外错误。如果有,请告诉我。

历史

  • 2020 年 6 月 10 日:初始版本,1.0.0
  • 2020 年 5 月 2 日:第二个版本,2.0.0

 

© . All rights reserved.