我正在构建基于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)
};
}
}
由于用户必须能够对投影属性进行排序(而不是Customer属性),我将表达式重新创建为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,anonymous type
,这是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
重载。
答案 0 :(得分:3)
修复很简单就会受伤:
[assembly: InternalsVisibleTo("My.Search.Lib.Assembly")]
答案 1 :(得分:1)
这是我要求的测试。这是在Northwind数据库上,这对我来说很好。
static void Main(string[] args)
{
var dc = new NorthwindDataContext();
var source = dc.Categories;
Expression<Func<Category, object>> expr =
c => new
{
c.CategoryID,
c.CategoryName,
};
var oldParameter = expr.Parameters.Single();
var parameter = Expression.Parameter(oldParameter.Type, oldParameter.Name);
var body = expr.Body;
body = RebindParameter(body, oldParameter, parameter);
Console.WriteLine("Parameter Type: {0}", parameter.Type);
Console.WriteLine("Body Type: {0}", body.Type);
var newExpr = Expression.Lambda(body, parameter);
Console.WriteLine("Old Expression Type: {0}", expr.Type);
Console.WriteLine("New Expression Type: {0}", newExpr.Type);
var query = Queryable.Select(source, (dynamic)newExpr);
Console.WriteLine(query);
foreach (var item in query)
{
Console.WriteLine(item);
Console.WriteLine("\t{0}", item.CategoryID.GetType());
Console.WriteLine("\t{0}", item.CategoryName.GetType());
}
Console.Write("Press any key to continue . . . ");
Console.ReadKey(true);
Console.WriteLine();
}
static Expression RebindParameter(Expression expr, ParameterExpression oldParam, ParameterExpression newParam)
{
switch (expr.NodeType)
{
case ExpressionType.Parameter:
var parameterExpression = expr as ParameterExpression;
return (parameterExpression.Name == oldParam.Name)
? newParam
: parameterExpression;
case ExpressionType.MemberAccess:
var memberExpression = expr as MemberExpression;
return memberExpression.Update(
RebindParameter(memberExpression.Expression, oldParam, newParam));
case ExpressionType.AndAlso:
case ExpressionType.OrElse:
case ExpressionType.Equal:
case ExpressionType.NotEqual:
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual:
var binaryExpression = expr as BinaryExpression;
return binaryExpression.Update(
RebindParameter(binaryExpression.Left, oldParam, newParam),
binaryExpression.Conversion,
RebindParameter(binaryExpression.Right, oldParam, newParam));
case ExpressionType.New:
var newExpression = expr as NewExpression;
return newExpression.Update(
newExpression.Arguments
.Select(arg => RebindParameter(arg, oldParam, newParam)));
case ExpressionType.Call:
var methodCallExpression = expr as MethodCallExpression;
return methodCallExpression.Update(
RebindParameter(methodCallExpression.Object, oldParam, newParam),
methodCallExpression.Arguments
.Select(arg => RebindParameter(arg, oldParam, newParam)));
default:
return expr;
}
}
此外,在这种情况下,动态方法解析对您来说并没有太大帮助,因为Select()
只有两个非常不同的重载。最后,您只需要记住,由于您没有任何静态类型信息,因此您不会对结果进行任何静态类型检查。话虽如此,这也适合你(使用上面的代码示例):
var query = Queryable.Select(source, expr).Cast<dynamic>();
Console.WriteLine(query);
foreach (var item in query)
{
Console.WriteLine(item);
Console.WriteLine("\t{0}", item.CategoryID.GetType());
Console.WriteLine("\t{0}", item.CategoryName.GetType());
}