LINQ to Entities Any()和Contains()减慢了小列表

时间:2014-05-20 18:21:27

标签: c# linq entity-framework

我正在使用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毫秒,因此我猜测性能与实体框架的工作方式有关。当然,它是第一次运行LINQ时速度最慢(我知道它需要编译查询),但在后续调用时它仍然相对较慢。

2 个答案:

答案 0 :(得分:4)

我尝试了很多不同的东西,最后找到了解决方案。

我认为当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);

这使我的初始查询性能提高了一些,并且随后的查询表现得更好。

答案 1 :(得分:1)

首先尝试过滤掉产品,然后再形成模型(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,
                    }),
    });