成分类:
class Ingredient
{
public String Name { get; set; }
public Double Amount { get; set; }
}
成分列表:
var ingredientsList = new List<Ingredient>();
我的“成分”表的数据库布局:
[Ingredients] (
[IngredientsID] [int] IDENTITY(1,1) NOT NULL,
[RecipeID] [int] NOT NULL,
[IngredientsName] [nvarchar](512) NOT NULL,
[IngredientsAmount] [float] NOT NULL
)
我可以在我的“Ingredients”表中查询我的ingredientsList
,做一个类似这样的where子句(伪代码警报!):
SELECT * FROM Ingredients WHERE
IngredientsName = ["Name" property on entities in my ingredientsList] AND
IngredientsAmount <= ["Amount" property on entities in my ingredientsList]
我当然希望使用LINQ完成此操作,而不是使用动态生成的SQL查询。
答案 0 :(得分:7)
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中有简单的措辞方式;如果每次都是“AND”,我们可以反复使用.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);
}
答案 1 :(得分:3)
在LINQ 2 SQL查询中使用本地集合的唯一程度是Contains()
函数,它基本上是对SQL in
子句的转换。例如......
var ingredientsList = new List<Ingredient>();
... add your ingredients
var myQuery = (from ingredient in context.Ingredients where ingredientsList.Select(i => i.Name).Contains(ingredient.Name) select ingredient);
这将生成相当于“...where ingredients.Name in (...)
”
不幸的是,我不认为这对你有用,因为你必须原子地加入每一列。
另外,使用LINQ 2 SQL 是动态生成的SQL查询。
当然,您可以在客户端进行加入,但这需要恢复整个Ingredients
表,这可能性能过高,而且绝对不好实践。
答案 2 :(得分:1)
我认为你要么必须使用多个查询,要么将你的成分列表复制到临时表中并以这种方式进行数据库查询。
我的意思是,你可能有一个SQL语句:
SELECT * FROM Ingredients WHERE
(IngredientsName = 'Flour' AND IngredientsAmount < 10) OR
(IngredientsName = 'Water' AND IngredientsAmount <= 5) OR
(IngredientsName = 'Eggs' AND IngredientsAmount <= 20)
但很快就会变丑。
我个人怀疑临时表解决方案将是最好的 - 但我不知道LINQ to SQL是否对它们有很多支持。
答案 3 :(得分:0)
List<string> ingredientNames = ingredientsList
.Select( i => i.Name).ToList();
Dictionary<string, Double> ingredientValues = ingredientsList
.ToDictionary(i => i.Name, i => i.Amount);
//database hit
List<Ingredient> queryResults = db.Ingredients
.Where(i => ingredientNames.Contains(i.Name))
.ToList();
//continue filtering locally - TODO: handle case-sensitivity
List<Ingredient> filteredResults = queryResults
.Where(i => i.Amount <= ingredientValues[i.Name])
.ToList();
答案 4 :(得分:0)
我在LINQPad中搞乱了这个解决方案,如果有的话,你可以看到转储输出。不确定它是否是你需要的,但从我的理解是它。我在我的Users
表中使用它,但您可以将其替换为Ingredients,将“UserList”替换为“IngredientList”,将“Username”替换为“Ingredient Name”。您可以在if语句中添加更多“OR”过滤表达式。设置ID非常重要。
最后请注意,“Dump()
”方法特定于LINQPad,不是必需的。
var userList = new List<User>();
userList.Add(new User() { ID = 1, Username = "goneale" });
userList.Add(new User() { ID = 2, Username = "Test" });
List<int> IDs = new List<int>();
// vv ingredients from db context
IQueryable<User> users = Users;
foreach(var user in userList)
{
if (users.Any(x => x.Username == user.Username))
IDs.Add(user.ID);
}
IDs.Dump();
userList.Dump();
users.Dump();
users = users.Where(x => IDs.Contains(x.ID));
users.Dump();
答案 5 :(得分:0)
我正在使用Union合并每个子查询的结果:
public static IQueryable<TSource> WhereTrueForAny<TSource, TValue>(this IQueryable<TSource> source, Func<TValue, Expression<Func<TSource, bool>>> selector, params TValue[] values)
{
// code is based on Marc Gravells answer
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 source.Where(x => false);
// if there is 1 filter, use it directly
if (values.Length == 1) return source.Where(selector(values[0]));
var lockingUpArray = values;
var p = lockingUpArray.First();
IQueryable<TSource> query = source.Where(selector(p));
foreach (var param in lockingUpArray.Skip(1))
{
query = query.Union(source.Where(selector(param)));
}
return query;
}