我搜索了很多关于我的性能问题并尝试了各种不同的东西,但我似乎无法让它足够快地工作。这是我最简单形式的问题:
我正在使用实体框架 5,并且我希望能够在用户选择父级时延迟加载父级的子实例,因此我不必拉整个数据库。但是,我一直在延迟加载孩子时遇到性能问题。我认为问题在于父级和子级之间的导航属性的连接。我也认为这一定是我做错了什么,因为我相信这是一个简单的案例。
所以我提出了一个程序来测试单个延迟加载以隔离问题。
这是测试:
我创建了一个 POCO 父类和一个子 POCO 类。父母有 n 个孩子,孩子有 1 个父母。SQL Server 数据库中只有 1 个父级,而该单父级有 25 000 个子级。我尝试了不同的方法来加载这些数据。每当我在同一个 DbContext 中加载子项和父项时,都需要很长时间。但是如果我将它们加载到不同的 DbContexts 中,它的加载速度会非常快。但是,我希望这些实例位于同一个 DbContext 中。
这是我的测试设置以及复制它所需的一切:
POCO:
public class Parent
{
public int ParentId { get; set; }
public string Name { get; set; }
public virtual List<Child> Childs { get; set; }
}
public class Child
{
public int ChildId { get; set; }
public int ParentId { get; set; }
public string Name { get; set; }
public virtual Parent Parent { get; set; }
}
数据库上下文:
public class Entities : DbContext
{
public DbSet<Parent> Parents { get; set; }
public DbSet<Child> Childs { get; set; }
}
用于创建数据库和数据的 TSQL 脚本:
USE [master]
GO
IF EXISTS(SELECT name FROM sys.databases
WHERE name = 'PerformanceParentChild')
alter database [PerformanceParentChild] set single_user with rollback immediate
DROP DATABASE [PerformanceParentChild]
GO
CREATE DATABASE [PerformanceParentChild]
GO
USE [PerformanceParentChild]
GO
BEGIN TRAN T1;
SET NOCOUNT ON
CREATE TABLE [dbo].[Parents]
(
[ParentId] [int] CONSTRAINT PK_Parents PRIMARY KEY,
[Name] [nvarchar](200) NULL
)
GO
CREATE TABLE [dbo].[Children]
(
[ChildId] [int] CONSTRAINT PK_Children PRIMARY KEY,
[ParentId] [int] NOT NULL,
[Name] [nvarchar](200) NULL
)
GO
INSERT INTO Parents (ParentId, Name)
VALUES (1, 'Parent')
DECLARE @nbChildren int;
DECLARE @childId int;
SET @nbChildren = 25000;
SET @childId = 0;
WHILE @childId < @nbChildren
BEGIN
SET @childId = @childId + 1;
INSERT INTO [dbo].[Children] (ChildId, ParentId, Name)
VALUES (@childId, 1, 'Child #' + convert(nvarchar(5), @childId))
END
CREATE NONCLUSTERED INDEX [IX_ParentId] ON [dbo].[Children]
(
[ParentId] ASC
)
GO
ALTER TABLE [dbo].[Children] ADD CONSTRAINT [FK_Children.Parents_ParentId] FOREIGN KEY([ParentId])
REFERENCES [dbo].[Parents] ([ParentId])
GO
COMMIT TRAN T1;
App.config 包含连接字符串:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<connectionStrings>
<add
name="Entities"
providerName="System.Data.SqlClient"
connectionString="Server=localhost;Database=PerformanceParentChild;Trusted_Connection=true;"/>
</connectionStrings>
</configuration>
测试控制台类:
class Program
{
static void Main(string[] args)
{
List<Parent> parents;
List<Child> children;
Entities entities;
DateTime before;
TimeSpan childrenLoadElapsed;
TimeSpan parentLoadElapsed;
using (entities = new Entities())
{
before = DateTime.Now;
parents = entities.Parents.ToList();
parentLoadElapsed = DateTime.Now - before;
System.Diagnostics.Debug.WriteLine("Load only the parent from DbSet:" + parentLoadElapsed.TotalSeconds + " seconds");
}
using (entities = new Entities())
{
before = DateTime.Now;
children = entities.Childs.ToList();
childrenLoadElapsed = DateTime.Now - before;
System.Diagnostics.Debug.WriteLine("Load only the children from DbSet:" + childrenLoadElapsed.TotalSeconds + " seconds");
}
using (entities = new Entities())
{
before = DateTime.Now;
parents = entities.Parents.ToList();
parentLoadElapsed = DateTime.Now - before;
before = DateTime.Now;
children = entities.Childs.ToList();
childrenLoadElapsed = DateTime.Now - before;
System.Diagnostics.Debug.WriteLine("Load the parent from DbSet:" + parentLoadElapsed.TotalSeconds + " seconds" +
", then load the children from DbSet:" + childrenLoadElapsed.TotalSeconds + " seconds");
}
using (entities = new Entities())
{
before = DateTime.Now;
children = entities.Childs.ToList();
childrenLoadElapsed = DateTime.Now - before;
before = DateTime.Now;
parents = entities.Parents.ToList();
parentLoadElapsed = DateTime.Now - before;
System.Diagnostics.Debug.WriteLine("Load the children from DbSet:" + childrenLoadElapsed.TotalSeconds + " seconds" +
", then load the parent from DbSet:" + parentLoadElapsed.TotalSeconds + " seconds");
}
using (entities = new Entities())
{
before = DateTime.Now;
parents = entities.Parents.ToList();
parentLoadElapsed = DateTime.Now - before;
before = DateTime.Now;
children = parents[0].Childs;
childrenLoadElapsed = DateTime.Now - before;
System.Diagnostics.Debug.WriteLine("Load the parent from DbSet:" + parentLoadElapsed.TotalSeconds + " seconds" +
", then load the children from Parent's lazy loaded navigation property:" + childrenLoadElapsed.TotalSeconds + " seconds");
}
using (entities = new Entities())
{
before = DateTime.Now;
parents = entities.Parents.Include(p => p.Childs).ToList();
parentLoadElapsed = DateTime.Now - before;
System.Diagnostics.Debug.WriteLine("Load the parent from DbSet and children from include:" + parentLoadElapsed.TotalSeconds + " seconds");
}
using (entities = new Entities())
{
entities.Configuration.ProxyCreationEnabled = false;
entities.Configuration.AutoDetectChangesEnabled = false;
entities.Configuration.LazyLoadingEnabled = false;
entities.Configuration.ValidateOnSaveEnabled = false;
before = DateTime.Now;
parents = entities.Parents.Include(p => p.Childs).ToList();
parentLoadElapsed = DateTime.Now - before;
System.Diagnostics.Debug.WriteLine("Load the parent from DbSet and children from include:" + parentLoadElapsed.TotalSeconds + " seconds with everything turned off");
}
}
}
以下是这些测试的结果:
仅从 DbSet 加载父级:0,972 秒
仅从 DbSet 加载子项:0,714 秒
从 DbSet:0,001 秒加载父级,然后从 DbSet:8,6026 秒加载子级
从 DbSet:0,6864 秒加载子项,然后从 DbSet:7,5816159 秒加载父项
从 DbSet 加载父级:0 秒,然后从父级延迟加载的导航属性加载子级:8,5644549 秒
从 DbSet 加载父级,从 include:8,6428788 秒加载子级
从 DbSet 加载父级,从 include:9,1416586 秒加载父级,所有内容都关闭
分析
每当父级和子级位于同一个 DbContext 中时,需要很长时间(9 秒)才能将所有内容连接起来。我什至尝试关闭从代理创建到延迟加载的所有内容,但无济于事。有人可以帮帮我吗 ?