【问题标题】:Linq multiple where queriesLinq 多个 where 查询
【发布时间】:2012-03-12 18:51:33
【问题描述】:

我在构建相当庞大的 linq 查询时遇到问题。基本上我有一种情况,我需要在循环中执行子查询来过滤从数据库返回的匹配数。示例代码在下面的循环中:

        foreach (Guid parent in parentAttributes)
        {
            var subQuery = from sc in db.tSearchIndexes
                           join a in db.tAttributes on sc.AttributeGUID equals a.GUID
                           join pc in db.tPeopleIndexes on a.GUID equals pc.AttributeGUID
                           where a.RelatedGUID == parent && userId == pc.CPSGUID                             
                           select sc.CPSGUID;

            query = query.Where(x => subQuery.Contains(x.Id));
         }

当我随后在查询变量上调用 ToList() 时,似乎只执行了一个子查询,并且留下了一堆我不需要的数据。但是这种方法有效:

       IList<Guid> temp = query.Select(x => x.Id).ToList();

        foreach (Guid parent in parentAttributes)
        {
            var subQuery = from sc in db.tSearchIndexes
                           join a in db.tAttributes on sc.AttributeGUID equals a.GUID
                           join pc in db.tPeopleIndexes on a.GUID equals pc.AttributeGUID
                           where a.RelatedGUID == parent && userId == pc.CPSGUID                             
                           select sc.CPSGUID;

            temp = temp.Intersect(subQuery).ToList();
        }

        query = query.Where(x => temp.Contains(x.Id));

不幸的是,这种方法很糟糕,因为它会导致对远程数据库的多次查询,而如果我可以让它工作,那么最初的方法只会导致一次命中。有什么想法吗?

【问题讨论】:

    标签: c# .net linq entity-framework-4


    【解决方案1】:

    我认为您遇到了在用于过滤的 lambda 表达式中捕获循环变量的特殊情况。也称为访问修改的闭包错误。

    试试这个:

       foreach (Guid parentLoop in parentAttributes)
        {
            var parent = parentLoop;
            var subQuery = from sc in db.tSearchIndexes
                           join a in db.tAttributes on sc.AttributeGUID equals a.GUID
                           join pc in db.tPeopleIndexes on a.GUID equals pc.AttributeGUID
                           where a.RelatedGUID == parent && userId == pc.CPSGUID                             
                           select sc.CPSGUID;
    
            query = query.Where(x => subQuery.Contains(x.Id));
         }
    

    问题是在闭包中捕获parent 变量(LINQ 语法被转换为),这导致所有subQueryes 以相同的父ID 运行。

    编译器会生成一个类来保存委托和委托访问的局部变量。编译器为每个循环重复使用该类的相同实例;因此,一旦查询执行,所有Wheres 都会以相同的parent Guid 执行,即最后一个执行。

    在循环范围内声明parent 会导致编译器实质上制作具有正确值的变量的副本,以被捕获。

    一开始可能有点难以掌握,所以如果这是第一次遇到这种情况;我推荐这两篇文章作为背景和详尽的解释:

    【讨论】:

    • +1 如果您想更多地了解这个概念,请查看 Skeet 的答案和他的参考文章。 stackoverflow.com/questions/271440/…
    • @DMoses 谢谢,我添加了一些链接,完全承认我无法像 Lippert 和 Skeet 先生那样优雅和准确地解释这一点:-)
    • 谢谢你。像魅力一样工作。先生,您是圣人和学者。
    • @DMoses 我很想知道子查询有多少次被具体化我认为每次包含都被称为它重新实现列表我会更高兴看到子查询作为一个列表而不是 IEnumerable
    【解决方案2】:

    也许是这样?

    var subQuery = from sc in db.tSearchIndexes
                   join a in db.tAttributes on sc.AttributeGUID equals a.GUID
                   join pc in db.tPeopleIndexes on a.GUID equals pc.AttributeGUID
                   where parentAttributes.Contains(a.RelatedGUID) && userId == pc.CPSGUID                             
                   select sc.CPSGUID;
    

    【讨论】:

    • 感谢 k06a 的回复,但上面的查询正在尝试(但未成功)做一些与您提出的查询略有不同的事情。本质上,当您按 any 进行过滤时,我会按所有 parentAttributes 进行过滤,因此这将返回更大的结果集。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多