我需要编写一个将在数据库表上执行关键字搜索的查询。代码目前看起来像这样(虽然有一组硬编码的关键字):
var keywords = new [] { "alpha", "bravo", "charlie" };
IQueryable<Story> stories = DataContext.Stories;
foreach( var keyword in keywords )
{
stories = from story in stories where story.Name.Contains ( keyword ) );
}
return stories;
ReSharper在foreach中为 keyword 抛出“访问修改后的闭包”警告。我理解错误,并在查看生成的SQL时确认问题:
SELECT [t0].[Id], [t0].[Name]
FROM [dbo].[Story] AS [t0]
WHERE (([t0].[Name] LIKE @p0))
AND (([t0].[Name] LIKE @p1))
AND (([t0].[Name] LIKE @p2))
-- @p0: Input NVarChar (Size = 9; Prec = 0; Scale = 0) [%charlie%]
-- @p1: Input NVarChar (Size = 9; Prec = 0; Scale = 0) [%charlie%]
-- @p2: Input NVarChar (Size = 9; Prec = 0; Scale = 0) [%charlie%]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.30729.1
因为关键字迭代器在循环期间发生了变化,所以我的SQL只包含对最后一个值(“charlie”)的引用。
我该怎么做才能避免这个问题?在应用每个新关键字where子句之前,我可能会将 stories queryable转换为列表,但这似乎效率低下。
解决
感谢所有答案。最终我遇到了两个不同的问题,这两个问题都得到了解决:
答案 0 :(得分:5)
将变量分配给foreach块范围内的临时变量,以便每次都获得一个新变量。
foreach( var keyword in keywords )
{
var kwd = keyword;
stories = from story in stories where story.Name.Contains ( kwd ) );
}
Eric Lippert有一个good article(或两个)解释了在闭包中包含循环变量的危险以及如何避免它。
答案 1 :(得分:1)
这是一个非常简单的方法:
var keywords = new [] { "alpha", "bravo", "charlie" };
IQueryable<Story> stories = DataContext.Stories;
foreach( var keyword in keywords )
{
string kw = keyword;
stories = from story in stories where story.Name.Contains ( kw ) );
}
return stories;
你也可以考虑
var keywords = new [] { "alpha", "bravo", "charlie" };
IQueryable<Story> stories = DataContext.Stories
.Where(story => keywords.All(kw => story.Name.Contains(kw));
答案 2 :(得分:1)
您需要制作关键字的本地副本:
foreach( var keyword in keywords )
{
var localKeyword = keyword;
stories = from story in stories where story.Name.Contains ( localKeyword ) );
}
答案 3 :(得分:0)
我刚解决了部分问题。使用关键字变量的本地范围副本可以轻松修复“访问已修改的闭包”,如下所示:
var keywords = new [] { "alpha", "bravo", "charlie" };
IQueryable<Story> stories = DataContext.Stories;
foreach( var keyword in keywords )
{
var innerKeyword = keyword;
stories = from story in stories where story.Name.Contains ( innerKeyword ) );
}
return stories;
不幸的是,添加多个where子句可能对我不起作用。每个LINQ表达式由AND分隔,因此只返回满足所有关键字的故事。我想要任何关键字的故事。
答案 4 :(得分:0)
没试过,但做过这样的工作吗?
from story in stories
where keywords.All(kw => story.Name.Contains(kw));
答案 5 :(得分:0)
编辑:这是不正确的,请参阅下面的评论。
如果您要查找与一个或多个关键字匹配的故事而不是所有关键字,正如您在后续答案中指出的那样,您应该使用Any运算符。我相信以下查询(未经测试)将起作用:
IQueryable<Story> matchingStories =
from story in stories
where keywords.Any(keyword => story.Name.Contains(keyword));