LinqToSql странное поведение - PullRequest
3 голосов
/ 18 марта 2009

У меня есть следующий код:


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

Ответы [ 2 ]

11 голосов
/ 18 марта 2009

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

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

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

1 голос
/ 19 марта 2009

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

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

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

Дело в том, что ваш итератор for-each всегда будет находиться в области видимости, отличной от той, в которой находится код вашего цикла.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...