如何使 LINQ-to-Objects 处理投影?
How to make LINQ-to-Objects handle projections?
我已经实现了一个基本的(朴素的?)LINQ 提供程序,它可以满足我的目的,但是我想解决一些问题,但我不确定如何解决。例如:
// performing projection with Linq-to-Objects, since Linq-to-Sage won't handle this:
var vendorCodes = context.Vendors.ToList().Select(e => e.Key);
我的 IQueryProvider
实现有一个 CreateQuery<TResult>
看起来像这样的实现:
public IQueryable<TResult> CreateQuery<TResult>(Expression expression)
{
return (IQueryable<TResult>)Activator
.CreateInstance(typeof(ViewSet<>)
.MakeGenericType(elementType), _view, this, expression, _context);
}
显然,当 Expression
是 MethodCallExpression
并且 TResult
是 string
时,这会令人窒息,所以我想我会 执行 这该死的东西:
public IQueryable<TResult> CreateQuery<TResult>(Expression expression)
{
var elementType = TypeSystem.GetElementType(expression.Type);
if (elementType == typeof(EntityBase))
{
Debug.Assert(elementType == typeof(TResult));
return (IQueryable<TResult>)Activator.CreateInstance(typeof(ViewSet<>).MakeGenericType(elementType), _view, this, expression, _context);
}
var methodCallExpression = expression as MethodCallExpression;
if(methodCallExpression != null && methodCallExpression.Method.Name == "Select")
{
return (IQueryable<TResult>)Execute(methodCallExpression);
}
throw new NotSupportedException(string.Format("Expression '{0}' is not supported by this provider.", expression));
}
所以当我 运行 var vendorCodes = context.Vendors.Select(e => e.Key);
我最终进入我的 private static object Execute<T>(Expression,ViewSet<T>)
重载,它打开最里面的过滤器表达式的方法名称并在底层进行实际调用 API.
现在,在这种情况下,我传递了 Select
方法调用表达式,因此过滤器表达式是 null
并且我的 switch
块被跳过 - 这很好 - 其中我被困在这里:
var method = expression as MethodCallExpression;
if (method != null && method.Method.Name == "Select")
{
// handle projections
var returnType = method.Type.GenericTypeArguments[0];
var expType = typeof (Func<,>).MakeGenericType(typeof (T), returnType);
var body = method.Arguments[1] as Expression<Func<T,object>>;
if (body != null)
{
// body is null here because it should be as Expression<Func<T,expType>>
var compiled = body.Compile();
return viewSet.Select(string.Empty).AsEnumerable().Select(compiled);
}
}
我需要对 MethodCallExpression
做什么才能将其传递给 LINQ-to-Objects 的 Select
方法?我是否正确地处理了这个问题?
()
这是有效的代码:
var method = expression as MethodCallExpression;
if (method != null && method.Method.Name == "Select")
{
// handle projections
var lambda = ((UnaryExpression)method.Arguments[1]).Operand as LambdaExpression;
if (lambda != null)
{
var returnType = lambda.ReturnType;
var selectMethod = typeof(Queryable).GetMethods().First(m => m.Name == "Select");
var typedGeneric = selectMethod.MakeGenericMethod(typeof(T), returnType);
var result = typedGeneric.Invoke(null, new object[] { viewSet.ToList().AsQueryable(), lambda }) as IEnumerable;
return result;
}
}
现在这个:
var vendorCodes = context.Vendors.ToList().Select(e => e.Key);
可以看起来像这样:
var vendorCodes = context.Vendors.Select(e => e.Key);
你甚至可以这样做:
var vendors = context.Vendors.Select(e => new { e.Key, e.Name });
关键是直接从 Queryable
类型中获取 Select
方法,使用 lambda 的 returnType
使其成为通用方法,然后调用它 viewSet.ToList().AsQueryable()
.
我已经实现了一个基本的(朴素的?)LINQ 提供程序,它可以满足我的目的,但是我想解决一些问题,但我不确定如何解决。例如:
// performing projection with Linq-to-Objects, since Linq-to-Sage won't handle this:
var vendorCodes = context.Vendors.ToList().Select(e => e.Key);
我的 IQueryProvider
实现有一个 CreateQuery<TResult>
看起来像这样的实现:
public IQueryable<TResult> CreateQuery<TResult>(Expression expression)
{
return (IQueryable<TResult>)Activator
.CreateInstance(typeof(ViewSet<>)
.MakeGenericType(elementType), _view, this, expression, _context);
}
显然,当 Expression
是 MethodCallExpression
并且 TResult
是 string
时,这会令人窒息,所以我想我会 执行 这该死的东西:
public IQueryable<TResult> CreateQuery<TResult>(Expression expression)
{
var elementType = TypeSystem.GetElementType(expression.Type);
if (elementType == typeof(EntityBase))
{
Debug.Assert(elementType == typeof(TResult));
return (IQueryable<TResult>)Activator.CreateInstance(typeof(ViewSet<>).MakeGenericType(elementType), _view, this, expression, _context);
}
var methodCallExpression = expression as MethodCallExpression;
if(methodCallExpression != null && methodCallExpression.Method.Name == "Select")
{
return (IQueryable<TResult>)Execute(methodCallExpression);
}
throw new NotSupportedException(string.Format("Expression '{0}' is not supported by this provider.", expression));
}
所以当我 运行 var vendorCodes = context.Vendors.Select(e => e.Key);
我最终进入我的 private static object Execute<T>(Expression,ViewSet<T>)
重载,它打开最里面的过滤器表达式的方法名称并在底层进行实际调用 API.
现在,在这种情况下,我传递了 Select
方法调用表达式,因此过滤器表达式是 null
并且我的 switch
块被跳过 - 这很好 - 其中我被困在这里:
var method = expression as MethodCallExpression;
if (method != null && method.Method.Name == "Select")
{
// handle projections
var returnType = method.Type.GenericTypeArguments[0];
var expType = typeof (Func<,>).MakeGenericType(typeof (T), returnType);
var body = method.Arguments[1] as Expression<Func<T,object>>;
if (body != null)
{
// body is null here because it should be as Expression<Func<T,expType>>
var compiled = body.Compile();
return viewSet.Select(string.Empty).AsEnumerable().Select(compiled);
}
}
我需要对 MethodCallExpression
做什么才能将其传递给 LINQ-to-Objects 的 Select
方法?我是否正确地处理了这个问题?
(
这是有效的代码:
var method = expression as MethodCallExpression;
if (method != null && method.Method.Name == "Select")
{
// handle projections
var lambda = ((UnaryExpression)method.Arguments[1]).Operand as LambdaExpression;
if (lambda != null)
{
var returnType = lambda.ReturnType;
var selectMethod = typeof(Queryable).GetMethods().First(m => m.Name == "Select");
var typedGeneric = selectMethod.MakeGenericMethod(typeof(T), returnType);
var result = typedGeneric.Invoke(null, new object[] { viewSet.ToList().AsQueryable(), lambda }) as IEnumerable;
return result;
}
}
现在这个:
var vendorCodes = context.Vendors.ToList().Select(e => e.Key);
可以看起来像这样:
var vendorCodes = context.Vendors.Select(e => e.Key);
你甚至可以这样做:
var vendors = context.Vendors.Select(e => new { e.Key, e.Name });
关键是直接从 Queryable
类型中获取 Select
方法,使用 lambda 的 returnType
使其成为通用方法,然后调用它 viewSet.ToList().AsQueryable()
.