LINQ is composable, but to do this without using UNION you'd have to roll your own Expression. Basically, we (presumably) want to create TSQL of the form:
SELECT *
FROM [table]
WHERE (Name = @name1 AND Amount <= @amount1)
OR (Name = @name2 AND Amount <= @amount2)
OR (Name = @name3 AND Amount <= @amount3)
...
where the name/amount pairs are determined at runtime. There is easy way of phrasing that in LINQ; if it was "AND" each time, we could use .Where(...) repeatedly. Union is a candidate, but I've seen repeated people have problems with that. What we want to do is emulate us writing a LINQ query like:
var qry = from i in db.Ingredients
where ( (i.Name == name1 && i.Amount <= amount1)
|| (i.Name == name2 && i.Amount <= amount2)
... )
select i;
This is done by crafting an Expression, using Expression.OrElse to combine each - so we will need to iterate over our name/amount pairs, making a richer Expression.
Writing Expression code by hand is a bit of a black art, but I have a very similar example up my sleeve (from a presentation I give); it uses some custom extension methods; usage via:
IQueryable query = db.Ingredients.WhereTrueForAny(
localIngredient => dbIngredient =>
dbIngredient.Name == localIngredient.Name
&& dbIngredient.Amount <= localIngredient.Amount
, args);
where args is your array of test ingredients. What this does is: for each localIngredient in args (our local array of test ingredients), it asks us to provide an Expression (for that localIngredient) that is the test to apply at the database. It then combines these (in turn) with 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);
}