LINQ to Entities Any()和Contains()以小列表运行缓慢

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

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

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

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

问题是,当我包含filter时,产品查询需要10到20倍的时间:

List 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个项目以及currentCategoryIdAndChildren中的1-5个项目。 令我感到惊讶的是,由于记录太少,它的速度会慢一个数量级。 按照这个速度,我最好过滤它的客户端,即使这意味着带回了很多不必要的记录。

有什么我想念的吗? 还有另一种方法吗?

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

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

我相信当EF将Contains()转换为SQL查询时,主要的减速发生了。 然而,最引人注目的是它似乎没有缓存查询。 从我可以收集到的,这是因为类别ID列表(currentCategoryIdAndChildren)是在EF之外生成的,因此它假设每次都不同。

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

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

这使我的初始查询性能提高了一些,并且后续查询的性能更高。

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