2014-11-04 10 views
7

powiedzmy Mam funkcję tak:Czy istnieje sposób wstawiania funkcji zewnętrznych do zapytania EF Linq?

var filterValue = GetCurrentFilter(state); 

a potem zapytań EF:

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

to działa, ale jak tylko próbuję inline że:

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

Nie dzieje się tak, ponieważ EF Linq próbował przetworzyć GetCurrentFilter w drzewie wyrażeń i nie może tego zrobić. To wszystko jest zrozumiałe.

Moje pytanie brzmi: czy istnieje sposób, aby EF Linq wiedział, że potrzebuje wykonać funkcję GetCurrentFilter, gdy buduje drzewo i używa jego wyniku w drzewie?

Coś

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

Od GetCurrentFilter nie posiada parametry, które jest częścią zapytania powinno być technicznie możliwe do zrobienia, że ​​jeśli EF LINQ obsługują ją, że jest. Podejrzewam, że po prostu brakuje mi prawidłowej składni.

Odpowiedz

7

Wykonaj GetCurrentFilter a (tylko do odczytu) zamiast metody. EF będzie porównywać właściwości z ich wartościami, zamiast próbować tłumaczyć je na SQL, w przeciwieństwie do metod.


Jedyna inna droga, że ​​trzeba to przemierzać całą drzewa wyrażenie, szukaj wykorzystania metodę ResultOf, oceny jego parametrów do wartości, a następnie wbudować tę wartość na którym połączenie ResultOf kiedyś, rebuiding zapytanie wokół tej wartości.

Aby to zadziałało, oznacza to, że musisz nie tylko zawijać kod, który chcesz umieścić w linii, w rozmowie z numerem EfUtil.ResultOf, ale także wywoływać metodę w samym zapytaniu, aby wymusić jej powrót i jej ocenę. :

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

pozwoliłoby to napisać:

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

byłoby wtedy ocenić GetCurrentFilter(state) po stronie klienta i inline wynik jako stała w zapytaniu.

W nieco prostszym testu, możemy napisać następujący:

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

Console.WriteLine(query.ToString()); 

I to wydrukować.

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

Dokładnie tego chcemy.

Należy pamiętać, że użycie parametru lambda (x w tych przykładach) nie może być używane w dowolnym miejscu połączenia pod numerem EfUtil.ResultOf, ponieważ kod nie będzie działał i nie będzie mógł zostać uruchomiony (chociaż możemy wygenerować lepszy komunikat o błędzie, jeśli dbamy o to wystarczająco).

+0

Ta metoda może mieć kilka parametrów, z których żadne nie są używane w zapytaniu. Nie ma możliwości zdefiniowania sparametryzowanych właściwości w języku C#. Ale masz rację, ponieważ nie wyjaśniłem tego w moim pytaniu. Pozwól mi to zaktualizować. –

+0

@ zespri Ten problem jest rozwiązywalny, chociaż ma dodatkowe ograniczenie. – Servy

+0

Kompilowanie lambda dla każdego wydanego zapytania spowoduje duże obciążenie procesora. Ile czasu zajmuje kompilacja lambda? 1ms? – usr

Powiązane problemy