2016-06-15 58 views
7

我已經實現了一個基本的(天真?)LINQ提供程序,可以用於我的目的,但我想解決一些怪癖,但我不知道如何。例如:如何使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); 
} 

顯然這扼流圈當ExpressionMethodCallExpressionTResultstring,所以我想我會執行該死的東西:

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)); 
} 

因此,當我運行我最終在我的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); 
    } 
} 

爲了能夠將它傳遞給LINQ-to-Objects的Select方法,我需要對我的MethodCallExpression做些什麼?我是否正確地接近這個?

+0

您可以從傳入Select方法獲得表達式,然後將其傳遞給ViewSet Select,或者將其編譯爲Func並將其傳遞到那裏。所以你只需重新投影到你的viewSet。 –

+0

@SergeyLitvinov好的......那麼我該如何從'MethodCallExpression'中得到'Expression >? –

+0

另外,如果我沒有'Func'返回類型的句柄,我怎樣才能將它轉換成正確的'Expression ',以便能夠訪問'Compile'方法並保持強類型這樣'.Select(convertedExpression)'可以工作嗎? –

回答

3

credits to Sergey Litvinov

下面是工作代碼:

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方法,使其使用拉姆達的returnType,然後調用它關閉viewSet.ToList().AsQueryable()的通用方法。