.NET 4.0アプリケーションにEF4モデルがあり、.NET 4.5およびEF5(新しいEntityFramework 5アセンブリを参照)にアップグレードし、[コード生成戦略]を[なし]に変更して、コード生成項目(EF 5 .x DbContext Generator)をモデルに追加します。これは、ほとんどすべての状況で正常に機能します。しかし、多くのレコード(> 100.000レコード)を参照するナビゲーションプロパティにアクセスすると、大きな問題が発生します。データベースはMSSQL2005サーバーです。
私のシナリオは次のようになります。
私のデータベース内のすべての顧客は一意のID(DBの主キー)を持っています。さらに、すべての顧客レコードには親顧客IDが含まれています(この特別な場合、ほとんどすべての顧客が同じ親IDを参照しています(150.000のうち約145.000レコード) records)これは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として使用しました。次に、ナビゲーションプロパティの名前を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で3つのステートメントすべてを実行すると、1 + 1 + 150.000レコードがすべてフェッチされるまで約1〜2秒かかります。
しかし、私が理解しているように、3番目のステートメントが問題です。150.000レコードを返します(.First()
上記のコードのように使用する場合でも、その前で使用するかどう.Single()
かに関係なく。EntityFrameworkは150.000レコードをすべてフェッチし、DbContextにレコードをキャッシュするのに非常に長い時間がかかるようです( 2分待った後、テストコードを停止し、実際の顧客ベーステーブルでテストしました。終了するのに100分かかりました)。ObjectContextでのキャッシュは約10秒しかかかりません(データベース自体が5〜10倍高速であると考えるのは悪いことです)。 、しかし私はそれで生きることができました)。.Take(10)
.OrderBy(...)
メモリ消費量でさえひどく、ObjectContextを使用するとアプリケーションのワーキングセットは約200mbを上げ、DbContextを使用するとワーキングセットは約10倍高くなります。
最初のレコードまたは最初のnレコード(通常は10から100レコード)のみが必要な場合に、selectステートメントにTOP(n)句を挿入して、データベースからのすべてのレコードの受信を停止する方法はありますか?最初のステートメントでは、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.InvalidOperationException
でatを取得します。CustomerBase firstChild = parentCustomer.CustomerBaseChildren.First();
オブジェクトがNoTrackingマージオプションで返される場合、Loadは、EntityCollectionまたはEntityReferenceにオブジェクトが含まれていない場合にのみ呼び出すことができます。
EF5でObjectContextを使用するようにコード生成戦略を元に戻すと、古き良きEF4のようにすべてが正常に機能します。DbContextの使用中に何か問題が発生したのでしょうか、それともDbContextが大規模な環境で使用できないのでしょうか。