我遇到了一个有趣的 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}。
答案 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
}