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

泛型集合到 DataTable 的映射器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (24投票s)

2015年3月25日

CPOL

2分钟阅读

viewsIcon

37077

downloadIcon

1840

一个高性能扩展,用于从泛型集合创建 DataTable。

引言

您知道的,有时您有一个非常大的泛型列表,但是您需要的是 DataTable,因为旧的遗留应用程序需要它,或者您想将其批量复制到数据库。

您可能会认为没问题。 Linq 命名空间中有一个 AsDataTable 函数。
是的,但是它只适用于 Linq to Dataset。
那么,除了每次需要时都硬编码之外,还有什么其他解决方案吗?
嗯,您可以使用反射,但问题是性能很差。

所以,因为我找不到有人创建了这个功能,所以我不得不自己做。

使用代码

只有一个公共方法,一个名为 AsDataTable 的扩展方法,它接受一个 IEnumerable 作为参数,并且使用起来很简单,例如 MyGenericList.AsDataTable()

/// <summary>
/// Creates a DataTable from an IEnumerable
/// </summary>
/// <typeparam name="TSource">The Generic type of the Collection</typeparam>
/// <param name="Collection"></param>
/// <returns>DataTable</returns>
public static DataTable AsDataTable<TSource>(this IEnumerable<TSource> Collection)
{
    DataTable dt = DataTableCreator<TSource>.GetDataTable();
    Func<TSource, object[]> Map = DataRowMapperCache<TSource>.GetDataRowMapper(dt);

    foreach (TSource item in Collection)
    {
        dt.Rows.Add(Map(item));
    }
    return dt;
}
''' <summary>
''' Creates a DataTable from an IEnumerable
''' </summary>
''' <typeparam name="TSource">The Generic type of the Collection</typeparam>
''' <param name="Collection"></param>
''' <returns>DataTable</returns>
<Extension> _
Public Function AsDataTable(Of TSource)(Collection As IEnumerable(Of TSource)) As DataTable
    Dim dt As DataTable = DataTableCreator(Of TSource).GetDataTable
    Dim Map As Func(Of TSource, Object()) = DataRowMapperCache(Of TSource).GetDataRowMapper(dt)

    For Each item As TSource In Collection
        dt.Rows.Add(Map(item))
    Next
    Return dt
End Function

这部分非常简单,它从缓存中获取一个新的 DataTable,并获取一个 Delegate,它充当实例和 ObjectArray 之间的映射器。
然后,该委托用于集合中的每个项目,以创建一个添加到 DataTable 的 RowCollection 的 ObjectArray。
添加数组而不是 DataRow 的原因是 DataRow 没有公共构造函数。 这样做更简单。

创建 DataTable

DataTable 的构造是通过反射完成的,因为它被缓存,所以每个 Item Class 只做一次,并且不会多次影响性能

