MS_EntityFrameworkResearch - NetDevInfraWGinOSSConsortium/NetDevInfraWiki GitHub Wiki

Entity Framework の調査

抂芁

Entity Framework の懞念に察する調査結果。

Entity Frameworkのメモリ䜿甚量の䟋

  • ナヌザヌ定矩の DbContext を生成
  • SqlQuery メ゜ッドで最小限のデヌタを取埗
  • DbContext ぞ栌玍
  • DbContext のラむフタむムの管理

ナヌザヌ定矩のDbContext

䟋えば、ProductContext クラスは

  • DbContext クラスから掟生する必芁がある。
  • DbSet のプロパティを含める必芁がある。
  • DbSet プロパティは、コンテキストで指定された゚ンティティのコレクションを衚す。
public class ProductContext : DbContext
{
  public DbSet<Category> Categories { get; set; }
  • Entity Framework Designer を䜿甚しおいる堎合、
    コンテキストは、Visual Studio のテンプレヌトによっお自動的に生成される。
  • コヌドファヌスト テクニックを䜿甚しおいる堎合は、手動でコンテキストを䜜成する。

DbContextのラむフタむム

メモリのデヌタ保持は、DbContext オブゞェクト むンスタンスのラむフタむムず同じ。

以䞋のように、using を䜿うず、ラむフタむムは明確になる。

using (ProductContext context = new ProductContext())
{
  // Perform data access using the context
}

補足ラむフタむムは短く保぀: DbContext は
倉曎远跡のために読み蟌んだ゚ンティティをすべお保持し続けるため、
長寿呜にするずメモリを食い、キャッシュが叀くなる。
たた、スレッド セヌフではない。

ASP.NET Core では AddDbContext の既定が Scopedリク゚スト単䜍で、
この原則がフレヌムワヌク偎で担保されおいる。
シングルトンにしたりバックグラりンド凊理で䜿い回したりしおはならない
IDbContextFactory<T> を䜿っお郜床生成する。

SqlQueryメ゜ッド

using (ProductContext context = new ProductContext())
{
  // Below code is enumerated using ToList() method.
  // Stores all records in “myCategories” object (Memory)
  IList<Category> myCategories =
  context.Database.SqlQuery<Category>("Select * from Category").ToList();
}

補足最新化EF Core での生 SQL: EF Core では
Database.SqlQuery<T> の代わりに以䞋を䜿う。

甹途 EF6 EF Core
゚ンティティ型を返す Set<T>().SqlQuery(...) Set<T>().FromSql($"...")
任意の型・スカラヌを返す Database.SqlQuery<T>(...) Database.SqlQuery<T>($"...")EF Core 8 以降
曎新系 Database.ExecuteSqlCommand(...) Database.ExecuteSql($"...")

重芁: EF Core の FromSql / ExecuteSql は
FormattableString を受け取り、補間した倀を自動的にパラメヌタ化する。

// OK: @p0 ずしおパラメヌタ化される
var rows = await context.Categories
    .FromSql($"SELECT * FROM Category WHERE Id = {id}")
    .ToListAsync();

文字列を自分で連結しお枡す FromSqlRaw / ExecuteSqlRaw は
SQL むンゞェクションの危険があるため、原則䜿わない。

SaveChangesメ゜ッド䜿甚時のコネクションやトランザクション

  • 各トランザクションのための新しい接続を䜜成する。
  • SaveChanges メ゜ッドが呌び出されたずき内郚でトランザクションを維持する。
  • 䞀回の SaveChanges メ゜ッドで耇数゚ンティティ曎新のトランザクションを維持する。

Entity Framework 5.0およびそれ以前のバヌゞョン

TransactionScope

TransactionScopeを䜿甚しおトランザクションを凊理する。

using (EntitiesContext context = new EntitiesContext())
{
  using (TransactionScope scope = new TransactionScope())
  {
    //Code here
  }
}

補足: このコヌドは scope.Complete() の呌び出しが省略されおいるため、
このたたでは必ずロヌルバックされる説明甚の骚栌ず読むべき。
たた、TransactionScope の既定の分離レベルは Serializable であり、
明瀺指定が掚奚される。詳现はTransactionScopeを参照。

手動の制埡も可胜

ASP.NET MVC で DbContext のトランザクションず
独自 SQL 凊理のトランザクションを同䞀のスコヌプにする
http://blog.makotoishida.com/2012/12/aspnet-mvcdbcontextsql.html

Entity Framework 6.0

トランザクションを維持するために、2 ぀の新しい API を導入しおいる。

DbContext.Database.BeginTransaction API

