2009-03-18 7 views
3

У меня есть следующий код:LinqToSql странное поведение


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); 

где tags является List<Tag> - список тегов, созданных конструктором, поэтому у них нет идентификатора. Я запускаю код, вставляя brakepoint в очередь с Debug и дожидаясь нескольких циклов. Затем я поместил tagToPosts.ToList() в окно «Часы» для выполнения запроса. В SQL Profiler можно увидеть следующий запрос:

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 в цикле. Есть ли у вас какие-либо идеи о том, как получить arout this и получить цикл, чтобы добавить Where с новым условием каждый tyme? Я вижу, что IQueryable сохраняет только указатель на переменную перед выполнением.

ответ

10

Изменить foreach на:

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

Причины этого является ленивой оценкой и переменная захвата. В основном, то, что вы делаете в операторе foreach, является не, отфильтровывая результаты, как может показаться. Вы строите дерево выражений, которое зависит от выполнения некоторых переменных. Важно отметить, что фактические переменные захватываются в деревьях выражений, а не их значения во время захвата. Так как переменная tag используется каждый раз (и ее область - это целое foreach, поэтому она не будет выходить за рамки в конце каждой итерации), она фиксируется для каждой части выражения, а последнее значение будет использоваться для все его вхождений. Решение состоит в том, чтобы использовать временную переменную, которая имеет область в foreach, поэтому она будет выходить за пределы области на каждой итерации, а следующая итерация будет считаться новой переменной.

1

Да, ответ Мехрдада правильный. Когда замыкание фиксирует переменную в C# (это то, что происходит, когда ваша «Где» лямбда относится к переменной «tag»), компилятор применяет довольно точное правило о том, как сделать захват. Если захваченная переменная находится в пределах одной и той же области действия, определяемой окружающими скобками {и}, тогда значение этой переменной будет записано как есть. Если он выходит за пределы области видимости, тогда будет записана только ссылка на эту переменную. В вашем исходном сообщении переменная «tag» уже прошла цикл по всему циклу до последнего значения. Но если вы сделаете модификацию, предложенную Мехрдадом, тогда вы будете захватывать переменную в той же области действия, поэтому индивидуальное значение переменной будет встроено в ваше закрытие, давая вам нужные вам результаты.

Кстати, вы можете сказать: «Да, но переменная« тег »находится в том же объеме». Но это не совсем, потому что под капотом, компилятор превращает для-каждый в чем-то вроде этого (это очень грубо, просто чтобы показать, что происходит со скобками):

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

точка, ваш итератор для каждый из них всегда будет находиться вне области, где находится ваш код цикла.

Смежные вопросы