CodeFirstする時にちょっと注意すべきコト


クライアントアプリケーションを作成するときなど、インプロセスなRDBMSとして有用なSQL Server CompactEditonとEntityFrameworkを使用することで、SQLを考えること無くデータベースを生成、利用出来ます。

これらの手法は非常に便利かつLINQも扱えるので強力ですが、いくつか注意しなければならないことがあります。

INSERT・DELETE・UPDATE相当の処理が遅い

これは、1レコード毎にSQL文が生成されてしまうことに起因します。
ユーザーの応答に応じた比較的少量のこれらの操作を行うことは特に問題とはならないでしょうが、例えば初期生成時に大量のレコードをINSERTする時などは、処理の非同期化・Commandを利用したExecute等考慮が必要かもしれません。

クエリの結果参照されたレコードはローカルビューに格納される

生成されたIDbSetを対象にLINQによる問い合わせを多数回行うことは比較的良くあるでしょう。
しかし、アプリケーションのライフサイクルと、DbContextから派生させたカスタムコンテキストクラスのインスタンスのライフサイクルを一致させてしまうのはあまり好ましくないかもしれません。
マップ先テーブルのレコード数がさして多くない、又はアプリケーションライフサイクル中に参照されるレコード範囲が局所化されているというシナリオであればライフサイクルを一致させても問題にはなりにくいと考えます。しかし、レコード数が大量で、問い合わせ毎の範囲が広範に及ぶ場合、主にメモリリソース、副次的にはSaveChangeメソッドの実行にインパクトを与え得ます。

この理由は、任意のレコードを参照した場合、カスタムコンテキストのインスタンスが破棄されるまで、ローカルビューに格納され続けます。従って、問い合わせ単位の参照数が少量でも、頻度が多くかつクエリの結果に重複が少ない場合、結果として大量のローカルビューを抱え込むコトになってしまうのです。

また、SaveChanbeメソッド実行時にローカルビューに格納済みの値が変化したか否か網羅的に検査する必要があることから、結果としてSaveChangeの実行効率が悪化する可能性があります。

ローカルビューにどのくらいのレコードが格納されているか確認するには、カスタムコンテキストに作成したIDbSet.Local.Countで取得可能です。

また、この問題の解決方法として、処理単元ごとに、カスタムコンテキストのインスタンスに対する生成と破棄を行う、又は先に挙げたCountを監視することで、肥大化した場合にカスタムコンテキストのインスタンスの生成と破棄を行う等が上げられるかと思います。

また、変更を伴わない、問い合わせのみの場合、AsNotraking()を利用することで、ローカルビューに格納されること無く、結果を受け取ることが可能です。
(後に@okazuki先生に教えていただきました。ありがとうございます。)

まとめ

EntityFrameworkを利用して、コードファーストでデータベースを操作することは、インピーダンスミスマッチを解消し、重要なテーマにフォーカスを当て続けることが出来るという面において非常に有用かつ強力な手段だと思います。ですが、一般的なデータベースへの操作、又はLINQtoObjectと同じ操作を行ってしまった場合、理論的には問題は無くとも、パフォーマンスが悪化する、リソースを過度に消費してしまう問題はどうしても避けて通れないと思います。

但し、EntityFrameworkの持つパフォーマンス的な制約をある程度理解するコトで、効率的で保守がしやすく、拡張性の高い開発が行えるのではないでは無いかと考えています。