web-dev-qa-db-ja.com

エンティティフレームワークでの複数のSaveChanges呼び出し

エンティティフレームワークに基づいて独自のカスタムリポジトリを構築しています。一部の拡張モデルをエンティティモデルとして保存できるようにする拡張メソッドを作成し、独自の追加および更新メソッドを構築しています。

現在、各メソッドには、最後に呼び出されるDbContextからのSaveChanges()があります。つまり、すべてのモデルに対して1つの呼び出しが呼び出されます。

私はMVC4サイト用にこの基本DALパターンを構築しています。これはほとんどの場合1つのモデルにアクセスすることを意味しますが、そうである必要はありません。

3つのエンティティを更新するときに各モデルのSaveChanges()を呼び出すことはあまりにも悪い習慣ですか?最初にオブジェクトコンテキストにすべてを追加し、何らかのトランザクションのコミットとしてSaveChanges()を行う必要がありますか?

22
Admir Tuzović

私はそれが一種の遅い答えであることを知っていますが、私はそれを共有することが有用であるとわかりました。

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以降にあります

67
Wahid Bitar

関連するエンティティを単一のトランザクションで保持する必要がある場合、SaveChangesを複数回(トランザクションスコープなしで)呼び出すのは悪い習慣です。作成したのは、漏れやすい抽象化です。別の作業単位クラスを作成するか、ObjectContext/DbContext自体。

6
Eranga

各メソッドでSaveChanges()を呼び出すことを強くお勧めします。リポジトリパターンと作業単位を使用することは、より良い方法です。作業単位により、db呼び出しの効率が向上し、一部のデータが無効な場合(たとえば、ユーザーの詳細は問題ないがアドレスが失敗した場合)にdbが汚染されないようになります。

役立つチュートリアルを紹介します。

http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-the-repository-and-unit-of-work-patterns-in-an- asp-net-mvc-application

3
Colin Bacon

これは、現在使用している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();

        }
    }
}
2
Hung Quach

このようなシナリオでは、 ここで説明 のような新しい最新のアプローチが推奨されます。

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);
    }
}
0
Korayem