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() .

链接地址: http://www.djcxy.com/p/34768.html

上一篇: 试图了解Ruby .chr和.ord方法

下一篇: 如何制作LINQ