当前位置:  开发笔记 > 编程语言 > 正文

使用IQueryable,foreach和multiple Where时LINQ to SQL bug(或非常奇怪的特性)

如何解决《使用IQueryable,foreach和multipleWhere时LINQtoSQLbug(或非常奇怪的特性)》经验,为你挑选了1个好方法。

我遇到了一个LINQ to SQL非常奇怪的场景.我想知道我做错了什么.但我认为这是一个真正的可能性,这是一个错误.

下面粘贴的代码不是我的真实代码.这是我使用Northwind数据库为这篇文章创建的简化版本.

一点背景:我有接受一个的方法IQueryableProduct和"过滤器对象"(我将在一分钟内描述).它应该运行一些"Where"扩展方法IQueryable,基于"过滤器对象",然后返回IQueryable.

所谓的"过滤器对象"是System.Collections.Generic.List这种结构的匿名类型:{ column = fieldEnum, id = int }

fieldEnum是Products表格的不同列的枚举,我可能希望用于过滤.

而不是进一步解释我的代码如何工作,如果你只是看看它会更容易.这很容易理解.

enum filterType { supplier = 1, category }
public IQueryable getIQueryableProducts()
{
    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循环的单个项的名称,它在迭代后意味着不同的东西.



1> Brian..:

我只看了你的问题,但我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变量上,在每个循环内部重新声明,并且您的代码将按预期运行.

推荐阅读
殉情放开那只小兔子
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有