2017-05-04 44 views
2

我有兩個的ViewModels:爲什麼我的一對多查詢如此慢以及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 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(); 

有〜8800條記錄中產品表和Part表中只有1條記錄。該查詢需要將近4分鐘才能運行。當我刪除零件列表如下:

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

...大約需要4秒。

下面是在數據庫中,通過代碼首先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

以下是Products中的每個產品的問題,您正在實現項目列表Parts,這意味着8800查詢到表Parts

如果在ProductViewModel改變Parts類型IEnumerable<PartViewModel>你可以這樣做:

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

}); 

這將解決這個問題。

+0

當我「刪除ToList」時,你能告訴我代碼的樣子嗎? – crackedcornjimmy

+0

完成,只是刪除ToList()。 – Gusman

+0

這給了我錯誤:不能隱式地將類型'System.Collections.Generic.IEnumerable '轉換爲'System.Collections.Generic.List '。存在明確的轉換(你是否缺少演員?) – crackedcornjimmy

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

這是非常低效 – McAden

+0

爲什麼這樣做時,一個單一的查詢數據庫會做得更好? – Gusman

+0

它比數據庫@Gusman的8000子查詢高效得多。 我想不出在單個查詢中做到這一點的方法。 – Diego658

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()調用,您可以優化您的查詢。這將禁用更改跟蹤,因此您對模型對象所做的任何更改都不會被保存。如果您需要更改數據庫中的值,請小心不要調用它,但對於只讀場景,最好包含該調用,因爲它可以防止意外的數據更改並運行得更快。

查找更多關於automapper的信息在:https://github.com/AutoMapper/AutoMapper/wiki/Queryable-Extensions

+0

如果您閱讀了查詢,您會看到每個產品的各個部件都有所區別,這將如何工作,因爲它會返回一個部件的單個實例? – Gusman

+0

這將返回Iqueryable 類型,所以是一個零件列表。實際上,第一個查詢不會返回任何內容,因爲這是一個等待執行的查詢。只有當你調用它的一些方法,如List(),它纔會執行。 – ACama

+0

當通過ToList執行最終查詢時,內部查詢也將實現,Linq足夠聰明,可以將它投影到一個簡單的連接中。 – Gusman

相關問題