1

I have written a ToList(); extension Method to convert a DataTable to List. This just works under some circumstances but we have much old code which uses DataTables and sometimes it's needed. My Problem is that this method works with reflection what is ok but not that performant. I need about 1,2sek for 100.000 DataRows.

So i decided to build this with Expression Trees. At first i want to replace the Setter Call of Properties. Up to this time i could easily get the value:

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

and set it:

propertyInfo.SetValue(tempObjekt, wert, null);

Now i searched StackOverflow and found this:

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);

My big Problem is that the Lambda Action expects an integer. But i need this expecting the type of my Property. I have the Type of my Property via PropertyInfo. But can't get this to work. Thought i can easily make:

Action<T, object>

but this results in following excepion:

ArgumentException The ParameterExpression from Type "System.Int32" cannot be used as Delegateparameter from Type "System.Object".

Someone out there knows a possible solution?

1
  • So you are trying to call the setters of the object based on value passed into it? Is the type of the property int? I'm a bit confused. You can do what you want without dynamic invoke. But in order to help I need to know if it's a single property, all properties, a list of properties, etc. You can use an action, you get the method info of the property's GetSetMethod() and you can pass it the type as a generic parameter or since you don't know it you'd use object and call Expression.Convert. Commented Oct 20, 2016 at 23:33

3 Answers 3

2

Instead of the generic Expression.Lambda method you can use this overload which takes a type:

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

Then you can use the Type.MakeGenericType method to create the type for your action:

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

Edit following the comments regarding performance:

You can also just build the expression runtime to map the DataTable to your class of type T with a select, so there's only need to use reflection once, which should greatly improve performance. I wrote the following extension method to convert a DataTable to List<T> (note that this method will throw a runtime exception if you don't plan to map all datacolumns to a property in the class, so be sure to take care of that if that might happen):

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();
    }
}

Then I wrote a small testclass and some code which creates a datatable of 1,000,000 rows that get mapped to a list. Building the expression + converting to a list now only takes 486ms on my pc (granted it is a very small class of course):

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());
    }
}
Sign up to request clarification or add additional context in comments.

7 Comments

Thanks this looks quite good. But if i try to call the setter know, the compiler tells me "Method or Delegate expected". I'm trying this setter(myTempObject, wert);
@Sebi You need to call setter.DynamicInvoke(...) to be able to use it
Thanks this works. LoadTime is 80sek now :D but this is sth. i get fixed.
@Sebi DynamicInvoke needs to use a lot of reflection so I can imagine it's slow if you call it in a tight loop ;-) But I still don't entirely understand what you're trying to do, you want a generic method to map a datatable to a list of an existing class?
@Sebi Edited my answer, I think this is what you're looking for (reflection used once to build the expression, not in the loop)
|
1

I think I've understood you correctly. I cannot translate your variables so I'm taking my best guess here based on what I'm seeing in your question:

For an Action<object,object> where the first parameter is the Entity itself and the second is the type of the property you can use

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();

If you know T (ie, the type of the Entity), you can do this instead:

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();

7 Comments

The Problem is that every solution needs to use reflection as well. So this is not faster then still call propertyInfo.SetValue();
If you cache the lambda and only compile it once it should be faster. You shouldn't be calling this in each loop. Your other option would be to use expression.property and assign like you have above but you still have to convert to the right type and since you still need to know the type you need the property info
Yes i build a version which cache the whole stuff, but still 2x slower then reflection access. So i think using this is better for me. Thanks for helping.
The answer above has been updated and his solution now should work very well for you. I use something similar in my code for mapping data reader records to object
Oh perfect i will give it a try :)
|
1

I comment here because I do not have the necessary reputation to comment on the response of @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);
    });

in order to avoid runtime exception i added a try-catch and .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));

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.