5

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

我的场景如下所示:

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

我的模型包含DbSet<CustomerBase> CustomerBase代表包含所有客户及其数据的表。此外,还有一些导航属性被调用ICollection<CustomerBase> CustomerBaseChildrenICollection<CustomerBase> CustomerBaseParent它们以 0..1 到 * 的多重性连接客户 ID 和客户父 ID。

我构建了一个简化版本来说明我的意思:

为此测试构建包含 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

将包含引用约束的表导入到新的实体模型中。我用作“实体容器名称”ef5Entities。然后我将导航属性 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();
}

我使用 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

如果我在 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 倍。

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

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

但随后收到System.InvalidOperationException以下CustomerBase firstChild = parentCustomer.CustomerBaseChildren.First();消息:

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

如果我将代码生成策略改回使用 EF5 的 ObjectContext,则一切正常,就像在旧的 EF4 中一样。我在使用 DbContext 时做错了什么,还是 DbContext 不能在更大的环境中使用?

4

1 回答 1

1

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

所以解决方案是使用带有 ObjectContext 的 EF4/EF5 或带有 DbContext 的 EF6。

于 2013-12-04T16:51:05.330 回答