2016-06-15 44 views
7

Zaimplementowałam podstawowego (naiwnego?) Dostawcę LINQ, który działa dobrze dla moich celów, ale jest kilka dziwactw, którymi chciałbym się zająć, ale nie jestem pewien jak. Na przykład:Jak sprawić, aby LINQ-to-Object obsłużyło projekcje?

// performing projection with Linq-to-Objects, since Linq-to-Sage won't handle this: 
var vendorCodes = context.Vendors.ToList().Select(e => e.Key); 

Moja implementacja IQueryProvider miał CreateQuery<TResult> realizacja wygląda tak:

public IQueryable<TResult> CreateQuery<TResult>(Expression expression) 
{ 
    return (IQueryable<TResult>)Activator 
     .CreateInstance(typeof(ViewSet<>) 
     .MakeGenericType(elementType), _view, this, expression, _context); 
} 

Oczywiście dławiki gdy Expression jest MethodCallExpression i TResult jest string, więc pomyślałem, ja bym wykonaj rzecz:

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

Tak więc po uruchomieniu kończę w moim przeciążeniu , które przełącza najbardziej wewnętrzną nazwę metody wyrażenia filtru i wykonuje faktyczne wywołania w bazowym interfejsie API.

Teraz, w tym wypadku jestem zdaniu wyraz połączenia Select metoda, więc wyrażenie filtr jest null i mój switch blok zostanie pominięty - co jest w porządku - gdzie utknąłem na jest tutaj:

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

Co muszę zrobić, aby mój MethodCallExpression był w stanie przekazać go do metody LINQ-to-Object 'Select? Czy podchodzę do tego poprawnie?

+0

Możesz pobrać Expression z przychodzącej metody Select, a następnie przekazać ją do ViewSet Select lub skompilować jako Func i przekazać tam. Więc po prostu wyślesz ponownie projekcję do swojego zestawu widoków. –

+0

@SergeyLitvinov ok ... więc w jaki sposób uzyskać 'Expression >' z tego 'MethodCallExpression'? –

+0

Również, jeśli nie mam uchwytu na typ powrotu 'Func', jak mogę rzucić go do prawidłowego' Wyrażenie 'tak, aby móc uzyskać dostęp do metody' Kompilacja' i utrzymać rzeczy mocno wpisane aby ".Select (ConverExpression)" mógł działać? –

Odpowiedz

3

(credits to Sergey Litvinov)

Oto kod, który pracował:

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

Teraz:

var vendorCodes = context.Vendors.ToList().Select(e => e.Key); 

może wyglądać następująco:

var vendorCodes = context.Vendors.Select(e => e.Key); 

I nawet można to zrobić:

var vendors = context.Vendors.Select(e => new { e.Key, e.Name }); 

Kluczem było sprowadzić metodę Select prosto od rodzaju Queryable, sprawiają, że metoda rodzajowa pomocą lambda na returnType, a następnie wywołać ją viewSet.ToList().AsQueryable().