2

我需要编写一个对数据库表执行关键字搜索的查询。代码目前看起来像这样(尽管有一组硬编码的关键字):

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 内的关键字引发“访问修改后的闭包”警告。我理解错误,并在查看生成的 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 子句之前,我可能会将可查询的故事转换为列表,但这似乎效率低下。

解决了

感谢所有的答案。最终我遇到了两个不同的问题,这两个问题都已解决:

  1. 在 foreach() 循环中使用局部变量来避免“访问修改后的闭包”问题。
  2. 使用 LINQKit 中的 PredicateBuilder 动态组装 OR 子句列表以允许“任何”样式的关键字搜索。
4

6 回答 6

5

将变量分配给 foreach 块范围内的临时变量,以便每次都获得一个新变量。

foreach( var keyword in keywords )
{
    var kwd = keyword;
    stories = from story in stories where story.Name.Contains ( kwd ) );
}

Eric Lippert 有一篇(或两篇)很好的文章解释了在闭包中包含循环变量的危险以及如何避免它。

于 2009-12-21T16:22:57.030 回答
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));
于 2009-12-21T16:23:00.693 回答
1

您需要制作关键字的本地副本:

foreach( var keyword in keywords )
{
    var localKeyword = keyword;
    stories = from story in stories where story.Name.Contains ( localKeyword ) );
}
于 2009-12-21T16:24:02.283 回答
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 where 表达式由 AND 分隔,因此只返回满足所有关键字的故事。我想要任何关键字的故事。

于 2009-12-21T16:28:35.710 回答
0

没有尝试过,但是这样的工作吗?

from story in stories
where keywords.All(kw => story.Name.Contains(kw));
于 2009-12-21T16:30:44.877 回答
0

编辑:这是不正确的,请参阅下面的评论。

如果您想查找匹配一个或多个关键字而不是所有关键字的故事,正如您在后续答案中指出的那样,您应该使用 Any 运算符。我相信以下查询(未经测试)将起作用:

IQueryable<Story> matchingStories = 
    from story in stories
    where keywords.Any(keyword => story.Name.Contains(keyword));
于 2009-12-21T16:40:42.407 回答