我遇到了一个LINQ to SQL非常奇怪的场景.我想知道我做错了什么.但我认为这是一个真正的可能性,这是一个错误.
下面粘贴的代码不是我的真实代码.这是我使用Northwind数据库为这篇文章创建的简化版本.
一点背景:我有接受一个的方法IQueryable
的Product
和"过滤器对象"(我将在一分钟内描述).它应该运行一些"Where"扩展方法IQueryable
,基于"过滤器对象",然后返回IQueryable
.
所谓的"过滤器对象"是System.Collections.Generic.List
这种结构的匿名类型:{ column = fieldEnum, id = int }
fieldEnum是Products
表格的不同列的枚举,我可能希望用于过滤.
而不是进一步解释我的代码如何工作,如果你只是看看它会更容易.这很容易理解.
enum filterType { supplier = 1, category } public IQueryablegetIQueryableProducts() { NorthwindDataClassesDataContext db = new NorthwindDataClassesDataContext(); IQueryable query = db.Products.AsQueryable(); //this section is just for the example. It creates a Generic List of an Anonymous Type //with two objects. In real life I get the same kind of collection, but it isn't hard coded like here var filter1 = new { column = filterType.supplier, id = 7 }; var filter2 = new { column = filterType.category, id = 3 }; var filterList = (new[] { filter1 }).ToList(); filterList.Add(filter2); foreach(var oFilter in filterList) { switch (oFilter.column) { case filterType.supplier: query = query.Where(p => p.SupplierID == oFilter.id); break; case filterType.category: query = query.Where(p => p.CategoryID == oFilter.id); break; default: break; } } return query; }
所以这是一个例子.假设List包含这个匿名类型的两个项目,{ column = fieldEnum.Supplier, id = 7 }
和{ column = fieldEnum.Category, id = 3}
.
运行上面的代码后,IQueryable
对象的基础SQL查询应包含:
WHERE SupplierID = 7 AND CategoryID = 3
但实际上,在代码运行后,执行的SQL就是
WHERE SupplierID = 3 AND CategoryID = 3
我尝试将其定义query
为属性并在setter上设置断点,以为我可以捕捉到它不应该在什么时候改变它.但一切都应该没问题.所以我只是在每个命令之后检查了底层SQL.我意识到第一次Where
运行正常,并且query
保持良好(意味着SupplierID = 7
)直到foreach
循环第二次运行之后.在oFilter
成为第二个匿名类型项而不是第一个之后,"查询"SQL将更改为Supplier = 3
.因此,必须在幕后发生的事情是Supplier
,LINQ to SQL记住供应商应该相等,而不仅仅是记住它应该等于7 oFilter.id
.但它oFilter
是一个foreach
循环的单个项的名称,它在迭代后意味着不同的东西.
我只看了你的问题,但我90%肯定你应该阅读On lambdas的第一部分,捕获和可变性(其中包括5个类似的SO问题的链接),所有这些都将变得清晰.
它的基本要点是oFilter
示例中的变量已通过引用而不是值通过闭包捕获.这意味着一旦循环完成迭代,变量的引用将是最后一个,因此在lambda执行时评估的值也是最后一个.
解决方法是在foreach
循环中插入一个新变量,其范围只是迭代而不是整个循环:
foreach(var oFilter in filterList) { var filter = oFilter; // add this switch (oFilter.column) // this doesn't have to change, but can for consistency { case filterType.supplier: query = query.Where(p => p.SupplierID == filter.id); // use `filter` here break;
现在每个闭包都在一个不同的 filter
变量上,在每个循环内部重新声明,并且您的代码将按预期运行.