我在升级到 .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> CustomerBaseChildren
,ICollection<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 不能在更大的环境中使用?