【问题标题】:LINQ to Entities Any() and Contains() slow with small listLINQ to Entities Any() 和 Contains() 速度慢,列表少
【发布时间】:2014-06-07 09:13:25
【问题描述】:

我正在使用 EF 6 从数据库中获取产品。产品类别映射为产品的导航属性,数据来自 ProductCategory 数据透视表。类别像树一样工作(即每个类别都可以有子类别),但只有最具体的产品-子类别关系存储在数据透视表中。例如,假设有这样的类别路径:

电子 > 音频 > 放大器 > 集成放大器。

作为集成放大器的产品在数据透视表中具有其产品 ID 和集成放大器类别 ID 的记录。

我需要按类别过滤,但即使按父类别过滤,产品也应该显示出来,例如集成放大器应显示在放大器列表中。所以首先我制作了一个相关类别 ID 的列表。 (这涉及对类别表的单独查询,但不会花费很长时间。)如果类别过滤器是放大器,则列表是放大器的 ID 和集成放大器的 ID。

问题是当我包含过滤器时,产品查询需要 10-20 倍的时间:

List<int> currentCategoryIdAndChildren = BuildCategoryIdList(currentCategoryId);

using (var db = new myContext())
{
    var products = db.Products
        .Select(p => new Product_PL
        {
            id = p.ID,
            name = p.Name,
            description = p.Description,
            categories = p.Categories
                        .Select(c => new Category_PL
                        {
                            categoryid = c.ID,
                        }),
        });

    // Filter by category
    products = products.Where(pl => pl.categories.Any(c => currentCategoryIdAndChildren.Contains(c.categoryid)));

    // Other filters, sorting, and paging here

    rptProducts.DataSource = products.ToList(); // Database call is made here
    rptProducts.DataBind();
}

我希望 Any() 和 Contains() 的组合会因大量记录而迅速放慢速度,但我正在处理产品中的 22 项、pl.categories 中的 1-3 项和 1-5 currentCategoryIdAndChildren 中的项目。令我惊讶的是,由于记录如此之少,它的速度却慢了一个数量级。按照这个速度,我最好在客户端过滤它,即使这意味着带回很多不必要的记录。

我有什么遗漏吗?还有其他方法吗?

更新:Express Profiler 报告说数据库查询本身只需要 3 毫秒,所以我猜测性能与 Entity Framework 的工作方式有关。当然,第一次运行 LINQ 时它是最慢的(我知道它需要编译查询),但在随后的调用中它仍然相对较慢。

【问题讨论】:

  • 您应该在选择的同时进行过滤。当您在第二行调用“products.Where”时,它首先必须枚举 Products。将 Where 子句移到第一个 linq 调用的末尾
  • @DLeh 你确定这是正确的吗?第一个预测实际上并没有实现,所以我希望 SQL 代码生成足够智能。一种检查方法是记录生成的 SQL。
  • 我不是很肯定,但这是我会尝试的第一件事。
  • @Dleh 产品在实际访问之前不会被枚举(例如使用 .ToList() 或 foreach)。我已经验证了 SQL 调用直到那时才进行。实际上,我在上面的代码之后还有其他几个条件过滤器以及分页,最后会产生一个巨大的过滤 SQL 调用。但我会尝试移动 Where 只是为了好玩。
  • 整个操作需要多长时间(不仅仅是 SQL 查询)?如果您只调用 ToList 而不将其分配为 DataSource 有什么区别?

标签: c# linq entity-framework


【解决方案1】:

我尝试了很多不同的方法,最终找到了解决方案。

我相信主要的减速发生在 EF 将 Contains() 转换为 SQL 查询时。然而,最值得注意的是它似乎没有缓存查询。据我所知,这是因为类别 ID 列表 (currentCategoryIdAndChildren) 是在 EF 之外生成的,因此它假定每次都会不同。

我能够通过使用 LINQKit 中的 PredicateBuilder 来加快速度。这让我可以更明确地创建逻辑:

var IsInCategory = PredicateBuilder.False<Product_PL>();

foreach (int categoryID in currentCategoryIdAndChildren)
{ IsInCategory = IsInCategory.Or(pl => pl.categories.Any(c => categoryID == c.categoryid)); }

products = products.Where(IsInCategory);

这让我的初始查询性能更好一些,而后续查询的性能更好。

【讨论】:

    【解决方案2】:

    尝试先过滤掉产品,然后再形成模型(Product_PL 和 Category_PL):

    var filteredProducts = db.Products.Where(p => p.Categories.Any(c => currentCategoryIdAndChildren.Contains(c.ID)))
        .Select(p => new Product_PL
        {
            id = p.ID,
            name = p.Name,
            description = p.Description,
            categories = p.Categories
                        .Select(c => new Category_PL
                        {
                            categoryid = c.ID,
                        }),
        });
    

    【讨论】:

    • 无论我将 Where 放在哪里,我都能获得相同的性能,尽管确切的 SQL 略有不同。我已经更新了我的问题,以澄清何时进行数据库调用。
    猜你喜欢
    • 2017-03-31
    • 2012-09-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-10
    • 1970-01-01
    • 2017-03-20
    相关资源
    最近更新 更多