  • トランザクションを開始する。
  • トランザクションの分離レベルを指定するこずができる。
  • いく぀かの操䜜を組み合わせお、同じトランザクション内で結合するこずができる。
  • したがっお、党おのトランザクションをコミットたたはロヌルバックするこずができる。
using (EntitiesContext context = new EntitiesContext())
{
  using (var transaction = context.Database.BeginTransaction())
  {
    try
    {
      EmployeeMaster employee = new EmployeeMaster();

      employee.Code = "A0001";
      employee.Name = "Jignesh Trivedi";
      employee.DepartmentId = 1;

      context.Employees.Add(employee);
      context.SaveChanges();

      DepartmentMaster dept = new DepartmentMaster();

      dept.Code = "DEP0001";
      dept.Name = "Department 1";

      context.Departments.Add(dept);
      context.SaveChanges();

      transaction.Commit();
    }
    catch (Exception ex)
    {
      transaction.Rollback();
    }
  }
}

補足catch での Rollback は䞍芁: using を抜ける際、
コミットされおいないトランザクションは Dispose で自動的に
ロヌルバックされるため、catch 内の明瀺的な Rollback() は本来䞍芁である
Dapperの節にある指摘ず同じ。

たた、䞊蚘のように䟋倖を握り぀ぶす catch (Exception ex) は
呌び出し元が゚ラヌを怜知できなくなるため、
実務では再スロヌするか、そもそも catch を曞かない。

DbContext.Database.UseTransaction API

  • Entity Framework 倖で明蚘されたトランザクションを䜿甚する
    DbContext むンスタンスを蚱可する。
  • Entity Framework で、この API を䜿甚し任意の既存トランザクションを䜿甚するこずができる。
using (SqlConnection con = new SqlConnection("connectionString"))
{
  con.Open();
  using (var transaction = con.BeginTransaction())
  {
    // Do something....

    //Pass this transaction to Entity Framework....
    using (EntitiesContext context = new EntitiesContext(con, false))
    {
      context.Database.UseTransaction(transaction);
      EmployeeMaster employee = new EmployeeMaster();
      employee.Code = "A0001";
      employee.Name = "Jignesh Trivedi";
      employee.DepartmentId = 1;
      context.Employees.Add(employee);

      context.SaveChanges();
    }
  }
}

補足UseTransaction の䟡倀: これは
EF ず Dapper / 生の ADO.NET を同䞀トランザクションで䜵甚する
ための API である。
接続を共有できるため、MS-DTCぞの昇栌を䌎わずに枈む
TransactionScopeの昇栌の話を参照。

ADO.NET vs ORM (Entity Framework, Dapper)で述べた
「EF ず Dapper のハむブリッド構成」を成立させる芁の機胜ず蚀える。
なお、コンストラクタの第 2 匕数 false は
「接続の所有暩を DbContext に枡さない」
DbContext の砎棄時に接続を閉じない、ずいう意味である。

ExecuteSqlCommandメ゜ッドの実行時の挙動

Entity Framework で曎新凊理を実行する堎合、
基本的には、SaveChanges メ゜ッドを䜿甚する。

ExecuteSqlCommand実行埌、゚ンティティずDBは䞍䞀臎状態に陥る

ExecuteSqlCommand メ゜ッドは、

  • コンテキスト内の゚ンティティの内容に関係なく、
    INSERT、UPDATE、DELETE などのク゚リを実行できる。

  • このため ExecuteSqlCommand メ゜ッドの実行埌は、
    ゚ンティティず DB の内容は䞀臎しおいない。

  • 埓っお、゚ンティティず DB の内容を䞀臎させる必芁がある堎合、
    コンテキスト内の゚ンティティをリフレッシュする必芁がある。

゚ンティティをリフレッシュする方法゚ンティティずDBの同期

゚ンティティをデタッチし再床問い合わせる。

((IObjectContextAdapter)context).ObjectContext.Detach(myCategory);

Refreshメ゜ッドでリフレッシュする。

ObjectContext context = ((IObjectContextAdapter)myDbContext).ObjectContext;
// Refresh specific Entity object in the context
context.Refresh(System.Data.Objects.RefreshMode.StoreWins, myCategory);

(OR)

// Refresh All Entities in the context.
var refreshableObjects = myDbContext.ChangeTracker.Entries().Select(c => c.Entity).ToList();
context.Refresh(System.Data.Objects.RefreshMode.StoreWins, refreshableObjects);

補足最新化EF Core での同期: EF Core には
ObjectContext / Refresh が存圚しない。代わりに、

// 個別の゚ンティティを DB の倀で䞊曞き
await context.Entry(myCategory).ReloadAsync();

// 远跡状態をすべお砎棄
context.ChangeTracker.Clear();   // EF Core 5 以降

を䜿う。

なお、EF Core 7 以降の ExecuteUpdate / ExecuteDelete も
同じく倉曎远跡を経由しないため、本節ず同じ䞍䞀臎が起きる。
「䞀括曎新した埌、同じ DbContext で読み盎すず叀い倀が返る」
ずいう事故に぀ながるので、

  • 䞀括曎新は専甚の短呜な DbContext で行う
  • もしくは実行埌に ChangeTracker.Clear() する

のが定石である。

Entity Data Model以䞋EDMのベスト・プラクティス

曎新凊理

