将EF4模型升级到EF5模型后性能下降(DbContext)

Joh*_*zer 5 entity-framework-5

我在.NET 4.0应用程序中有一个EF4模型,我升级到.NET 4.5和EF5(引用新的EntityFramework 5程序集),我将"代码生成策略"更改为"无"并添加了代码生成项(EF 5) .x DbContext Generator)到模型.几乎在任何情况下都能正常工作.但是,当我访问引用大量记录(> 100.000条记录)的导航属性时,现在遇到了大问题.该数据库是MSSQL 2005 Server.

我的场景看起来像这样:

我的数据库中的每个客户都有一个唯一的ID(它是数据库中的主键),此外每个客户记录都包含一个父客户ID(在这种特殊情况下,几乎每个客户都引用相同的父ID(150.000中约145.000条记录)记录)id为1)的记录.

我的模型包含DbSet<CustomerBase> CustomerBase表示包含所有客户及其数据的表的表.此外,还有一些名为的导航属性ICollection<CustomerBase> CustomerBaseChildren,ICollection<CustomerBase> CustomerBaseParent它将客户ID和客户父ID与0..1到*多样性连接起来.

我构建了一个简化版本来演示我的意思:

为此测试构建包含150.000条记录的表:

CREATE TABLE CustomerBase
(
  id int IDENTITY(1,1) PRIMARY KEY NOT NULL,
  parent_id int FOREIGN KEY REFERENCES CustomerBase(id),
  some_data1 varchar(100),
  some_data2 varchar(100),
  some_data3 varchar(100),
  some_data4 varchar(100),
  some_data5 varchar(100),
)
GO

DECLARE @i int = 0
WHILE @i < 150000 BEGIN
  INSERT INTO CustomerBase (parent_id, some_data1, some_data2, some_data3, some_data4, some_data5) VALUES (1, newid(), newid(), newid(), newid(), newid())

  SET @i = @i + 1
END
Run Code Online (Sandbox Code Playgroud)

将包含引用约束的表导入新的实体模型.我用作"实体容器名称"ef5Entities.然后我将Navigation Propierties CustomerBase1和CustomerBase2重命名为CustomerBaseChildren和CustomerBaseParent.

这是我的示例应用程序:

static void Main(string[] args)
{
  ef5Entities context = new ef5Entities();

  // Start with selecting a single customer.
  // Works fine in EF4/ObjectContext, works even faster in in EF5/DbContext
  CustomerBase someCustomer = context.CustomerBase.First(customer => customer.id == 1234);
  // Do something ...

  // Get the parent of the customer.
  // Works fine in EF4/ObjectContext, works even faster in in EF5/DbContext
  CustomerBase parentCustomer = someCustomer.CustomerBaseParent;
  // Do something ...

  // Get the first child of the given parent id.
  // Takes about 10 seconds in EF4/ObjectContext, I stopped the debugger after 2 minutes waiting in EF5/DbContext
  CustomerBase firstChild = parentCustomer.CustomerBaseChildren.First();

  Console.WriteLine("Press any key to quit.");
  Console.ReadKey();
}
Run Code Online (Sandbox Code Playgroud)

我使用SQL Server Profiler来查看在数据库上执行的实体框架.似乎EF4和EF5代码完全相同:

SELECT TOP (1) 
[Extent1].[id] AS [id], 
[Extent1].[parent_id] AS [parent_id], 
[Extent1].[some_data1] AS [some_data1], 
[Extent1].[some_data2] AS [some_data2], 
[Extent1].[some_data3] AS [some_data3], 
[Extent1].[some_data4] AS [some_data4], 
[Extent1].[some_data5] AS [some_data5]
FROM [dbo].[CustomerBase] AS [Extent1]
WHERE 1234 = [Extent1].[id]

exec sp_executesql N'SELECT 
[Extent1].[id] AS [id], 
[Extent1].[parent_id] AS [parent_id], 
[Extent1].[some_data1] AS [some_data1], 
[Extent1].[some_data2] AS [some_data2], 
[Extent1].[some_data3] AS [some_data3], 
[Extent1].[some_data4] AS [some_data4], 
[Extent1].[some_data5] AS [some_data5]
FROM [dbo].[CustomerBase] AS [Extent1]
WHERE [Extent1].[id] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1

exec sp_executesql N'SELECT 
[Extent1].[id] AS [id], 
[Extent1].[parent_id] AS [parent_id], 
[Extent1].[some_data1] AS [some_data1], 
[Extent1].[some_data2] AS [some_data2], 
[Extent1].[some_data3] AS [some_data3], 
[Extent1].[some_data4] AS [some_data4], 
[Extent1].[some_data5] AS [some_data5]
FROM [dbo].[CustomerBase] AS [Extent1]
WHERE [Extent1].[parent_id] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1
Run Code Online (Sandbox Code Playgroud)

如果我在SQL Management Studio中执行所有三个语句,则需要大约1-2秒才能获取所有1 + 1 + 150.000条记录.

但据我所知,第三个陈述是问题所在.它返回150.000记录(即使我使用.First()像上面或代码.Single().Take(10)不管我用.OrderBy(...)它前面与否.这似乎是实体框架获取所有150.000记录和缓存中的DbContext记录需要花费非常多的时间(在等待2分钟后,我停止了测试代码,用真实的客户基表测试它需要100分钟才能完成.在ObjectContext中缓存只需要大约10秒钟(考虑到数据库本身速度快5-10倍是不好的) ,但我可以用那个生活).

即使内存消耗非常可怕,使用ObjectContext,应用程序工作集也会增加大约200mb,而DbContext的工作集会提高大约10倍.

有没有办法在select语句中注入一个TOP(n)子句来停止从数据库接收所有记录,如果我只想要第一条记录或前n条记录(通常是10到100条记录)?在第一个语句中,select语句中有一个TOP(1)(如果使用.Single()而不是TOP(2).First()).

我甚至试图将线路CustomerBase someCustomer = context.CustomerBase.First(customer => customer.id == 1234);改为无跟踪:CustomerBase someCustomer = context.CustomerBase.AsNoTracking().First(customer => customer.id == 1234);

但随后得到一个System.InvalidOperationExceptionCustomerBase firstChild = parentCustomer.CustomerBaseChildren.First();以下消息:

使用NoTracking合并选项返回对象时,只能在EntityCollection或EntityReference不包含对象时调用Load.

如果我将代码生成策略更改为使用带有EF5的ObjectContext,一切都可以正常工作,就像在旧的EF4中一样.我在使用DbContext时做错了什么,或者DbContext在大型环境中不可用?

Joh*_*zer 1

我最近升级到 Visual Studio 2013、.NET 4.5.1 和 Entity Framework 6。如果我修改我的模型以使用 EF6 而不是 EF5,它的工作方式就像一个魅力。

因此,解决方案是将 EF4/EF5 与 ObjectContext 结合使用,或将 EF6 与 DbContext 结合使用。