2013-09-27 14 views
6

Sto provando a scrivere un linq al metodo di estensione dell'entità che accetta un Func per selezionare un ID di proprietà e confrontarlo con un elenco di id.Come utilizzare un Func in un'espressione con Linq su Entity Framework?

Classi

public class A 
{ 
    public int AId { get; set; } 
} 

public class B 
{ 
    public int BId { get; set; } 
} 

Metodo di estensione

public static IQueryable<T> WithId<T>(this IQueryable<T> entities, 
    Func<T, int> selector, IList<int> ids) 
    { 
     Expression<Func<T, bool>> expression = x => ids.Contains(selector(x)); 
     return entities.Where(expression); // error here (when evaluated) 
    } 

metodo di chiamata

var ids = new List<int> { 1, 2, 3 }; 
DbContext.EntityAs.WithId(e => e.AId, ids); 
DbContext.EntityBs.WithId(e => e.BId, ids); 

Il problema che sto sperimentare è che sta cercando di richiamare la funzione che non è consentita in Entity Framework.

Come è possibile utilizzare un selettore di proprietà (Func) per valutare la query?

+0

L'ambito del codice che è possibile richiamare in una query EF è limitato dal fatto che deve ancora essere tradotto in SQL. Nel tuo caso EF non sa come tradurre un IList automaticamente. –

+0

Non sono sicuro che tu abbia ragione con quello. DbContext.EntityAs.Where (e => ids.Contains (e.Id)) è tradotto correttamente da EF. Sto solo cercando di fare una funzione riutilizzabile, così posso definire su quale proprietà selezionare. – David

+0

Poiché EF sa come fare 'selezionare x dove x in (1,2,3)' nel caso di enumerable o 'select x dove x in (select y)' nel caso di un'altra relazione di entità. Nel tuo caso EF dovrebbe compilare qualcosa come 'select x dove x in (seleziona y dove F (y) in (F (1), F (2), ...))'. Mentre è possibile farlo manualmente, EF non supporta il caso * ancora * –

risposta

15

È necessario passare uno Expression<Func<T, int>> anziché uno Func<T, int> e creare autonomamente l'espressione completa. Ciò farà il trucco:

public static IQueryable<T> WithId<T>(this IQueryable<T> entities, 
    Expression<Func<T, int>> propertySelector, ICollection<int> ids) 
{ 
    var property = 
     (PropertyInfo)((MemberExpression)propertySelector.Body).Member; 

    ParameterExpression parameter = Expression.Parameter(typeof(T)); 

    var expression = Expression.Lambda<Func<T, bool>>(
     Expression.Call(
      Expression.Constant(ids), 
      typeof(ICollection<int>).GetMethod("Contains"), 
      Expression.Property(parameter, property)), 
     parameter); 

    return entities.Where(expression); 
} 

Quando si tenta di mantenere il codice DRY quando si lavora con il vostro O/RM, ti capiterà spesso di avere a giocherellare con alberi di espressione. Ecco another fun example.

+0

Fantastico. Stavo sperimentando come costruire l'albero delle espressioni da http://blogs.msdn.com/b/miah/archive/2009/02/06/dynamic-expression-trees.aspx e http://stackoverflow.com/questions/ 820896/listobject-contains-expression-tree ma non è riuscito a capire come creare la collezione/elenco. Grazie! – David

+3

@DavidLiddle: Ti farò un piccolo segreto: scrivo semplicemente la query LINQ, compilo e apro Reflector per vedere cosa genera il compilatore C#. Puoi anche vedere queste informazioni nel debugger, ma Reflector è molto più facile. – Steven

+0

Puoi dare un esempio di "scrivi semplicemente la query LINQ". Usando ILSpy vedo solo la query LINQ esatta che ho scritto! – David