c#使用ExpressionTree将DataTable映射到List

本文关键字:List 映射 使用 ExpressionTree DataTable | 更新日期: 2023-09-27 18:13:55

我已经编写了一个ToList();扩展方法来将数据表转换为列表。这只是在某些情况下工作,但我们有很多旧代码使用数据表,有时需要它。我的问题是,这种方法与反射什么是好的,但不是性能。我需要大约1,200个sek为100,000个数据行。

所以我决定用表达式树来构建这个。首先,我想取代属性的Setter调用。到目前为止,我可以很容易地得到值:

var exactType = Nullable.GetUnderlyingType(propType) ?? propType;
var wert = Convert.ChangeType(zeile[spaltenname], exactType);

并设置为:

propertyInfo.SetValue(tempObjekt, wert, null);

现在我搜索StackOverflow,发现这个:

var zielExp = Expression.Parameter(typeof(T));
var wertExp = Expression.Parameter(propType);
var propertyExp = Expression.Property(zielExp, matchProp);
var zuweisungExp = Expression.Assign(propertyExp, wertExp);
var setter = Expression.Lambda<Action<T, int>>(zuweisungExp, zielExp, wertExp).Compile();
setter(tempObjekt, wert);

我最大的问题是Lambda Action需要一个整数。但我需要这个期望我的属性的类型。我有我的属性的类型通过PropertyInfo。但我不能让这个工作。我想我可以很容易地做到:

Action<T, object>

,但这会导致以下异常:

ArgumentException参数表达式从类型"系统。Int32"不能用作"System.Object"类型的Delegateparameter。

有人知道一个可能的解决方案吗?

c#使用ExpressionTree将DataTable映射到List<T>

代替一般的Expression.Lambda方法,你可以使用这个重载,它的类型是:

public static LambdaExpression Lambda(
   Type delegateType,
   Expression body,
   params ParameterExpression[] parameters
)

然后你可以使用Type.MakeGenericType方法为你的动作创建类型:

var actionType = typeof(Action<,>).MakeGenericType(typeof(T), proptype);
var setter = Expression.Lambda(actionType, zuweisungExp, zielExp, wertExp).Compile();

编辑关于性能的评论:

您还可以构建表达式运行时,通过select将DataTable映射到类型为T的类,因此只需要使用反射一次,这将极大地提高性能。我编写了以下扩展方法来将DataTable转换为List<T>(注意,如果您不打算将所有数据列映射到类中的属性,则此方法抛出运行时异常,因此请务必注意,如果可能发生这种情况):

public static class LocalExtensions
{
    public static List<T> DataTableToList<T>(this DataTable table) where T : class
    {
        //Map the properties in a dictionary by name for easy access
        var propertiesByName = typeof(T)
            .GetProperties(BindingFlags.Public | BindingFlags.Instance)
            .ToDictionary(p => p.Name);
        var columnNames = table.Columns.Cast<DataColumn>().Select(dc => dc.ColumnName);
        //The indexer property to access DataRow["columnName"] is called "Item"
        var property = typeof(DataRow).GetProperties().First(p => p.Name == "Item" 
            && p.GetIndexParameters().Length == 1 
            && p.GetIndexParameters()[0].ParameterType == typeof(string));
        var paramExpr = Expression.Parameter(typeof(DataRow), "r");
        var newExpr = Expression.New(typeof(T));
        //Create the expressions to map properties from your class to the corresponding
        //value in the datarow. This will throw a runtime exception if your class 
        //doesn't contain properties for all columnnames!
        var memberBindings = columnNames.Select(columnName =>
        {
            var pi = propertiesByName[columnName];
            var indexExpr = Expression.MakeIndex(paramExpr, property, 
                new[] { Expression.Constant(columnName) });
            //Datarow["columnName"] is of type object, cast to the right type
            var convert = Expression.Convert(indexExpr, pi.PropertyType);
            return Expression.Bind(pi, convert);
        });
        var initExpr = Expression.MemberInit(newExpr, memberBindings);
        var func = Expression.Lambda<Func<DataRow, T>>(initExpr,paramExpr).Compile();
        return table.Rows.Cast<DataRow>().Select(func).ToList();
    }
}

然后我写了一个小的testclass和一些代码,创建了一个1,000,000行的数据表,并将其映射到一个列表。构建表达式+转换为列表现在只需要486ms在我的pc上(当然,它是一个非常小的类):

class Test
{
    public string TestString { get; set; }
    public int TestInt { get; set; }
}
class Program
{
    static void Main()
    {
        DataTable table = new DataTable();
        table.Columns.Add(new DataColumn("TestString", typeof(string)));
        table.Columns.Add(new DataColumn("TestInt", typeof(int)));
        for(int i = 0; i < 1000000; i++)
        {
            var row = table.NewRow();
            row["TestString"] = $"String number: {i}";
            row["TestInt"] = i;
            table.Rows.Add(row);
        }
        var stopwatch = Stopwatch.StartNew();
        var myList = table.DataTableToList<Test>();
        stopwatch.Stop();
        Console.WriteLine(stopwatch.Elapsed.ToString());
    }
}

我想我理解对了。我无法翻译你的变量,所以我在这里根据我在你的问题中看到的做出最好的猜测:

对于Action<object,object>,其中第一个参数是实体本身,第二个参数是属性的类型,您可以使用

var instance = Expression.Parameter(typeof(object), "i");
var argument = Expression.Parameter(typeof(object), "a");
var convertObj = Expression.TypeAs(instance, propertyInfo.DeclaringType);
var convert = Expression.Convert(argument, propertyInfo.PropertyType);
var setterCall = Expression.Call(convertObj, propertyInfo.GetSetMethod(), convert);
var compiled = ((Expression<Action<object, object>>) Expression.Lambda(setterCall, instance, argument)).Compile();

如果你知道T(即实体的类型),你可以这样做:

var instance = Expression.Parameter(typeof(T), "i");
var argument = Expression.Parameter(typeof(object), "a");
var convert = Expression.Convert(argument, propertyInfo.PropertyType);
var setterCall = Expression.Call(instance , propertyInfo.GetSetMethod(), convert);
var compiled = ((Expression<Action<T, object>>) Expression.Lambda(setterCall, instance, argument)).Compile();

我在这里评论,因为我没有必要的声誉来评论@Alexander Derek的回应

    var memberBindings = columnNames.Select(columnName =>
    {
        var pi = propertiesByName[columnName];
        var indexExpr = Expression.MakeIndex(paramExpr, property, 
            new[] { Expression.Constant(columnName) });
        //Datarow["columnName"] is of type object, cast to the right type
        var convert = Expression.Convert(indexExpr, pi.PropertyType);
        return Expression.Bind(pi, convert);
    });

为了避免运行时异常,我添加了try-catch和。where()

        var memberBindings = columnNames.Select(columnName =>
        {
            try
            {
                var pi = propertiesByName[columnName];
                var indexExpr = Expression.MakeIndex(paramExpr, property,
                    new[] { Expression.Constant(columnName) });
                var convert = Expression.Convert(indexExpr, pi.PropertyType);
                return Expression.Bind(pi, convert);
            }
            catch(Exception e)
            {
                return null;
            }                
        });
        var initExpr = Expression.MemberInit(newExpr, memberBindings.Where(obj => obj != null));