LINQ to SQL和对象生存期,引用与值

时间:2009-07-18 04:14:54

标签: c# linq linq-to-sql

我遇到了一个有趣的 bug 和linq到sql。看一下下面的代码,该代码是从我正在编写的搜索引擎的LINQtoSQL查询中松散翻译的。

查询的目标是找到连续顺序具有ID“Joe”,“Jeff”,“Jim”的任何组。

请注意名为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++;
            }
        }
    }
}

代码完成后查询的值应为{“Joe”,“Bored”,1}。

4 个答案:

答案 0 :(得分:3)

没有“代理”变量而失败的原因是LINQ查询中的表达式捕获变量。没有代理,循环的每次迭代都引用相同的两个变量(keyword,sequenceID),并且当最终评估和执行查询时,替换每个引用的值是相同的;即,当循环终止时,这些变量中存在的任何值(当您希望我们评估'查询'时)。

查询的行为符合预期的 代理,因为捕获的变量是每次迭代循环唯一声明的;后续迭代不会修改捕获的变量,因为它们不再在范围内。代理变量根本没用。此外,这种行为是设计的;让我看看我是否能找到一个很好的参考链接...

答案 1 :(得分:3)

答案 2 :(得分:2)

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

答案 3 :(得分:2)

这不是一个错误,它是“按设计”。

这里发生的事情是你正在捕获lambda表达式中for循环的迭代变量。它实际上是在查询中使用但在引擎盖下它将被转换为lambda表达式。

在for循环中,所有循环只有一个迭代变量。循环的每次迭代都不是一个。因此每个查询都捕获相同的变量。执行时,查询将针对存储在迭代变量中的当前值(或者在本例中为last)运行。

你的临时变量技巧起作用的原因是循环的每次迭代都会有一个临时变量的实例。因此,每个查询都捕获一个不同的独立值。

更简洁的示例演示此问题如下

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
}