I’m building a LINQ-based query generator.
One of the features is being able to specify an arbitrary server-side projection as part of the query definition. For example:
class CustomerSearch : SearchDefinition<Customer>
{
protected override Expression<Func<Customer, object>> GetProjection()
{
return x => new
{
Name = x.Name,
Agent = x.Agent.Code
Sales = x.Orders.Sum(o => o.Amount)
};
}
}
Since the user must then be able to sort on the projection properties (as opposed to Customer properties), I recreate the expression as a Func<Customer,anonymous type> instead of Func<Customer, object>:
//This is a method on SearchDefinition
IQueryable Transform(IQueryable source)
{
var projection = GetProjection();
var properProjection = Expression.Lambda(projection.Body,
projection.Parameters.Single());
In order to return the projected query, I’d love to be able to do this (which, in fact, works in an almost identical proof of concept):
return Queryable.Select((IQueryable<TRoot>)source, (dynamic)properProjection);
TRoot is the type parameter in SearchDefinition. This results in the following exception:
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException:
The best overloaded method match for
'System.Linq.Queryable.Select<Customer,object>(System.Linq.IQueryable<Customer>,
System.Linq.Expressions.Expression<System.Func<Customer,object>>)'
has some invalid arguments
at CallSite.Target(Closure , CallSite , Type , IQueryable`1 , Object )
at System.Dynamic.UpdateDelegates.UpdateAndExecute3[T0,T1,T2,TRet]
(CallSite site, T0 arg0, T1 arg1, T2 arg2)
at SearchDefinition`1.Transform(IQueryable source) in ...
If you look closely, it’s inferring the generic parameters incorrectly: Customer,object instead of Customer,anonymous type, which is the actual type of the properProjection expression (double-checked)
My workaround is using reflection. But with generic arguments, it’s a real mess:
var genericSelectMethod = typeof(Queryable).GetMethods().Single(
x => x.Name == "Select" &&
x.GetParameters()[1].ParameterType.GetGenericArguments()[0]
.GetGenericArguments().Length == 2);
var selectMethod = genericSelectMethod.MakeGenericMethod(source.ElementType,
projectionBody.Type);
return (IQueryable)selectMethod.Invoke(null, new object[]{ source, projection });
Does anyone know of a better way?
Update: the reason why dynamic fails is that anonymous types are defined as internal. That’s why it worked using a proof-of-concept project, where everything was in the same assembly.
I’m cool with that. I’d still like to find a cleaner way to find the right Queryable.Select overload.
The fix is so simple it hurts: