2014-11-04 13 views
7

Let dire che ho una funzione come questa:Esiste un modo per incorporare le funzioni esterne in una query EF Linq?

var filterValue = GetCurrentFilter(state); 

E poi una query di EF:

var result = context.EntitySet.Where(x=> x.column > filterValue); 

questo funziona, ma non appena provo a inline che:

var result = context.EntitySet.Where(x=> x.column > GetCurrentFilter(state)); 

Non lo fa perché EF Linq ha provato ad analizzare l'albero delle espressioni GetCurrentFilter e non è in grado di farlo. Questo è tutto abbastanza comprensibile.

La mia domanda è, c'è un modo per far sapere a EF Linq che è necessario eseguire la funzione GetCurrentFilter quando costruisce l'albero e utilizza il suo risultato nell'albero?

Qualcosa di simile

var result = context.EntitySet.Where(x=> x.column > EfUtil.ResultOf(GetCurrentFilter(state))); 

Poiché GetCurrentFilter non ha parametri che è una parte della query questo dovrebbe essere tecnicamente possibile farlo se EF Linq può sostenerla che è. Sospetto che mi manchi solo la sintassi corretta per questo.

risposta

7

Marca GetCurrentFilter una proprietà (di sola lettura) anziché un metodo. EF valuterà le proprietà ai loro valori, piuttosto che provare a tradurli in SQL, a differenza dei metodi.


L'unica altra strada che avete è quello di attraversare l'intero albero di espressione, la ricerca per l'utilizzo del metodo ResultOf, valutare la sua parametro su un valore, e quindi in linea, che il valore in cui la chiamata ResultOf era una volta, rebuiding la query attorno a quel valore.

Per far funzionare ciò significa che è necessario non solo racchiudere il codice che si desidera in linea in una chiamata a EfUtil.ResultOf, ma significa anche chiamare un metodo sulla query stessa per forzarlo a tornare indietro e valutarlo :

public class EfUtil 
{ 
    public static T ResultOf<T>(T value) 
    { 
     return value; 
    } 
} 
//Note this could probably use a better name 
public static IQueryable<T> EvaluateResults<T>(this IQueryable<T> query) 
{ 
    return query.Provider.CreateQuery<T>(
     new ExpressionEvaluator().Visit(query.Expression)); 
} 

internal class ExpressionEvaluator : ExpressionVisitor 
{ 
    protected override Expression VisitMethodCall(MethodCallExpression m) 
    { 
     if (m.Method.Name == "ResultOf" && m.Method.DeclaringType == typeof(EfUtil)) 
     { 
      Expression target = m.Arguments[0]; 

      object result = Expression.Lambda(target) 
       .Compile() 
       .DynamicInvoke(); 

      return Expression.Constant(result, target.Type); 
     } 
     else 
      return base.VisitMethodCall(m); 
    } 
} 

Questo permetterebbe di scrivere:

var result = context.EntitySet.Where(x=> x.column > EfUtil.ResultOf(GetCurrentFilter(state))) 
    .EvaluateResults(); 

sarebbe quindi valutare GetCurrentFilter(state) sul lato client e inline il risultato come una costante nella query.

Come test leggermente più semplice, possiamo scrivere la seguente:

var query = new[] { 1, 2, 3 } 
    .AsQueryable() 
    .Where(x => x > EfUtil.ResultOf(Math.Max(1, 2))) 
    .EvaluateResults(); 

Console.WriteLine(query.ToString()); 

Ed stamperà:.

System.Int32 [] Dove (x => (x> 2))

Quale è esattamente ciò che vogliamo.

Si noti che l'uso del parametro lambda (x in questi esempi) non può essere utilizzato in nessuna parte all'interno della chiamata a EfUtil.ResultOf o il codice non funzionerà e non potrebbe essere fatto funzionare (sebbene potremmo generare un messaggio di errore migliore se ci tenevamo abbastanza).

+0

Questo metodo potrebbe avere alcuni parametri, nessuno dei quali viene utilizzato nella query. Non c'è modo di definire le proprietà parametrizzate in C#. Ma tu hai ragione, nel senso che non l'ho chiarito nella mia domanda. Lascia che ti aggiorni. –

+0

@zespri Questo problema è risolvibile, sebbene abbia una restrizione aggiuntiva. – Servy

+0

La compilazione di una lambda per ogni query che viene emessa causerà un carico elevato della CPU. Quanto tempo impiega la compilazione di una lambda? 1ms? – usr

Problemi correlati