エンティティフレームワークとパフォーマンス

Webポータルプロジェクトに取り組む過程で、パフォーマンスを改善する機会を探り、StackOverflow.comプロジェクトの作者によって書かれたmicro-ORM Dapperに関する短い記事に出会いました。 当初、彼らのプロジェクトはLinq2Sqlで記述されていましたが、現在、パフォーマンスが重要な場所はすべて、示されたソリューションを使用して書き直されています。

この欠点、および私が見た他の同様のソリューションの欠点は、それらが開発プロセスを促進するのにほとんど役に立たないことであり、概して、実体化のみを提供し、ADO.Netでの作業を直接隠します。 SQLクエリは手書きで作成する必要があります。



Linq2Entities構文は、より「クリーンなコード」に処理されるため、コードテストとその再利用の両方が可能になります。 さらに、データベースを変更すると、コンテキストを更新した直後に、コンパイラはリモートまたは名前変更されたフィールドが使用されるすべての場所でエラーを生成します。テーブル間のリレーションの変更された構造は、対応するナビゲーションプロパティが使用される場所を強調表示します。





しかし、この記事はEFが開発をどれだけ高速化するかについてではなく、linqで記述された要求の一部ではなく、直接sqlで記述されていることがあまり良くないということでもありません。 ここでは、一方でEFエンティティとLinq2Entitiesクエリを組み合わせ、もう一方でADO.Netの「純粋なパフォーマンス」を組み合わせることができるソリューションを提供します。 しかし、最初に、少しの背景。 そのようなプロジェクトで働いていた人は皆、行ごとの呼び出しが非常にゆっくりと動作するという事実に直面したと思います。 そしておそらく、多くの人が巨大なクエリを作成し、可能な限りすべてを絞り込んで、これを最適化しようとしました。 それは動作しますが、非常に怖いようです-メソッドのコードは巨大であり、維持するのが難しく、テストすることができません。 私が試みたソリューションの最初の段階は、個々の要求ごとに、必要なすべてのエンティティを具体化することです。 そして、それらの接続/ドメイン構造への変換は、実体化とは別に発生します。

例で説明します。 保険証券のリストを表示する必要があります。最初の要求は次のようになります。

int clientId = 42; var policies = context.Set<policy>().Where(x => x.active_indicator).Where(x => x.client_id == clientId);
      
      





さらに、必要な情報を表示するには、「従属」エンティティまたは「子」エンティティとも呼ばれる必要があります。

 var coverages = policies.SelectMany(x => x.coverages); var premiums = coverages.Select(x => x.premium).Where(x => x.premium_type == SomeIntConstant);
      
      





NavPropsを介して接続されたエンティティは、Includeを使用してロードすることもできますが、これには独自の難点があります;前述の例のように、それはより簡単であることがわかりました(そしてより生産的です)

この変更自体は、最初の包括的な要求に比べてパフォーマンスは向上しませんでしたが、コードを簡素化し、より小さなメソッドに分割できるようにし、コードをより魅力的で見慣れたものにしました。



次のステップでパフォーマンスが向上しました。SQLServerプロファイラーを起動したとき、30のクエリのうち2つが他のクエリより10〜15倍長いことがわかりました。 これらのクエリの最初はこのようなものでした

 var tasks = workflows.SelectMany(x => x.task) .Where(x => types.Contains(x.task_type)) .GroupBy(x => new { x.workflow_id, x.task_type}) .Select(x => x.OrderByDescending(y => y.task_id).FirstOrDefault());
      
      





判明したように、EFは非常に失敗したリクエストを生成し、GroupByを最後から最初に移動するだけで、これらのリクエストの速度を他のリクエストに近づけ、合計実行時間を約30〜35%短縮しました。

 var tasks = context.Set<task> .GroupBy(x => new { x.workflow_id, x.task_type}) .Select(x => x.OrderByDescending(y => y.task_id).FirstOrDefault()) .Join(workflows, task => task.workflow_id, workflow => workflow.workflow_id, (task, workflow) => task) .Where(x => types.Contains(x.task_type));
      
      





念のため、このリクエストのJoinは前のSelectManyと同等であると言います。

巨大なリクエストの深みにあるこのような欠陥を見つけて排除することは、不可能に迫る問題です。 また、これを含めることもできません。



記事の冒頭に戻って、マイクロORMについて、このようなアプローチはおそらくすべてのシナリオで正当化されるわけではないとすぐに言いたいと思います。 私たちのデータベースでは、データの一部をデータベースからロードし、変換と計算を行い、JSONを使用してブラウザーでクライアントに送信する必要がありました。

ソリューションのプロトタイプとして、PetaPocoを使用して具体化を実装しようとしましたが、テスト結果に非常に感銘を受けました。クエリのターゲットグループの具体化の時間差は4.6倍(756ミリ秒に対して756ミリ秒)でした。 EFのパフォーマンスに失望したと言う方が正しいでしょうが。

StyleCopの厳しい設定のため、プロジェクトでPetaPocoを使用することは機能しませんでした。タスクに適合させるために、私はそれに取り組み、変更を加えなければなりませんでした。

ソリューションは、クエリを生成するときに、クエリ内のEFがコンテキスト用に生成したオブジェクトのプロパティ名に対応するデータセットのフィールド名を示すという事実に依存しています。 または、これらのフィールドの順序に依存することもできますが、これも機能します。 クエリおよびクエリからパラメーターを抽出するには、ToObjectQueryメソッドが使用され、ToTraceStringメソッドとParametersプロパティは結果のオブジェクトで既に使用されています。 以下は、MSDNから取られた単純な読み取りサイクルで、マテリアライザーはソリューションのハイライトです。 PetaPocoは実行時にマテリアライザーコードを出力しますが、T4テンプレートを使用してマテリアライザーコードを生成することにしました。 .edmxを読み取りながら、コンテキストのエンティティを生成するファイルを基礎として、そこからすべての補助クラスを使用し、直接生成するコードを置き換えました。

生成されたクラスの例:

  public class currencyMaterialize : IMaterialize<currency>, IEqualityComparer<currency> { public currency Materialize(IDataRecord input) { var output = new currency(); output.currency_id = (int)input["currency_id"]; output.currency_code = input["currency_code"] as string; output.currency_name = input["currency_name"] as string; return output; } public bool Equals(currency x, currency y) { return x.currency_id == y.currency_id; } public int GetHashCode(currency obj) { return obj.currency_id.GetHashCode(); } }
      
      





PetaPocoが出力するコードは、これと条件付きで同一であり、同じ実行時間で確認されます。

ご覧のように、このクラスはIEqualityComparerインターフェイスも実装しています。この方法で具体化されたオブジェクトでは、EFが具体化するオブジェクトとは異なり、メモリ内でDistinctを作成するために、通常のReferenceEquals比較は機能しなくなります。そのような追加が必要でした。



調査の結果をアイテムテンプレートの形式で設計し、Visual Studioギャラリーに公開しまし 。 使用方法の簡単な説明がそこにあります。 誰かが解決策に興味を持ってくれたら嬉しいです。



All Articles