我正在构建一个基于LINQ的查询生成器。
其中之一的特点是能够在查询定义中指定任意服务器端投影。例如:
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)
};
}
}
既然用户必须能够根据投影属性(而不是客户属性)进行排序,因此我将表达式重新创建为 Func<Customer,anonymous type>
而不是 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());
为了返回预期的查询结果,我希望能够做到这一点(实际上,在几乎相同的概念证明中,这种方法是有效的):
return Queryable.Select((IQueryable<TRoot>)source, (dynamic)properProjection);
TRoot是SearchDefinition中的类型参数。这会导致以下异常:
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 ...
如果你仔细看,它会错误地推断出泛型参数:
Customer,object
而不是Customer,匿名类型
,后者才是properProjection
表达式的实际类型(已经双重检查过)。我的解决方法是使用反射。但是对于泛型参数,这真是一团糟:
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 });
有没有更好的方法?
更新:dynamic
失败的原因是匿名类型被定义为 internal
。这就是为什么在一个概念验证项目中,所有内容都在同一程序集中时它能够正常工作的原因。
我很满意这样。但我仍然希望找到一种更清晰的方法来找到正确的 Queryable.Select
重载。
ParameterRebinder.ReplaceParameter
真的有必要吗?表达式主体已经具有正确的类型,因此当重建表达式时,它将具有正确的类型。我的测试似乎在这里起作用。 - Jeff Mercado