我在.NET 4.0應用程序中有一個EF4模型,我升級到.NET 4.5和EF5(引用新的EntityFramework 5程序集),我更改了「代碼生成策略「更改爲」無「,並向模型添加了代碼生成項目(EF 5.x DbContext生成器)。幾乎在任何情況下都可以正常工作。但是現在當我訪問一個引用大量記錄(> 100.000條記錄)的導航屬性時會遇到很大的問題。該數據庫是MSSQL 2005服務器。將EF4模型升級爲EF5模型(DbContext)後性能下降
我的情況是這樣的:
在我的分貝每一個客戶都有一個唯一的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
導入表包括的借鑑約束到一個新的實體模型。我用作「實體容器名稱」ef5Entities。然後,我將Navigation Adierties 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);
但接下來在CustomerBase firstChild = parentCustomer.CustomerBaseChildren.First();
以下消息得到System.InvalidOperationException
:
當一個對象被用NoTracking合併選項返回,僅當EntityCollection或EntityReference不包含對象時才能調用加載。
如果我將代碼生成策略更改爲使用ObjectContext和EF5,那麼在EF4中一切正常。我在使用DbContext的時候做錯了什麼,或者DbContext是在大環境下無法使用的嗎?