C# LINQ到实体Any()和Contains()慢,列表小

C# LINQ到实体Any()和Contains()慢,列表小,c#,linq,entity-framework,C#,Linq,Entity Framework,我使用EF6从数据库中获取产品。产品类别映射为产品上的导航属性,数据来自ProductCategory透视表。类别的工作方式类似于一棵树(即,每个类别都可以有子类别),但透视表中只存储最具体的产品子类别关系。例如,假设存在如下类别路径: 电子设备>音频>放大器>集成放大器 集成放大器产品在数据透视表中有一条记录,其中包含产品ID和集成放大器类别ID 我需要按类别过滤,但即使按父类别过滤,产品也应显示,例如,集成放大器应显示在放大器列表中。首先,我列出了相关的类别ID。(这涉及到对categori

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

电子设备>音频>放大器>集成放大器

集成放大器产品在数据透视表中有一条记录,其中包含产品ID和集成放大器类别ID

我需要按类别过滤,但即使按父类别过滤,产品也应显示,例如,集成放大器应显示在放大器列表中。首先,我列出了相关的类别ID。(这涉及到对categories表的单独查询,但不会花费很长时间。)如果类别过滤器是放大器,则列表是放大器的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();
}
List currentCategoryIDChildren=BuildCategoryIdList(currentCategoryId);
使用(var db=new myContext())
{
var products=db.products
.选择(p=>新产品
{
id=p.id,
名称=p.名称,
描述=p.描述,
类别=p.类别
.选择(c=>新类别
{
类别ID=c.ID,
}),
});
//按类别筛选
products=products.Where(pl=>pl.categories.Any(c=>CurrentCategoryAndChildren.Contains(c.categoryid));
//这里还有其他过滤器、排序和分页功能
rptProducts.DataSource=products.ToList();//在此处进行数据库调用
rptProducts.DataBind();
}
我希望Any()和Contains()的组合会随着大量记录的出现而迅速放缓,但我正在处理产品中的22项、pl.categories中的1-3项以及CurrentCategorityAndChildren中的1-5项。我感到惊讶的是,由于记录太少,速度慢了一个数量级。以这种速度,我最好在客户端过滤它,即使这意味着带回许多不必要的记录

有什么我遗漏的吗?还有别的办法吗


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

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


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

我认为主要的减速发生在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);
var IsInCategory=PredicateBuilder.False();
foreach(CurrentCategoryAndChildren中的int categoryID)
{IsInCategory=IsInCategory.Or(pl=>pl.categories.Any(c=>categoryID==c.categoryID));}
产品=产品。其中(IsInCategory);

这使我在初始查询时获得了更好的性能,在后续查询时获得了更好的性能。

您应该在选择的同时进行筛选。当您在第二行中调用“products.Where”时,它首先必须枚举产品。将Where子句移动到第一个linq的末尾call@DLeh你确定那是对的吗?第一个投影实际上没有实现,因此我希望SQL代码生成足够智能。检查的一种方法是记录生成的SQL。我不是肯定的,但这是我要尝试的第一件事。@Dleh产品在实际访问之前(例如使用.ToList()或foreach)不会被枚举。我已经验证了直到那时才进行SQL调用。实际上,在上面的代码之后,我还有其他几个条件过滤器以及分页,它在最后导致了一个巨大的过滤SQL调用。但是我会尝试移动Where只是为了好玩。整个操作需要多长时间(不仅仅是SQL查询)?如果只调用ToList而不将其指定为数据源,有什么区别吗?无论我将其放置在何处,我都会获得相同的性能,尽管确切的SQL略有不同。我已经更新了我的问题,以澄清何时进行数据库调用。
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);