Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I create a dynamic multi-property Select on an IEnumerable<T> at runtime?

I asked a very similar question yesterday, but it wasn't until today I realised the answer I accepted doesn't solve all my problems. I have the following code:

public Expression<Func<TItem, object>> SelectExpression<TItem>(string fieldName)
{
    var param = Expression.Parameter(typeof(TItem), "item");
    var field = Expression.Property(param, fieldName);
    return Expression.Lambda<Func<TItem, object>>(field, 
        new ParameterExpression[] { param });
}

Which is used as follows:

string primaryKey = _map.GetPrimaryKeys(typeof(TOriginator)).Single();
var primaryKeyExpression = SelectExpression<TOriginator>(primaryKey);
var primaryKeyResults = query.Select(primaryKeyExpression).ToList();

This allows me to pull out the primary keys from an IQueryable<TUnknown>. The problem is that this code only works with a single primary key and I need to add support for multiple PKs.

So, is there any way I can adapt the SelectExpression method above to take an IEnumerable<string> (which is my list of primary key property names) and have the method return an expression that selects those keys?

I.e. Given the following:

var knownRuntimePrimaryKeys = new string[] { "CustomerId", "OrderId" }`

My Select needs to do the following (at runtime):

var primaryKeys = query.Select(x=> new { x.CustomerId, x.OrderId });
like image 316
djdd87 Avatar asked Jan 25 '12 10:01

djdd87


1 Answers

There is no easy way to do exactly what you want, because it would require you to create a new type dynamically (anonymous types are created by the compiler when they're known statically). While it is certainly feasible, it's probably not the easiest option...

You can achieve a similar result using tuples:

public Expression<Func<TItem, object>> SelectExpression<TItem>(string[] propertyNames)
{
    var properties = propertyNames.Select(name => typeof(TItem).GetProperty(name)).ToArray();
    var propertyTypes = properties.Select(p => p.PropertyType).ToArray();
    var tupleTypeDefinition = typeof(Tuple).Assembly.GetType("System.Tuple`" + properties.Length);
    var tupleType = tupleTypeDefinition.MakeGenericType(propertyTypes);
    var constructor = tupleType.GetConstructor(propertyTypes);
    var param = Expression.Parameter(typeof(TItem), "item");
    var body = Expression.New(constructor, properties.Select(p => Expression.Property(param, p)));
    var expr = Expression.Lambda<Func<TItem, object>>(body, param);
    return expr;
}
like image 87
Thomas Levesque Avatar answered Sep 30 '22 18:09

Thomas Levesque