2016-06-15 75 views
7

Ho implementato un provider LINQ di base (ingenuo?) Che funziona correttamente per i miei scopi, ma c'è un numero di stranezze che mi piacerebbe affrontare, ma non sono sicuro di come. Per esempio:Come rendere LINQ-to-Objects gestire le proiezioni?

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

mio IQueryProvider implementazione ha avuto un CreateQuery<TResult> implementazione simile a questo:

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

Ovviamente questo soffoca quando il Expression è un MethodCallExpression e TResult è un string, così ho pensato di eseguire la cosa darn:

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

Così, quando corro , finisco nel mio overload private static object Execute<T>(Expression,ViewSet<T>), che attiva il nome del metodo dell'espressione del filtro più interno e effettua le chiamate effettive nell'API sottostante.

Ora, in questo caso sto passando l'espressione chiamata Select metodo, quindi l'espressione di filtro è null e il mio blocco switch viene saltato - che va bene - dove mi sono bloccato in è qui:

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

Cosa devo fare al mio MethodCallExpression per poterlo passare al metodo Select di LINQ-to-Objects? Mi sto avvicinando a questo correttamente?

+0

È possibile ottenere Expression dal metodo Select in ingresso, quindi passarlo a ViewSet Select o compilarlo come Func e passarlo lì. Quindi dovrai semplicemente inviare nuovamente la proiezione al tuo viewSet. –

+0

@SergeyLitvinov ok ... quindi come faccio ad avere un 'espressione > 'da quel' MethodCallExpression'? –

+0

Anche se non ho un handle sul tipo di ritorno di 'Func', come posso lanciarlo sull'esatto' Expression 'in modo da poter accedere al metodo' Compile' e mantenere le cose fortemente tipizzate in modo che '.Select (convertedExpression)' possa funzionare? –

risposta

3

(credits to Sergey Litvinov)

Ecco il codice che ha funzionato:

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

Ora questo:

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

potrebbe essere questa:

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

E si potrebbe anche fare questo:

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

La chiave è stato quello di recuperare il metodo Select direttamente dal tipo Queryable, ne fanno un metodo generico utilizzando il Lambda returnType, e quindi richiamare fuori viewSet.ToList().AsQueryable().