  • EDM を耇数の゚ンティティに統合した堎合、
    単䞀の SaveChanges メ゜ッドの呌び出しで容易に耇数の゚ンティティの曎新が可胜。
  • EDM を耇数の゚ンティティに分割した堎合、
    SaveChanges メ゜ッドを耇数回呌び出す必芁があり、
    接続ずトランザクションの明瀺的な管理が必芁。

EDMのサむズ

DB に、50100 のテヌブルが含たれおいる堎合、
すべおの゚ンティティを含む 1 ぀の倧きな EDM を持぀こずは良い方法ではない。

耇数゚ンティティを単䞀EDMに結合する

耇数゚ンティティを 1 ぀の倧きい EDM に集玄するず、
䞋蚘のようないく぀かの問題を起こす。

  • 性胜の問題

    • メタデヌタ ロヌド時間の性胜
    • ビュヌ生成における性胜
  • 雑然ずする。

    • デザむナヌ
    • むンテリセンス
    • CLR ネヌムスペヌス

EDMを任意の単䜍に分割

䞊蚘を回避するため、
ベスト・プラクティスは任意の単䜍に Entity Data モデルを分割する方法である。

補足最新化EDMX が無くなっおも論点は残る: EF Core には EDMX が無いため
「ビュヌ生成の性胜」ずいう問題は消えたが、
DbContext の粒床ずいう論点は残る。

  • 1 ぀の巚倧な DbContext は、初回のモデル構築起動時が遅くなる
  • 業務境界ごずに DbContext を分けるず、
    • モデル構築が軜くなり、責務も明確になる
    • ただしトランザクションをたたぐ堎合は接続の共有が必芁
      前述の UseTransaction / EF Core では UseTransaction + 同䞀接続

マむクロサヌビス的に境界を切るなら分割、
単䞀 DB のモノリスなら 1 ぀に、ずいうのが実務的な萜ずし所である。
なお、EF Core 6 以降はコンパむル枈みモデルdotnet ef dbcontext optimizeで
起動時のモデル構築コストを倧幅に削枛できる。

動的SQLを実装する方法

https://github.com/OpenTouryoProject/SampleProgram/issues/3

System.Linq.Dynamic

Expression Tree

context.Database.SqlQuery

補足3぀の手段の䜿い分け: 芋出しのみだが、芁点を補足する。

手段 内容 向き
System.Linq.Dynamic珟圚は System.Linq.Dynamic.Core Where("Name == @0", name) のように文字列で LINQ を曞く 怜玢条件が実行時に決たる画面
匏朚Expression Tree Expression<Func<T,bool>> を組み立おお合成する 型安党に条件を積み䞊げたい堎合
Database.SqlQuery / FromSql 生 SQL を組み立おる SQL 自䜓を動的に線集したい堎合

珟実的には、条件の有無で Where を積み䞊げるだけで
倧半の芁件は満たせるIQueryable は遅延評䟡なので、
最埌に ToListAsync() するたで SQL は発行されない。

var q = context.Employees.AsQueryable();
if (!string.IsNullOrEmpty(name))  q = q.Where(e => e.Name.Contains(name));
if (deptId is int d)              q = q.Where(e => e.DepartmentId == d);
var rows = await q.ToListAsync();

それを超える「パラメタセットによっお SQL 定矩そのものを線集する」芁件は、
ADO.NET vs ORM (Entity Framework, Dapper)で
述べられおいるずおり ORM では衚珟しきれず、
動的パラメタラむズド・ク゚リOTR_DynamicParameterizedQuery.mdのような
専甚の仕組みが必芁になる。

参考

キャッシュの問題

補足EF の「キャッシュ」は 2 皮類: 混同されやすいので敎理する。

皮類 内容
第 1 レベル キャッシュ倉曎远跡 DbContext が読み蟌んだ゚ンティティを保持する。Find は先にここを芋る。無効化できない
ク゚リ プラン キャッシュ LINQ 匏 → SQL の倉換結果をアプリ内でキャッシュする

参考リンクの「キャッシュの問題」は前者の話で、
「別の凊理が DB を曎新したのに、DbContext は叀い倀を返す」ずいう珟象。
察策は**DbContext を長生きさせない**こずに尜きる
読み取り専甚なら AsNoTracking() も有効。

Entity Dataモデルのサむズ

性胜

補足EF Core の性胜チェックリスト: 珟圚抌さえるべき項目。

項目 内容
AsNoTracking() 参照専甚のク゚リでは必ず付ける
射圱Select 必芁な列だけ取る。DTO ぞ盎接射圱する
Include の蚭蚈 N+1 を避ける。ただし過剰な Include は行の重耇カルテシアン爆発を招く。EF Core 5 以降は AsSplitQuery() で分割できる
䞀括凊理 ExecuteUpdate / ExecuteDelete、倧量挿入は SqlBulkCopy
コンパむル枈みク゚リ EF.CompileAsyncQuery でホット パスの倉換コストを削枛
コンパむル枈みモデル dotnet ef dbcontext optimize で起動時間を短瞮
ログ確認 発行 SQL を必ず目芖するSQL Server のログ

Tags: 移行, .NET開発, デヌタアクセス, ADO.NET, Entity Framework, 性胜

⚠ **GitHub.com Fallback** ⚠