/// <summary>
/// Creates a DataTable with the same fields as the Generic Type argument
/// </summary>
/// <typeparam name="TSource">The Generic type</typeparam>
/// <returns>DataTable</returns>
static internal DataTable CreateDataTable<TSource>()
{
    DataTable dt = new DataTable();
    foreach (FieldInfo SourceMember in typeof(TSource).GetFields(BindingFlags.Instance | BindingFlags.Public))
    {
        dt.AddTableColumn(SourceMember, SourceMember.FieldType);
    }

    foreach (PropertyInfo SourceMember in typeof(TSource).GetProperties(BindingFlags.Instance | BindingFlags.Public))
    {
        if (SourceMember.CanRead)
        {
            dt.AddTableColumn(SourceMember, SourceMember.PropertyType);
        }
    }
    return dt;
}
''' <summary>
''' Creates a DataTable with the same fields as the Generic Type argument
''' </summary>
''' <typeparam name="TSource">The Generic type</typeparam>
''' <returns>DataTable</returns>
Friend Function CreateDataTable(Of TSource)() As DataTable
    Dim dt As New DataTable()
    For Each SourceMember As FieldInfo In GetType(TSource).GetFields(BindingFlags.Instance Or BindingFlags.[Public])
        dt.AddTableColumn(SourceMember, SourceMember.FieldType)
    Next

    For Each SourceMember As PropertyInfo In GetType(TSource).GetProperties(BindingFlags.Instance Or BindingFlags.[Public])
        If SourceMember.CanRead Then
            dt.AddTableColumn(SourceMember, SourceMember.PropertyType)
        End If
    Next
    Return dt
End Function

此方法的作用是循环遍历 TSource 类的所有公共实例成员,并检查它们是字段还是可读属性。

如果 Member 的类型是 DataColumn 支持的类型,它将被添加,使用名称、类型以及是否允许 DbNull。

/// <summary>
/// Adds a Column to a DataTable
/// </summary>
public static void AddTableColumn(this DataTable dt, MemberInfo SourceMember, Type MemberType)
{
    if (MemberType.IsAllowedType())
    {
        DataColumn dc;
        string FieldName = GetFieldNameAttribute(SourceMember);
        if (string.IsNullOrWhiteSpace(FieldName))
        {
            FieldName = SourceMember.Name;
        }
        if (Nullable.GetUnderlyingType(MemberType) == null)
        {
            dc = new DataColumn(FieldName, MemberType);
            dc.AllowDBNull = !MemberType.IsValueType;
        }
        else
        {
            dc = new DataColumn(FieldName, Nullable.GetUnderlyingType(MemberType));
            dc.AllowDBNull = true;
        }
        dt.Columns.Add(dc);
    }
}
''' <summary>
''' Adds a Column to a DataTable
''' </summary>
<Extension> _
Private Sub AddTableColumn(dt As DataTable, SourceMember As MemberInfo, MemberType As Type)
    If MemberType.IsAllowedType Then
        Dim dc As DataColumn
        Dim FieldName As String = GetFieldNameAttribute(SourceMember)
        If String.IsNullOrWhiteSpace(FieldName) Then
            FieldName = SourceMember.Name
        End If
        If Nullable.GetUnderlyingType(MemberType) Is Nothing Then
            dc = New DataColumn(FieldName, MemberType)
            dc.AllowDBNull = Not MemberType.IsValueType
        Else
            dc = New DataColumn(FieldName, Nullable.GetUnderlyingType(MemberType))
            dc.AllowDBNull = True
        End If
        dt.Columns.Add(dc)
    End If
End Sub

如果您希望 DataColumn 具有与 Member 不同的名称,您可以向其添加 FieldNameAttribute

[FieldName("Some odd fieldname")]
public string Name { get; set; }
<FieldName("Some odd fieldname")>
Property Name As String

FieldNameAttribute 显然优先于 TargetMembers 名称

创建 DataRowMapper

mapper 使用表达式树和反射创建。
它是通过循环遍历 DataTable 中的 DataColumn,并通过名称或 FieldnameAttribute 将它们与 Instanceclass 匹配来完成的

