1

私が直面しているパフォーマンスの問題を掘り下げるためにアプリケーションをトリミングした後、私は非常に基本的なデータモデルを持っています。データモデルは、callend ProductとCategoryの例のように、2つのエンティティで構成されています。

class Product {
    public virtual int ID {get;set;}
    public virtual string Name {get;set;}
    public virtual int CategoryID {get;set;}
    public virtual Category Category {get;set;}
}

class Category {
    public virtual int ID {get;set;}
    public virtual string Name {get;set;}
}

カテゴリIDプロパティを使用してすべての参照が同じカテゴリに設定されている100個の新製品を追加し、db.SaveChanges()を呼び出すと、かなり時間がかかる場合があります。AutoDetectChangesEnabledをfalseに設定することで、必要な時間を短縮することができました。しかし、さらに2、3回実行した後、データベースに存在する製品の量が増えると、100個の新製品を挿入するのに必要な時間が増えることに気付きました。

23416 -->  +/- 7000 ms
25516 -->  +/- 7500 ms

実稼働環境ではテーブルが大幅に大きくなることがわかっているので、生のSQLクエリまたはBulkInsertの使用にフォールバックする前に、オプションが何であるかを知りたいと思います。

スタンリーとラファエルのコメントに応えて:

私のテストケースでは、両方のIDプロパティが主キーであり、CategoryIDにはインデックスがあります。トリガーなどはありません。

ループを100回実行しています。このループでは、新しいDbContextを初期化し、100個の製品を挿入して、DbContextを破棄します。

明確にするために:新しいプロジェクトを作成し、SqlCeでEFをセットアップし、Code-Firstを使用して上記のように文字通りデータモデルを構築し、同じ結果を得ることができます。

他のいくつかのテストを行った後、問題はSql ServerCompactEditionにあるようです。通常のMSSQLサーバーを使用すると、オブザーバーのパフォーマンスの問題が解消され、100行の挿入に一貫して約50ミリ秒かかります。問題の原因はわかったようですが、外部クエリツールを使用してコンパクトデータベースに100行を挿入しても、EFを使用して100行を挿入した場合と同じパフォーマンスの問題が発生しない理由はまだわかりません。

4

1 に答える 1

0

以前に ETL に Entity Framework を使用したことがあり、コンテキストの破棄に熱心に取り組んでいましたが、メモリ消費の問題に遭遇しました。ルーチンの一部としてガベージ コレクションを実行すると、メモリ消費量が削減されることがわかりましたが、その一部にすぎません。

一括挿入または一括更新に Entity Framework を使用するときに、n+1 アンチパターンを回避する方法を知りません。数百または数千のレコードをロードするこのシナリオが制御可能であり、めったに行わない場合は、これを試すことができます。

using (MyDbContext db = new MyDbContext())
{
    // prep your records and add them to your context

    // Save the records
    db.SaveChanges
}

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

ETL は 1 回限りのロードであり、数日間かけて拡張する時間があったため、これはうまくいきました。特に、制御できないユーザーやシステムに一括挿入を許可する場合は、このようなことを本番環境にプッシュしません。

私のチームは最終的に EF をトランザクションでの使用のみに使用することになり、一括読み込み、統合、およびレポート用に別のデータ アクセス レイヤーを使用しています。

この問題に対する私のアプローチは、モデル駆動型の開発を維持しようとする一方で、SQL Server で xml データ型を利用することです。オブジェクトを ICollection の一部として準備し、それらを XML としてシリアル化し、その文字列を次のようにストアド プロシージャにパラメーターとして送信します。

create procedure [dbo].[BulkAddProducts]
        @Values xml = null
    as begin
        insert into dbo.Products
            select
                    Products.Product.value('*:Name[1]', 'nvarchar(512)') as Name
                    ,Products.Product.value('*:CategoryID[1]', 'int') as CateogryID
                from @Values.nodes(N'/*:ArrayOfProduct/*:Product') Products(Product)
    end
go

以下は、proc を使用しないクエリの実際の例です。

declare @Val xml = '
<ArrayOfProduct xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/MyAppName.Models">
    <Product>
        <Name>Product1</Name>
        <CategoryID>5</CategoryID>
    </Product>
    <Product>
        <Name>Product2</Name>
        <CategoryID>5</CategoryID>
    </Product>
</ArrayOfProduct>
'
select
        Products.Product.value('*:Name[1]', 'nvarchar(512)') as Name
        ,Products.Product.value('*:CategoryID[1]', 'int') as CateogryID
    from @Val.nodes(N'/*:ArrayOfProduct/*:Product') Products(Product)
go
于 2012-10-05T23:28:55.937 に答える