Entity framework a IN klauzule
V konferenci o .net na builder.cz se objevil dotaz, jak sestavit SQL dotaz pomocí Entity frameworku, který by vygeneroval na výstupu omezující podmínku IN.
Jelikož v Linq to SQL je toto poměrně triviální řešení a je možné použít extenzní metodu Contains, neváhal jsem a autorovi potvrdil, že obdobně to bude i v případě Entity frameworku. Jenže pak ve mě začal hlodat červík nedůvěřivosti, vždyť to autor dotazu měl správně, tak proč to nejde. Až jsem přišel na to, že v EFv1 nelze použít Contains metodu tak, aby se vygenerovala IN klauzule.
Samozřejmě existuje řešení, kdy je možné provést celý SQL dotaz a až následně v paměti aplikovat spojení, které zajistí omezující podmínku. Je to však řešení nepříliš praktické.
Zkusil jsem tedy chvilku bádat a tady je řešení. Jedná se o to, že klauzule IN je možné reprezentovat také jako spojení jednotlivých hodnot operátorem OR. Pro lepší možnost použití je pak vytvořena extenzní metoda s názvem In a přebírající dva parametry.
public static class EFExtensions { private static Expression<Func<TEntity, bool>> GetIn<TEntity, TValue>( Expression<Func<TEntity, TValue>> propertySelector, IEnumerable<TValue> values) { var property = propertySelector.Parameters.Single(); if ((values == null) || (!values.Any())) return e => false; var parts = values.Select(value => Expression.Equal( propertySelector.Body, Expression.Constant(value, typeof(TValue)))); var body = parts.Aggregate(Expression.Or); return Expression.Lambda<Func<TEntity, bool>>(body, property); } public static IQueryable<TE> In<TE, TV>(this IQueryable<TE> source, Expression<Func<TE, TV>> propertySelector, params TV[] values) { return source.Where(GetIn(propertySelector, values)); } public static IQueryable<TE> In<TE, TV>(this IQueryable<TE> source, Expression<Func<TE, TV>> propertySelector, IEnumerable<TV> values) { return source.Where(GetIn(propertySelector, values)); } }
Použití této extenzní metody je pak velice jednoduché a demonstruje ji následující případ:
var ids = new int[] {1, 2, 3}; var data = db.TestTable.In(e => e.IntValues, ids).OrderBy(e => e.StringValues);