Linq to Entities为什么我的一对多查询速度如此之慢?

时间:2017-05-04 21:39:07

标签: c# .net entity-framework linq

我有两个ViewModel:

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; }
}

我正在查询这样的数据库,以获取产品列表以及相关的部件:

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

Product表中有~8800条记录,Part表中只有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; }
}

如果您需要更多代码或信息,请与我们联系。你能看出我做错了什么吗?什么能以更快的方式恢复数据?

3 个答案:

答案 0 :(得分:3)

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

}).ToList();

问题是,对于Products中的每个产品,您要在Parts处实现项目列表,这意味着对表Parts进行了8800次查询。

如果您将PartsProductViewModel的类型更改为IEnumerable<PartViewModel>,则可以执行以下操作:

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

});

这将解决问题。

答案 1 :(得分:0)

分开查询。

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())

答案 2 :(得分:0)

你可以删除ToList()调用但是你留下了IQueryable类型。最简单的处理方法是使用AutoMapper等工具,并在查询中直接将其映射到ProductViewModel。所以代码看起来像这样:

using AutoMapper.QueryableExtensions;

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

现在您选择了所需的所有部件,您可以通过调用实际执行查询:

parts.ToList();

所以一般的想法是过滤你想要的所有东西,然后使用ToList()或Count()这样的调用来实际执行查询。

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

https://github.com/AutoMapper/AutoMapper/wiki/Queryable-Extensions了解有关automapper的更多信息。