我在.NET 4.0应用程序中有一个EF4模型,我升级到.NET 4.5和EF5(引用新的EntityFramework 5程序集),我将“代码生成策略”更改为“无”并添加了代码生成项(EF 5.x DbContext Generator)到模型。几乎在任何情况下都能正常工作。但是,当我访问引用大量记录(> 100.000记录)的导航属性时,现在遇到了很大的问题。该数据库是MSSQL 2005 Server。
我的方案如下:
我的数据库中的每个客户都有一个唯一的ID(它是数据库中的主键),此外每个客户记录都包含一个父客户ID(在这种特殊情况下,几乎每个客户都引用相同的父ID(约145.000条记录)在150.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
将包含参考约束的表导入新的实体模型。我用作“实体容器名称”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();
}
我使用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(...)
。像Entity Framework获取所有150.000条记录并在DbContext中缓存记录需要花费大量时间(在等待2分钟后我停止了测试代码,使用我的真实客户基表测试它需要100分钟才能完成)。在ObjectContext中缓存需要只有大约10秒钟(考虑到数据库本身速度快了5-10倍,这是不好的,但我可以用它来生活)。
即使内存消耗非常可怕,使用ObjectContext,应用程序工作集也会增加大约200mb,而DbContext的工作集会提高大约10倍。
如果我只想要第一条记录或前n条记录(通常是10到100条记录),有没有办法在select语句中注入一个TOP(n)子句来停止接收数据库中的所有记录?在第一个语句中,select语句中有一个TOP(1)(如果使用.Single()
而不是.First()
,则为TOP(2))。
我甚至尝试将行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在大型环境中不可用?
答案 0 :(得分:1)
我最近升级到Visual Studio 2013,.NET 4.5.1和Entity Framework 6.如果我修改我的模型以使用EF6而不是EF5,它就像魅力一样。
因此,解决方案是使用带有ObjectContext的EF4 / EF5或带有DbContext的EF6。