【问题标题】:LINQ to SQL and object lifetimes, references vs. valuesLINQ to SQL 和对象生命周期、引用与值
【发布时间】:2009-07-18 04:14:54
【问题描述】:

我在 linq to 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}。

【问题讨论】:

  • 作为一个挑剔的人,您使用 System.Linq;两次。
  • 我在您的代码中看不到任何 LINQ to SQL 内容。那只是对象的 LINQ。顺便说一句:stackoverflow.com/questions/1095707/…

标签: c# linq linq-to-sql


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    【讨论】:

      【解决方案3】:

      这不是错误,而是“设计使然”。

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

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

      【讨论】:

        【解决方案4】:
        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};
        

        【讨论】:

        • 对于搜索引擎,您需要一种方法来处理任意元素数组,例如 new string[] { "John", "joe", "Jeff", "Jim" };
        • 然而,我很惊讶这竟然变得如此干净:)
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多