エンティティフレームワークに基づいて独自のカスタムリポジトリを構築しています。一部の拡張モデルをエンティティモデルとして保存できるようにする拡張メソッドを作成し、独自の追加および更新メソッドを構築しています。
現在、各メソッドには、最後に呼び出されるDbContextからのSaveChanges()があります。つまり、すべてのモデルに対して1つの呼び出しが呼び出されます。
私はMVC4サイト用にこの基本DALパターンを構築しています。これはほとんどの場合1つのモデルにアクセスすることを意味しますが、そうである必要はありません。
3つのエンティティを更新するときに各モデルのSaveChanges()を呼び出すことはあまりにも悪い習慣ですか?最初にオブジェクトコンテキストにすべてを追加し、何らかのトランザクションのコミットとしてSaveChanges()を行う必要がありますか?
私はそれが一種の遅い答えであることを知っていますが、私はそれを共有することが有用であるとわかりました。
EF6では、dbContext.Database.BeginTransaction()
を使用することでこれを簡単に達成できます
このような :
using (var context = new BloggingContext())
{
using (var dbContextTransaction = context.Database.BeginTransaction())
{
try
{
// do your changes
context.SaveChanges();
// do another changes
context.SaveChanges();
dbContextTransaction.Commit();
}
catch (Exception ex)
{
//Log, handle or absorbe I don't care ^_^
}
}
}
詳細については this をご覧ください
再びEF6以降にあります
関連するエンティティを単一のトランザクションで保持する必要がある場合、SaveChanges
を複数回(トランザクションスコープなしで)呼び出すのは悪い習慣です。作成したのは、漏れやすい抽象化です。別の作業単位クラスを作成するか、ObjectContext/DbContext
自体。
各メソッドでSaveChanges()を呼び出すことを強くお勧めします。リポジトリパターンと作業単位を使用することは、より良い方法です。作業単位により、db呼び出しの効率が向上し、一部のデータが無効な場合(たとえば、ユーザーの詳細は問題ないがアドレスが失敗した場合)にdbが汚染されないようになります。
役立つチュートリアルを紹介します。
これは、現在使用しているUnitOfWorkを使用して複数のcontext.SaveChanges()
を処理する別のアプローチです。
この最後のメソッドが呼び出されるまで、hold all context.SaveChanges()
メソッドを使用します。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace DataAccess
{
public class UnitOfWork : IUnitOfWork
{
private readonly Context context;
private readonly Dictionary<Type, object> repositories = new Dictionary<Type, object>();
private int beginChangeCount;
private bool selfManagedTransaction = true;
public UnitOfWork(Context context)
{
this.context = context;
}
//Use generic repo or init the instance of your repos here
public IGenericRepository<TEntity> GetRepository<TEntity>() where TEntity : BaseEntityModel
{
if (repositories.Keys.Contains(typeof(TEntity)))
return repositories[typeof(TEntity)] as IGenericRepository<TEntity>;
var repository = new Repository<TEntity>(context);
repositories.Add(typeof(TEntity), repository);
return repository;
}
public void SaveChanges()
{
if (selfManagedTransaction)
{
CommitChanges();
}
}
public void BeginChanges()
{
selfManagedTransaction = false;
Interlocked.Increment(ref beginChangeCount);
}
public void CommitChanges()
{
if (Interlocked.Decrement(ref beginChangeCount) > 0)
{
return;
}
beginChangeCount = 0;
context.SaveChanges();
selfManagedTransaction = true;
}
}
}
使用サンプル。
以下のコードでコメントを見つけてください
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
namespace BusinessServices.Domain
{
public class AService : BaseBusinessService, IAService
{
private readonly IBService BService;
private readonly ICService CService;
private readonly IUnitOfWork uow;
public AService (IBService BService, ICService CService, IUnitOfWork uow)
{
this.BService = BService;
this.CService = CService;
this.uow = uow;
}
public void DoSomeThingComplicated()
{
uow.BeginChanges();
//Create object B - already have uow.SaveChanges() inside
//still not save to database yet
BService.CreateB();
//Create object C - already have uow.SaveChanges() inside
//still not save to databse yet
CService.CreateC();
//if there are no exceptions, all data will be saved in database
//else nothing in database
uow.CommitChanges();
}
}
}
このようなシナリオでは、 ここで説明 のような新しい最新のアプローチが推奨されます。
TransactionScope
クラスに精通している場合は、DbContextScope
の使用方法をすでに知っています。本質的には非常に似ています-唯一の違いは、DbContextScope
がデータベーストランザクションではなくDbContext
インスタンスを作成および管理することです。ただし、TransactionScope
と同様に、DbContextScope
はアンビエントであり、ネストでき、ネスト動作を無効にでき、非同期実行フローで正常に動作します。
public void MarkUserAsPremium(Guid userId)
{
using (var dbContextScope = _dbContextScopeFactory.Create())
{
var user = _userRepository.Get(userId);
user.IsPremiumUser = true;
dbContextScope.SaveChanges();
}
}
DbContextScope
内では、スコープが管理するDbContext
インスタンスに2つの方法でアクセスできます。次のようなDbContextScope.DbContexts
プロパティを介して取得できます。
public void SomeServiceMethod(Guid userId)
{
using (var dbContextScope = _dbContextScopeFactory.Create())
{
var user = dbContextScope.DbContexts.Get<MyDbContext>.Set<User>.Find(userId);
[...]
dbContextScope.SaveChanges();
}
}
しかし、それはもちろんDbContextScope
を作成したメソッドでのみ利用可能です。アンビエントDbContext
インスタンスに他の場所(リポジトリクラスなど)にアクセスする必要がある場合、次のように使用するIAmbientDbContextLocator
に依存するだけです。
public class UserRepository : IUserRepository
{
private readonly IAmbientDbContextLocator _contextLocator;
public UserRepository(IAmbientDbContextLocator contextLocator)
{
if (contextLocator == null) throw new ArgumentNullException("contextLocator");
_contextLocator = contextLocator;
}
public User Get(Guid userId)
{
return _contextLocator.Get<MyDbContext>.Set<User>().Find(userId);
}
}