-
Notifications
You must be signed in to change notification settings - Fork 0
MS_EntityFrameworkConcerns
- 戻る(Entity Framework)
- Entity Framework の懸念
- Entity Framework の調査
Entity Frameworkを利用する上での懸念をまとめてみた。
補足(本ページの読み方): 本ページは EF6 時代の評価であり、
「ADO.NET vs ORM (Entity Framework, Dapper)」の
ORM 側に対する批判をより詳しく展開したもの。
指摘の多くは現在も成立する ORM 一般の論点だが、
EF Coreで改善された点もあるため、
節ごとに「現在どうか」を補足する。
- EntityFramework(CodeFirst)で Join を試してみる : 日曜ゲームクリエータの日記
http://kazenetu.exblog.jp/19246594/ - [Entity Framework] LINQ で JOIN 句を使用して複数テーブルからデータを取得する: ある SE のつぶやき
http://fnya.cocolog-nifty.com/blog/2014/01/entity-framew-6.html - [Entity Framework] LINQ で LEFT JOIN 句を使用して複数テーブルからデータを取得する: ある SE のつぶやき
http://fnya.cocolog-nifty.com/blog/2014/01/entity-framew-7.html
- Entity Framework で Join を使わずに内部結合する - Qiita
http://qiita.com/keidrumfreak/items/f15d36bfdb35ca2dc6b0 - [Entity Framework] LINQ で JOIN 等を使わずに複数テーブルからデータを取得する: ある SE のつぶやき
http://fnya.cocolog-nifty.com/blog/2014/01/entity-framew-5.html
補足(節の意図): 「集計処理のサポート」は見出しのみで本文が無い。
文脈から、GROUP BYを伴う集計を LINQ で表現しきれるかという
懸念を示していると読める。EF Core では、
GroupByの翻訳が長く制約されており、
特に EF Core 3.0 では翻訳できないGroupByが
実行時例外になるケースが多かった
(それ以前は暗黙にクライアント側で評価されていた)。
EF Core 7 以降で翻訳可能な範囲がかなり広がっているが、
複雑な集計は生 SQL やビューに逃がすのが現在も無難である。
全体的に、
- context へのアクセス
- 呼び出す LINQ メソッド
から
- どういう SQL が実行されるのか?
- 内部動作はどのようになっているのか?
などがブラックボックス化されているため、
Entity Framework 自体の仕様に精通していないと、
ハイパフォーマンスな実装をすることができない。
という問題がある。
補足(ブラックボックス性は「見れば」緩和できる): この指摘の本質は
「見えない」ことなので、ログを出せば大幅に改善する。
- EF Core:
optionsBuilder.LogTo(Console.WriteLine)
(開発時のみEnableSensitiveDataLogging()でパラメータ値も出る)- EF6:
context.Database.Log = Console.Write;- DB 側: SQLプロファイラ(SQLトレース)や
クエリ ストア(SQL Server のログ)ただし、「書いた LINQ から発行される SQL を予測できる必要がある」という
本質的な難しさは残る。この点は現在も ORM 全般の弱点である。
foreach でデータアクセスした場合など、
検索 SQL が検索キーや射影の列名を指定しないので性能的に問題になる。
補足:
context.YYYYYsを直接foreachすると
SELECT(全列・全行)が発行される。
絞り込みと射影を LINQ 側で書けばWHERE/ 列指定に翻訳される。// 全列・全行 foreach (var y in context.YYYYYs) { } // WHERE と列指定に翻訳される var rows = await context.YYYYYs .Where(y => y.DeptId == deptId) .Select(y => new { y.Id, y.Name }) .ToListAsync();つまり「EF が列名を指定しない」のではなく、
指定するように書いていないというのが正確なところである。
以下の様な手段により、処理対象の Entity をメモリに起こしてしまう。
この場合、ToArray() メソッドを使って、はじめに結果を確定させる。
var YYYYYs = context.YYYYYs.ToArray();この場合、Load() メソッドを使って、DbContext 内にデータをキャッシュさせる。
context.YYYYYs.Load();この後、context.YYYYYs にアクセスしても SQL は実行されない。
ライブラリがいくつかある。(非 MS 製、NuGet からインストール可能)
-
zzzprojects/EntityFramework.Extended · GitHub
https://github.com/zzzprojects/EntityFramework.Extended -
MikaelEliasson/EntityFramework.Utilities · GitHub
https://github.com/MikaelEliasson/EntityFramework.Utilities -
参考
- Entity Framework でアトミックインクリメント & 一括更新 | C#.NET vs VB.NET
http://csharpvbcomparer.blogspot.com/2015/04/net-ef-atomic-increment-and-bulk-update.html - Entity Framework でもバルク更新したいよね(・∀・) - うさ☆うさ日記
http://d.hatena.ne.jp/machi_pon/20120202/1328187399
- Entity Framework でアトミックインクリメント & 一括更新 | C#.NET vs VB.NET
補足(最新化:一括更新は標準機能になった): この節の懸念は、
EF Core 7 でExecuteUpdate/ExecuteDeleteが追加されたことで解消した。
サードパーティ ライブラリを入れなくても、SQL 1 発で一括処理できる。await context.YYYYYs .Where(y => y.CreatedAt < threshold) .ExecuteDeleteAsync();なお、これらは変更追跡を経由しないため
SaveChangesは不要であり、逆にメモリ上のエンティティとは
同期しない点に注意(Entity Framework参照)。
大量の挿入は現在もSqlBulkCopyが最速である
(SQL Server 大量データ処理時の性能問題)。
- LINQ to Objectは、メモリ上での処理
- LINQ to Entitiesは、DBMS 上での処理
となる。
-
基本的には、LINQ to Objectが高速だが、
-
ストレージが必要な大量データ処理になってくると、
LINQ to Entitiesの方が高速になる可能性がある。
補足(比較の前提): 「LINQ to Object が高速」というのは
既にメモリ上にあるデータに対する演算の話である。
DB 上のデータを扱う場合は、
- LINQ to Entities: DB 側で絞り込み、必要な行・列だけ転送
- LINQ to Objects: 全件転送してからメモリ上で絞り込み
となるため、通信量と DB 側のインデックス活用の差で
ほぼ常に LINQ to Entities が有利である
(SQL Server のインデックス)。
処理される場所だけでなく、処理結果が異なることがある。
- 以下は、DB 側で
Nameでソート(OrderBy)される。
context.YYYYYs.OrderBy(x => x.Name).ToArray()- 以下は DB からロードされた後、メモリ上で
Nameでソート(OrderBy)される。
context.YYYYYs.ToArray().OrderBy(x => x.Name)DBMS の照合順序などの関係により、
処理される場所によって、ソート(OrderBy)順が異なるためである。
移行メモ(誤字): 元ページの
OrberByはOrderByの誤記。
本文中の記述も併せて修正した。
補足(この指摘は重要): これは性能ではなく正しさの問題であり、
現在も完全に成立する。
- DB 側: 照合順序(例
Japanese_XJIS_100_CI_AS)に従う
→ 大文字小文字を区別しない、全角半角を区別しないなど- メモリ側: .NET の
StringComparer(既定はカルチャ依存)に従う「ページングは DB 側、表示順の微調整はメモリ側」のように
ソートの場所が混在すると順序が破綻する。
ページング(Skip/Take)を伴う場合は特に致命的で、
同じ行が 2 ページに出たり欠けたりする。
AsNoTracking() メソッドで Collection を取得すると、
Entity Statesのトラッキングをしなくなる。
var YYYYYs = context.YYYYYs.AsNoTracking().ToArray();これにより、使用するメモリ量を削減できる。
補足: メモリだけでなく、変更追跡のためのスナップショット作成が
省かれるため CPU も削減でき、参照専用のクエリでは体感差が出る。
ただし、AsNoTracking()で取得したエンティティをUpdate()すると
全列が更新対象になる点に注意
(Entity Framework参照)。
EF Core ではQueryTrackingBehavior.NoTrackingで既定値を変更することもできる。
-
クラス名(+s) がテーブル名にマップされる。
-
プロパティ名がカラム名にマップされる。
-
主キーは
idor クラス名 +idがデフォルト。 -
参考
- Entity Framework Code First | densan-labs.net
http://densan-labs.net/tech/codefirst/index.html
- Entity Framework Code First | densan-labs.net
コードファーストのデータモデル クラスの変更をもとに、
既存のデータを残したままデータベースのテーブルを変更する機能。
-
Entity Framework Code First Migrations
https://learn.microsoft.com/ja-jp/ef/ef6/modeling/code-first/migrations/ -
- データベース マイグレーション | densan-labs.net
http://densan-labs.net/tech/codefirst/migration.html
- データベース マイグレーション | densan-labs.net
コードファーストでは規約に基づいて Entity と Database を紐付ける。
属性による規約。
| 項番 | 属性名 | 説明 |
|---|---|---|
| 1 | Table | Entity と Table 間のマッピング |
| 2 | Column | Entity の Property と Table の Column 間のマッピング |
| 3 | ComplexType | Entity の複数の Property(Properties) と Table 間のマッピング ComplexType の複数の Property(Properties) は、別の Table に切り出される。 |
| 4 | Key | Entity の Property を対応する Table の PrimaryKey に設定する。 |
| 5 | Required | Entity の Property に対応する Table の Column を NOT NULL に設定する。 MVC の ModelMetadata として検証処理に使用される。 |
| 6 | MaxLength | Entity の Property に対応する Table の Column のデータ長を設定する。 MVC の ModelMetadata として検証処理に使用される。 |
| 7 | Index | Entity の Property に対応する Table の Column を使用した非クラスタ化・インデックスを作成する。 |
| 8 | NoMapped | Entity の Property を Database にマップしない。 |
その他、DbContext を継承した Context のコンストラクタで接続文字列を決定できる。
移行メモ(正誤): 項番 8 の属性名は
NoMappedではなくNotMappedが正しい。
また、EF Core では[Index]属性は
一時期廃止されたのち EF Core 5 で復活しており
([Index(nameof(Prop))]としてクラスに付与)、
EF6 の書き方(プロパティに付与)とは異なる。
ComplexTypeは EF Core では長く未サポートで、
EF Core 8 で複合型(Complex Types)として再導入された。
Fluent API による規約。
-
Fluent API と DbContext の機能 - @IT
http://www.atmarkit.co.jp/fdotnet/ef4basic/ef4codefirst03/ef4codefirst03_01.html -
Entity Framework Fluent API - プロパティと型の構成/マッピング
https://learn.microsoft.com/ja-jp/ef/ef6/modeling/code-first/fluent/types-and-properties
補足(属性と Fluent API の使い分け): 両方使える場合、
Fluent API が優先される。
属性はエンティティ クラスに永続化の知識を持ち込むため、
ドメイン モデルを純粋に保ちたい場合は
IEntityTypeConfiguration<T>に分離するのが定石である。
エンティティ間の関連(アソシエーション)を表す。
- x 対一は、オブジェクト参照を使用。
- x 対多は、
ICollection<T>を使用。
補足(遅延読み込みの既定が違う): EF6 は
ナビゲーション プロパティをvirtualにすると遅延読み込みが既定で有効だったが、
EF Core では既定で無効である
(Microsoft.EntityFrameworkCore.Proxiesを導入して
UseLazyLoadingProxies()を呼ぶと有効化できる)。遅延読み込みは N+1 問題の主要因であるため、
EF Core の既定(明示的なInclude)のほうが安全側に倒れている。
-
性能と柔軟性が重視されるエンタープライズの領域では、使い難いとの批判も多い模様。
-
ツール等のスタンドアロンのプログラム、情報系システムなどの EUC による
RAD 開発などにはマッチする可能性もある。 -
また、ADO.NET は枯れた技術であるのに対し、Entity Framework は進化の余地がある。
- Entity Framework の歴史を振り返る - kendik.net
http://kendik.hatenablog.com/entry/2013/05/24/033400 - Entity Framework のバージョン履歴
https://learn.microsoft.com/ja-jp/ef/ef6/what-is-new/past-releases
- Entity Framework の歴史を振り返る - kendik.net
-
ADO .NET Entity Framework Vote of No Confidence
http://efvote.wufoo.com/forms/ado-net-entity-framework-vote-of-no-confidence/ -
ADO.NET Entity Framework Taking Some Heat
http://www.infoq.com/news/2008/06/entity-framework-heat -
Is ORM (Linq, Hibernate...) really that useful - Stack Overflow
http://stackoverflow.com/questions/938524/is-orm-linq-hibernate-really-that-useful -
Entity Framework VS LINQ to SQL VS ADO.NET with stored procedures - Stack Overflow
http://stackoverflow.com/questions/2698151/entity-framework-vs-linq-to-sql-vs-ado-net-with-stored-procedures -
データベース ファースト 恵みの波紋
https://ogacha.wordpress.com/tag/%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9-%E3%83%95%E3%82%A1%E3%83%BC%E3%82%B9%E3%83%88/
補足(信任投票の顛末): 2008 年の "Vote of No Confidence" は、
EF v1 が「永続化非依存(Persistence Ignorance)を欠く」
「モデル駆動が過剰」といった点を批判したもの。
その後、EF 4.1 の Code First と POCO サポートによって
批判の中心的な論点はほぼ解消された。
歴史的経緯として読むべき節である。
Entity Framework をキャンセルしたASP.NET Identityの
UserStoreを実装して「ORM の問題だな。」と思った点は、
プログラムの
- インターフェイスとなっている親子階層のあるオブジェクト・モデルと、
- ストレージとの同期処理(セーブ・ロード)のタイミングなどが、
実装者にとって「難しい。」ってトコロではないかと思う。
-
難しさの背景は、
-
自分でUserStoreを実装していてもこの同期は難しい。
- 同期処理の仕様を決める必要がある。
- 同期処理の仕様を思い出し、
処理全体との繋がりを理解する必要がある。
-
ORM プロダクトでブラックボックス化した場合、
- 同期処理の仕様を決める必要は無いが、
- 同期処理の仕様を調べたり、デバッガでリバースして
(Entity Framework の調査)、
処理全体との繋がりを理解する必要がある。
等と言った点だと思う。
-
-
※
-
UserStoreは、Memory モードと DBMS モードを処理するので、
Memory モードの実装の後、DBMS モードを実装しようとしたタイミングで
この難しさに気付く。 -
オブジェクトを操作したタイミングで直ちに同期される仕様にすれば、
問題はなくなるが、実際は、性能云々に書いたように、
性能を考慮して適切なタイミングに同期する必要がある。
-
補足(この難しさの正体): ここで述べられているのは、
オブジェクト グラフの永続化タイミングという、
ORM の本質的な難所である。
EF のSaveChanges()や JPA の flush が
「いつ、どこまでを書き込むのか」を理解しにくいのと同根で、
Unit of Work パターンが抱える固有の複雑さと言える。実務での緩和策は、
- 集約(Aggregate)の境界を小さく保つ
親子階層を深くしないDbContextの寿命を短くする
ASP.NET Core の Scoped(リクエスト単位)が既定なのはこのため- 読み取りと書き込みでモデルを分ける(CQRS 的な割り切り)
あたりになる。
基幹系システムでは以下の理由でミスマッチと判断されることが多い。
概念モデルに対してプログラミングを採用している。
- 外部スキーマを使用できないため、論理データ独立性が無い。
- 概念スキーマのデータ構造を使用してプログラミングを行う必要がある。
-
DBMS のスキーマからモデル(EDM)を生成する方法を採用している場合、
スキーマの構造が変わった場合、モデル(EDM)の作り直しが発生する。 -
生成されたモデル(EDM)をカスタマイズしていた場合、
作り直しにより、カスタマイズが破棄されてしまう可能性がある。- カスタマイズがなければ、概念スキーマ構造の変更を
モデル(EDM)に同期し、変更に迅速に対応できるとも言える。 - しかし、論理データ独立性が無いため、プログラムの変更は必要になる。
- カスタマイズがなければ、概念スキーマ構造の変更を
-
RAD 開発ツールに特有の、保守性の悪さがある。
補足(EDMX が無くなったことによる変化): EF Core には EDMX が無く、
リバース エンジニアリングはdotnet ef dbcontext scaffoldによる
C# コード生成である。
生成先をpartial classとして扱い、カスタマイズを別ファイルに置けば、
再 scaffold でカスタマイズが失われる問題は緩和できる。
ただし「論理データ独立性が無い」という指摘そのものは、
エンティティ=テーブルという対応を採る限り現在も成立する。対処としては、
- DB 側にビューを作り、それをエンティティにマップする
(EF Core のToView()。外部スキーマに相当する層を DB 側で持つ)- 読み取り専用の DTO へ直接射影する(
Selectで必要な形に整形)といった形で、外部スキーマ的な層を別途設けることになる。
SQL が、LINQ to Entity のエンジンに生成される形であり、
また、内部実装とその動作がブラックボックスになっているため(※1)
それらが明確にならないと設計、チューニング、問題分析などが困難。
従って(特に日本の)エンタープライズ・アプリケーションでは敬遠されている。
これは、Java の Java8 で JPA で Hibernate で Jinq が敬遠されるのと ≒。
- (※1)Questions about EntityFramework
https://github.com/OpenTouryoProject/SampleProgram/issues/6
https://www.osscons.jp/jowlrb9pr-537/
- Entity Framework、RDB より NoSQL のほうがマッチしそう。
- しかし、長々、NoSQL 用の Entity Framework プロバイダがリリースされなかった。
- 2018 年に EF コアが SQL データベースと NoSQL データベースを統一したらしい。
- ...しかし、まったく流行っていない感。
補足(最新化:Cosmos DB プロバイダ): ここで言及されているのは
EF Core の Azure Cosmos DB プロバイダ(EF Core 3.0 で正式提供)である。
現在も提供・改善が続いているが、
- リレーショナル固有の機能(結合、マイグレーション等)は使えない
- Cosmos DB 側の API(パーティション キー、RU 消費)を意識する必要があり、
結局「抽象化できていない」ため、NoSQL では各サービスの SDK を直接使うのが主流のままである。
「まったく流行っていない感」という観察は、現在も概ね妥当と言える。
補足(現在の総括): 本ページの懸念のうち、
懸念 現在 一括更新ができない 解消( ExecuteUpdate/ExecuteDelete)遅延読み込みによる N+1 緩和(EF Core は既定で無効) EDMX の作り直し 解消(EDMX 自体が無い) 信任投票の論点(POCO / 永続化非依存) 解消(Code First) 発行 SQL が見えない 緩和(ログ・クエリ ストア) 論理データ独立性が無い 残る(ビューや DTO で別途対処) 複雑な集計・動的クエリの表現力 残る(生 SQL との併用が前提) 結論として、現在は排他ではなく併用が定石である。
大半の CRUD は EF Core、性能が要る箇所と複雑な参照系は
Dapperか生の ADO.NET、という構成が広く採られている
(ADO.NET vs ORM (Entity Framework, Dapper))。
Tags: 移行, .NET開発, データアクセス, ADO.NET, Entity Framework, 性能
このWikiは「Open棟梁Project」,「OSSコンソーシアム 開発基盤部会」によって運営されています。