LinqToSql奇怪的行为

时间:2009-03-18 15:38:26

标签: c# linq linq-to-sql lambda

我有以下代码:


var tagToPosts = (from t2p in dataContext.TagToPosts
                                    join t in dataContext.Tags on t2p.TagId equals t.Id
                                    select new { t2p.Id, t.Name });
//IQueryable tag2postsToDelete;
foreach (Tag tag in tags)
{
    Debug.WriteLine(tag);
    tagToPosts = tagToPosts.Where(t => t.Name != tag.Name);
}
IQueryable tagToPostsToDelete = (from t2p in dataContext.TagToPosts
                                            join del in tagToPosts on t2p.Id equals del.Id
                                            select t2p);
dataContext.TagToPosts.DeleteAllOnSubmit(tagToPostsToDelete);

其中tagsList<Tag> - 构造函数创建的标记列表,因此它们没有id。 我运行代码将一个刹车点放在Debug上,然后等待几个周期。然后我将WatchToPosts.ToList()放在Watch窗口中以便执行查询。 在SQL事件探查器中,我可以看到以下查询:

exec sp_executesql N'SELECT [t0].[Id], [t1].[Name]
FROM [dbo].[tblTagToPost] AS [t0]
INNER JOIN [dbo].[tblTags] AS [t1] ON [t0].[TagId] = [t1].[Id]
WHERE ([t1].[Name]  @p0) AND ([t1].[Name]  @p1) AND ([t1].[Name]  @p2)',N'@p0 nvarchar(4),@p1 nvarchar(4),@p2 nvarchar(4)',@p0=N'tag3',@p1=N'tag3',@p2=N'tag3'

我们可以看到每个参数参数在一个循环中具有最后tag.Name的值。 您是否有任何关于如何获得这个问题的想法,并获得每个tyme添加Where新条件的周期?我可以看到IQueryable只在执行之前存储指向变量的指针。

2 个答案:

答案 0 :(得分:11)

将您的foreach更改为:

foreach (Tag tag in tags){
    var x = tag.Name;
    tagToPosts = tagToPosts.Where(t => t.Name != x);
}

这背后的原因是懒惰的评估和变量捕获。基本上,您在foreach语句中所做的是而不是过滤结果,因为它可能看起来像。您正在构建一个依赖于要执行的某些变量的表达式树。重要的是要注意实际变量是在表达式树中捕获的,而不是捕获时的值。由于每次都使用变量tag(并且它的范围是整个foreach,因此它不会在每次迭代结束时超出范围),它会被捕获用于表达式的每个部分最后一个值将用于所有的出现次数。解决方案是使用临时变量,该变量在foreach 中作为范围,因此它将在每次迭代时超出范围,并且在下一次迭代时,它将被视为新变量< /强>

答案 1 :(得分:1)

是的,Mehrdad的回答是正确的。当闭包捕获C#中的变量时(当你的“Where”lambda引用变量“tag”时会发生这种情况),编译器会应用一个关于如何进行捕获的非常精确的规则。如果捕获的变量在相同的范围内,由周围的{和}括号确定,则该变量的值将按原样捕获。如果它超出范围,则仅捕获对该变量的引用。在您的原始帖子中,“tag”变量已经在整个循环中循环到其最后一个值。但是如果你做了Mehrdad建议的修改,那么你在同一范围内捕获变量,因此变量的各个值将嵌入到闭包中,为你提供所需的结果。

顺便说一下,你可能会说,“是的,但”标签“变量属于同一范围。”但事实并非如此,因为在引擎盖下,编译器会将for-each变成这样的东西(这非常粗糙,只是为了显示括号中会发生什么):

{ 
   var iterator = GetTheIterator();
   { 
      while(iterator.MoveNext())
      {
          // Your loop code here
      }
   }
}

重点是,for-each的迭代器将始终位于循环代码所在范围之外的范围内。