/// <summary>
/// Creates a delegate that maps an instance of TSource to an ItemArray of the supplied DataTable
/// </summary>
/// <typeparam name="TSource">The Generic Type to map from</typeparam>
/// <param name="dt">The DataTable to map to</param>
/// <returns>Func(Of TSource, Object())</returns>
static internal Func<TSource, object[]> CreateDataRowMapper<TSource>(DataTable dt)
{
    Type SourceType = typeof(TSource);
    ParameterExpression SourceInstanceExpression = Expression.Parameter(SourceType, "SourceInstance");
    List<Expression> Values = new List<Expression>();

    foreach (DataColumn col in dt.Columns)
    {
        foreach (FieldInfo SourceMember in SourceType.GetFields(BindingFlags.Instance | BindingFlags.Public))
        {
            if (MemberMatchesName(SourceMember, col.ColumnName))
            {
                Values.Add(GetSourceValueExpression(SourceInstanceExpression, SourceMember));
                break;
            }
        }
        foreach (PropertyInfo SourceMember in SourceType.GetProperties(BindingFlags.Instance | BindingFlags.Public))
        {
            if (SourceMember.CanRead && MemberMatchesName(SourceMember, col.ColumnName))
            {
                Values.Add(GetSourceValueExpression(SourceInstanceExpression, SourceMember));
                break;
            }
        }
    }
    NewArrayExpression body = Expression.NewArrayInit(Type.GetType("System.Object"), Values);
    return Expression.Lambda<Func<TSource, object[]>>(body, SourceInstanceExpression).Compile();
}
''' <summary>
''' Creates a delegate that maps an instance of TSource to an ItemArray of the supplied DataTable
''' </summary>
''' <typeparam name="TSource">The Generic Type to map from</typeparam>
''' <param name="dt">The DataTable to map to</param>
''' <returns>Func(Of TSource, Object())</returns>
Friend Function CreateDataRowMapper(Of TSource)(dt As DataTable) As Func(Of TSource, Object())
    Dim SourceType As Type = GetType(TSource)
    Dim SourceInstanceExpression As ParameterExpression = Expression.Parameter(SourceType, "SourceInstance")
    Dim Values As New List(Of Expression)

    For Each col As DataColumn In dt.Columns
        For Each SourceMember As FieldInfo In SourceType.GetFields(BindingFlags.Instance Or BindingFlags.[Public])
            If MemberMatchesName(SourceMember, col.ColumnName) Then
                Values.Add(GetSourceValueExpression(SourceInstanceExpression, SourceMember))
                Exit For
            End If
        Next
        For Each SourceMember As PropertyInfo In SourceType.GetProperties(BindingFlags.Instance Or BindingFlags.[Public])
            If SourceMember.CanRead AndAlso MemberMatchesName(SourceMember, col.ColumnName) Then
                Values.Add(GetSourceValueExpression(SourceInstanceExpression, SourceMember))
                Exit For
            End If
        Next
    Next
    Dim body As NewArrayExpression = Expression.NewArrayInit(Type.[GetType]("System.Object"), Values)
    Return Expression.Lambda(Of Func(Of TSource, Object()))(body, SourceInstanceExpression).Compile()
End Function

当我们有匹配项时,我们创建一个 MemberExpression,表示 Field 或 Property 中的值,我们将其添加到用于创建 NewArrayInitExpression 的数组中。

/// <summary>
/// Creates an Expression representing the value of the SourceMember
/// </summary>
/// <param name="SourceInstanceExpression"></param>
/// <param name="SourceMember"></param>
/// <returns></returns>
private static Expression GetSourceValueExpression(ParameterExpression SourceInstanceExpression, MemberInfo SourceMember)
{
    MemberExpression MemberExpression = Expression.PropertyOrField(SourceInstanceExpression, SourceMember.Name);
    Expression SourceValueExpression;

    if (Nullable.GetUnderlyingType(SourceMember.ReflectedType) == null)
    {
        SourceValueExpression = Expression.Convert(MemberExpression, typeof(object));
    }
    else
    {
        SourceValueExpression = Expression.Condition(
            Expression.Property(Expression.Constant(SourceInstanceExpression), "HasValue"),
            MemberExpression,
            Expression.Constant(DBNull.Value),
            typeof(object));
    }
    return SourceValueExpression;
}
''' <summary>
''' Creates an Expression representing the value of the SourceMember
''' </summary>
''' <param name="SourceInstanceExpression"></param>
''' <param name="SourceMember"></param>
''' <returns></returns>
Private Function GetSourceValueExpression(SourceInstanceExpression As ParameterExpression, SourceMember As MemberInfo) As Expression
    Dim MemberExpression As MemberExpression = Expression.PropertyOrField(SourceInstanceExpression, SourceMember.Name)
    Dim SourceValueExpression As Expression

    If Nullable.GetUnderlyingType(SourceMember.ReflectedType) Is Nothing Then
        SourceValueExpression = Expression.Convert(MemberExpression, GetType(Object))
    Else
        SourceValueExpression = Expression.Condition(
            Expression.Property(Expression.Constant(SourceInstanceExpression), "HasValue"),
            MemberExpression,
            Expression.Constant(DBNull.Value),
            GetType(Object)
            )
    End If
    Return SourceValueExpression
End Function

然后,此表达式被编译为委托。

DataTable 和 MapperDelegate 被缓存以提高性能

关注点

MemberExpression 的创建可以与 DataTable 的创建同时进行,但我反对这样做,以便更容易进行未来的增强

历史

  • 2015 年 3 月 25 日:v1.0 首次发布
  • 2015 年 4 月 28 日:v1.1 一些重构和类型检查
© . All rights reserved.