How to make LINQ
I have implemented a basic (naive?) LINQ provider that works ok for my purposes, but there's a number of quirks I'd like to address, but I'm not sure how. For example:
// performing projection with Linq-to-Objects, since Linq-to-Sage won't handle this:
var vendorCodes = context.Vendors.ToList().Select(e => e.Key);
My IQueryProvider
implementation had a CreateQuery<TResult>
implementation looking like this:
public IQueryable<TResult> CreateQuery<TResult>(Expression expression)
{
return (IQueryable<TResult>)Activator
.CreateInstance(typeof(ViewSet<>)
.MakeGenericType(elementType), _view, this, expression, _context);
}
Obviously this chokes when the Expression
is a MethodCallExpression
and TResult
is a string
, so I figured I'd execute the darn thing:
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));
}
So when I run var vendorCodes = context.Vendors.Select(e => e.Key);
I end up in my private static object Execute<T>(Expression,ViewSet<T>)
overload, which switches on the innermost filter expression's method name and makes the actual calls in the underlying API.
Now, in this case I'm passing the Select
method call expression, so the filter expression is null
and my switch
block gets skipped - which is fine - where I'm stuck at is here:
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);
}
}
What do I need to do to my MethodCallExpression
in order to be able to pass it to LINQ-to-Objects' Select
method? Am I even approaching this correctly?
(credits to Sergey Litvinov)
Here's the code that worked:
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;
}
}
Now this:
var vendorCodes = context.Vendors.ToList().Select(e => e.Key);
Can look like this:
var vendorCodes = context.Vendors.Select(e => e.Key);
And you could even do this:
var vendors = context.Vendors.Select(e => new { e.Key, e.Name });
The key was to fetch the Select
method straight from the Queryable
type, make it a generic method using the lambda's returnType
, and then invoke it off viewSet.ToList().AsQueryable()
.
上一篇: 试图了解Ruby .chr和.ord方法
下一篇: 如何制作LINQ