LINQ является составным, но чтобы сделать это без использования UNION, вам нужно накатить свой собственный Expression
. По сути, мы (предположительно) хотим создать TSQL в форме:
SELECT *
FROM [table]
WHERE (Name = @name1 AND Amount <= @amount1)
OR (Name = @name2 AND Amount <= @amount2)
OR (Name = @name3 AND Amount <= @amount3)
...
где пары имя / количество определяются во время выполнения. Есть простой способ выразить это в LINQ; если бы это было «И» каждый раз, мы могли бы использовать .Where(...)
повторно. Union
- кандидат, но я видел, что у повторяющихся людей есть проблемы с этим. То, что мы хотим сделать, это эмулировать нас писать запрос LINQ, например:
var qry = from i in db.Ingredients
where ( (i.Name == name1 && i.Amount <= amount1)
|| (i.Name == name2 && i.Amount <= amount2)
... )
select i;
Это делается путем создания Expression
, используя Expression.OrElse
для объединения каждого из них, поэтому нам нужно будет перебирать наши пары имя / количество, делая богаче Expression
.
Написание Expression
кода от руки - это черное искусство, но у меня есть очень похожий пример у меня в рукаве (из презентации, которую я даю); он использует несколько пользовательских методов расширения; использование через:
IQueryable query = db.Ingredients.WhereTrueForAny(
localIngredient => dbIngredient =>
dbIngredient.Name == localIngredient.Name
&& dbIngredient.Amount <= localIngredient.Amount
, args);
, где args
- ваш набор тестовых ингредиентов. Это делается следующим образом: для каждого localIngredient
в args
(наш локальный массив тестовых ингредиентов) он запрашивает у нас Expression
(для этого localIngredient
), который является тестом для применения в базе данных. Затем он объединяет их (в свою очередь) с Expression.OrElse
:
public static IQueryable<TSource> WhereTrueForAny<TSource, TValue>(
this IQueryable<TSource> source,
Func<TValue, Expression<Func<TSource, bool>>> selector,
params TValue[] values)
{
return source.Where(BuildTrueForAny(selector, values));
}
public static Expression<Func<TSource, bool>> BuildTrueForAny<TSource, TValue>(
Func<TValue, Expression<Func<TSource, bool>>> selector,
params TValue[] values)
{
if (selector == null) throw new ArgumentNullException("selector");
if (values == null) throw new ArgumentNullException("values");
// if there are no filters, return nothing
if (values.Length == 0) return x => false;
// if there is 1 filter, use it directly
if (values.Length == 1) return selector(values[0]);
var param = Expression.Parameter(typeof(TSource), "x");
// start with the first filter
Expression body = Expression.Invoke(selector(values[0]), param);
for (int i = 1; i < values.Length; i++)
{ // for 2nd, 3rd, etc - use OrElse for that filter
body = Expression.OrElse(body,
Expression.Invoke(selector(values[i]), param));
}
return Expression.Lambda<Func<TSource, bool>>(body, param);
}