C# 为什么我的一对多查询在LINQtoEntities中如此缓慢?

C# 为什么我的一对多查询在LINQtoEntities中如此缓慢?,c#,.net,entity-framework,linq,C#,.net,Entity Framework,Linq,我有两个视图模型: public class ProductViewModel { public int Id { get; set; } public string Name { get; set; } public List<PartViewModel> Parts { get; set; } } public class PartViewModel { public int Id { get; set; } public stri

我有两个视图模型:

public class ProductViewModel
{
    public int Id { get; set; }

    public string Name { get; set; }

    public List<PartViewModel> Parts { get; set; }
}

public class PartViewModel
{
    public int Id { get; set; }

    public string Name { get; set; }
}
产品表中约有8800条记录,零件表中只有1条记录。此查询运行大约需要4分钟。当我这样删除明细表时:

var prods = _context.Products.Select(pr => new ProductViewModel
    {
        Id = pr.Id,
        Name = pr.Name
    }).ToList();
…大约需要4秒钟

以下是我在数据库中的表定义,通过Code First EF创建(我确保显示索引,因为这可能是索引问题:

CREATE TABLE [dbo].[Product](
    [Id] [int] NOT NULL,
    [Name] [nvarchar](max) NOT NULL,
 CONSTRAINT [PK_Product] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

ALTER TABLE [dbo].[Product] ADD  CONSTRAINT [PK_Product] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

CREATE TABLE [dbo].[Part](
    [Id] [int] NOT NULL,
    [Name] [nvarchar](max) NOT NULL,
    [ProductId] [int] NULL,
 CONSTRAINT [PK_Part] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

ALTER TABLE [dbo].[Part]  WITH CHECK ADD  CONSTRAINT [FK_Part_Product_ProductId] FOREIGN KEY([ProductId])
REFERENCES [dbo].[Product] ([Id])
GO

ALTER TABLE [dbo].[Part] CHECK CONSTRAINT [FK_Part_Product_ProductId]
GO

CREATE NONCLUSTERED INDEX [IX_Part_ProductId] ON [dbo].[Part]
(
    [ProductId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

ALTER TABLE [dbo].[Part] ADD  CONSTRAINT [PK_Part] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
最后,以下是两个代码优先实体:

[Table("Product")]
public partial class Product
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public Product()
    {
        Parts = new HashSet<Part>();
    }

    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    [Required]
    public int Id { get; set; }

    [Required]
    public string Name { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<Part> Parts { get; set; }
}

[Table("Part")]
public class Part
{
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    [Required]
    public int Id { get; set; }

    [Required]
    public string Name { get; set; }    

    public virtual Product Product { get; set; }
}
[表格(“产品”)]
公共部分类积
{
[System.Diagnostics.CodeAnalysis.SuppressMessage(“Microsoft.Usage”,“CA2214:DoNotCallOverridableMethodsInConstructors”)]
公共产品()
{
Parts=新HashSet();
}
[数据库生成(DatabaseGeneratedOption.None)]
[必需]
公共int Id{get;set;}
[必需]
公共字符串名称{get;set;}
[System.Diagnostics.CodeAnalysis.SuppressMessage(“Microsoft.Usage”,“CA2227:CollectionPropertiesShouldBreadOnly”)]
公共虚拟ICollection部分{get;set;}
}
[表(“部分”)]
公共课部分
{
[数据库生成(DatabaseGeneratedOption.None)]
[必需]
公共int Id{get;set;}
[必需]
公共字符串名称{get;set;}
公共虚拟产品产品{get;set;}
}
如果你需要更多的代码或信息,请告诉我。你能看到我做错了什么吗?怎样才能更快地恢复数据

Parts = pr.Parts.Select(prt => new PartViewModel
{
    Id = prt.Id,
    Name = prt.Name

}).ToList();
问题是,对于
Products
中的每个产品,您正在具体化
Parts
中的项目列表,这意味着对表
Parts
进行8800次查询

如果将
ProductViewModel
中的
零件的类型更改为
IEnumerable
,则可以执行以下操作:

Parts = pr.Parts.Select(prt => new PartViewModel
{
    Id = prt.Id,
    Name = prt.Name

});
这将解决问题。

将查询分开

var prods = _context.Products.Select(pr => new ProductViewModel
    {
        Id = pr.Id,
        Name = pr.Name
    }).ToList();

    var parts = _context.Parts.Select(prt => new PartViewModel
    {
        Id = prt.Id,
    ProductId = prt.ProductId,
        Name = prt.Name
    }).ToList();


prods.ForEach( pr => pr.Parts = parts.Where(prt=> prt.ProductId == pr.Id).ToList())
您可以删除ToList()调用,但剩下的是IQueryable类型。最简单的处理方法是使用AutoMapper之类的工具,并在查询中直接将其映射到ProductViewModel。因此,代码如下所示:

using AutoMapper.QueryableExtensions;

var parts = _context.Parts
                .Include(part => part.Whatever)
                .OrderByDescending(part => part.Whatever)
                .AsNoTracking()
                .ProjectTo<PartsListViewModel>()
因此,一般的想法是过滤所有需要的内容,然后使用ToList()或Count()之类的调用来实际执行查询

另外,如果添加AsNoTracking(),可以稍微优化查询调用。这将禁用更改跟踪,因此您对模型对象所做的任何更改都不会被保存。如果需要更改数据库中的值,请小心不要调用它,但对于只读方案,最好包含该调用,因为它将防止意外数据更改并运行得更快


有关automapper的更多信息,请访问:。

您能告诉我当我“删除ToList”时代码是什么样子吗?完成后,只需删除ToList().抱歉!!我完全看错了代码,我看的是产品,而不是ProductViewModel。你能在你的类中将List替换为IEnumerable吗?这应该不会有什么害处,因为你将在其中枚举项,并且将与linq完全兼容。@crackedcornjimmy我不确定这样做“as List”是否正确…我建议您将ProductViewModel上的Parts属性更改为IEnumerablet这就是答案。除非您必须这样做,否则永远不要列出EF查询。这是非常低效的。为什么对DB的一次查询会做得更好?它比对数据库@Gusman的8000个子查询要高效得多。我不能想一个在单个查询中实现的方法。阅读我的答案,你可以看到如何实现。如果你阅读查询,你会看到每个产品都有其不同的部件,当它返回单个部件实例时,这将如何工作?这将返回Iqueryable类型,因此是一个部件列表。实际上,第一个查询不会返回任何东西,因为此时我t是一个等待执行的查询。只有当您对其调用某些方法(如List())时,它才会执行。当通过ToList执行最终查询时,内部查询也将被物化,Linq足够聪明,可以将其投影到一个简单的联接中。
using AutoMapper.QueryableExtensions;

var parts = _context.Parts
                .Include(part => part.Whatever)
                .OrderByDescending(part => part.Whatever)
                .AsNoTracking()
                .ProjectTo<PartsListViewModel>()
parts.ToList();