LINQ to SQL и время жизни объекта, ссылки и значения - PullRequest
1 голос
/ 18 июля 2009

Я наткнулся на интересную ошибку с linq to sql. Посмотрите на приведенный ниже код, который свободно переводится из запроса LINQtoSQL от поисковой системы, которую я пишу.

Цель запроса - найти любые группы, в которых есть идентификаторы "Джо", "Джефф", "Джим" в последовательном порядке.

Обратите особое внимание на переменные с именами localKeyword и localInt. Если бы вы удалили объявления этих кажущихся бесполезными локальных переменных и заменили их теми, которые они проксируют, вы бы обнаружили, что запрос больше не работает.

Я все еще начинающий с linq to sql, но похоже, что он передает все местные жители в качестве ссылок. Это приводит к тому, что запрос имеет значение только локальных переменных при оценке запроса. В LINQ to SQL мой запрос в итоге выглядел как

SELECT * FROM INDEX ONE, INDEX TWO, INDEX THREE 
  WHERE ONE.ID = 'Jim' and TWO.ID = 'Jim' and 
    TWO.SEQUENCE = ONE.SEQUENCE + 2 and 
    THREE.ID = 'Jim' and 
    THREE.SEQUENCE = ONE.SEQUENCE + 2 and 
    ONE.GROUP == TWO.GROUP and ONE.GROUP == THREE.GROUP

Запрос, конечно, перефразирован. Что именно происходит, это ошибка? Я прошу, возможно, лучше понять, почему это происходит. Вы должны найти код компиляции в Visual Studio 2008.

using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;

namespace BreakLINQ
{
    class Program
    {
        public struct DataForTest
        {
            private int _sequence;
            private string _ID;
            private string _group;

            public int Sequence
            {
                get
                {
                    return _sequence;
                }
                set
                {
                    _sequence = value;
                }
            }
            public string ID
            {
                get
                {
                    return _ID;
                }
                set
                {
                    _ID = value;
                }
            }
            public string Group
            {
                get
                {
                    return _group;
                }
                set
                {
                    _group = value;
                }
            }
        }
        static void Main(string[] args)
        {
            List<DataForTest> elements = new List<DataForTest>
            {
                new DataForTest() { Sequence = 0, ID = "John", Group="Bored" },
                new DataForTest() { Sequence = 1, ID = "Joe", Group="Bored" },
                new DataForTest() { Sequence = 2, ID = "Jeff", Group="Bored" },
                new DataForTest() { Sequence = 3, ID = "Jim", Group="Bored" },
                new DataForTest() { Sequence = 1, ID = "Jim", Group="Happy" },
                new DataForTest() { Sequence = 2, ID = "Jack", Group="Happy" },
                new DataForTest() { Sequence = 3, ID = "Joe", Group="Happy" },
                new DataForTest() { Sequence = 1, ID = "John", Group="Sad" },
                new DataForTest() { Sequence = 2, ID = "Jeff", Group="Sad" },
                new DataForTest() { Sequence = 3, ID = "Jack", Group="Sad" }
            };

            string[] order = new string[] { "Joe", "Jeff", "Jim" };
            int sequenceID = 0;
            var query = from item in elements
                        select item;
            foreach (string keyword in order)
            {
                if (sequenceID == 0)
                {
                    string localKeyword = keyword;
                    query = from item in query
                            where item.ID == localKeyword
                            select item;
                }
                else
                {
                    string localKeyword = keyword;
                    int localSequence = sequenceID;
                    query = from item in query
                            where (from secondItem in elements
                                   where secondItem.Sequence == item.Sequence + localSequence &&
                                         secondItem.ID == localKeyword
                                   select secondItem.Group).Contains(item.Group)
                            select item;
                }
                sequenceID++;
            }
        }
    }
}

Значение запроса после завершения кода должно иметь значение {"Джо", "Скучно", 1}.

Ответы [ 4 ]

3 голосов
/ 18 июля 2009
3 голосов
/ 18 июля 2009

Причина, по которой это не работает без переменных-прокси, состоит в том, что переменные фиксируются выражениями в запросе LINQ. Без прокси каждая итерация цикла ссылается на одни и те же две переменные (ключевое слово, sequenceID), и когда запрос, наконец, оценивается и выполняется, значение, заменяемое для каждой из этих ссылок, идентично; а именно, какое бы значение не присутствовало в этих переменных, когда цикл заканчивается (когда вы хотите, чтобы мы оценили «запрос»).

Запрос ведет себя как ожидалось с прокси, потому что захваченные переменные однозначно объявляются на каждую итерацию цикла; последующие итерации не изменяют захваченные переменные, потому что они больше не находятся в области видимости. Прокси-переменные вовсе не бесполезны. Кроме того, это поведение является намеренным; дай мне посмотреть, смогу ли я найти хорошую справочную ссылку ...

2 голосов
/ 18 июля 2009

Это не ошибка, это «по замыслу».

То, что происходит внутри, заключается в том, что вы захватываете переменную итерации цикла for в лямбда-выражении. Это на самом деле используется в запросе, но под капотом это будет преобразовано в лямбда-выражение.

В цикле for существует только одна переменная итерации для всех циклов. Не один для каждой итерации цикла. Таким образом, каждый запрос захватывает одну и ту же переменную. При выполнении запрос будет выполняться против текущего или, в этом случае, последнего, значения, хранящегося в переменной итерации.

Причина, по которой ваш трюк с временными переменными работает, состоит в том, что по существу будет один экземпляр временной переменной для каждой итерации цикла. Таким образом, каждый запрос захватывает отдельное независимое значение.

Более краткий пример, демонстрирующий эту проблему:

var list = new List<Func<int>>();
foreach (var cur in Enumerable.Range(1,3)) {
  list.Add(() => cur);
}
foreach ( var lambda in list ) {
  Console.WriteLine(lambda());  // always prints 3
}
2 голосов
/ 18 июля 2009
var correctQuery = 
   from o in elements
   join tw in elements on o.Sequence equals tw.Sequence - 1
   join th in elements on tw.Sequence equals th.Sequence - 1
   where
       o.ID == "Joe" && tw.ID == "Jeff" && th.ID == "Jim" && o.Group == tw.Group &&
       th.Group == tw.Group
   select new {o.ID, o.Sequence, o.Group};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...