4

は私がしたい2つの理由 Entity Frameworkでリポジトリのトランザクションを実装するにはどうすればよいですか?

  1. のための私のアプリケーションでのリポジトリの設計パターンデカップル私はいくつかの点

  2. でEntity Frameworkのを使用しないことを決定する場合におけるエンティティから自分のアプリケーションを利用しようとしています

    は私がモデル

I成功裏のセットアップと相互作用して、リポジトリのパターンを使用するロジックができ、再利用になりたいです。しかし、私はトランザクションに対処するために1つの複雑さを持っています。

私はリポジトリへの複数の呼び出しを行い、コミットまたはロールバックできるようにトランザクションを使用できるようにします。ここで

その後、私は今、とても

using Support.Repositories.Contracts; 
using System; 
using System.Collections.Generic; 
using System.Data.Entity; 
using System.Linq; 
using System.Linq.Expressions; 

namespace Support.Repositories 
{ 
    public class EntityRepository<TEntity> : IRepository<TEntity> 
     where TEntity : class 
    { 
     protected readonly DbContext Context; 
     protected readonly DbSet<TEntity> DbSet; 

     public EntityRepository(DbContext context) 
     { 
      Context = context; 
      DbSet = context.Set<TEntity>(); 
     } 

     public TEntity Get(int id) 
     { 
      return DbSet.Find(id); 
     } 

     public IEnumerable<TEntity> GetAll() 
     { 
      return DbSet.ToList(); 
     } 

     public IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate) 
     { 
      return DbSet.Where(predicate); 
     } 

     public TEntity SingleOrDefault(Expression<Func<TEntity, bool>> predicate) 
     { 
      return DbSet.SingleOrDefault(predicate); 
     } 

     public void Add(TEntity entity) 
     { 
      DbSet.Add(entity); 
     } 

     public void AddRange(IEnumerable<TEntity> entities) 
     { 
      DbSet.AddRange(entities); 
     } 

     public void Remove(TEntity entity) 
     { 
      DbSet.Remove(entity); 
     } 

     public void RemoveRange(IEnumerable<TEntity> entities) 
     { 
      DbSet.RemoveRange(entities); 
     } 

    } 
} 

のようにEntity Frameworkのための実装を作成し、私のリポジトリインターフェースは

using System; 
using System.Collections.Generic; 
using System.Linq.Expressions; 

namespace Support.Repositories.Contracts 
{ 
    public interface IRepository<TModel> where TModel : class 
    { 
     // Get records by it's primary key 
     TModel Get(int id); 

     // Get all records 
     IEnumerable<TModel> GetAll(); 

     // Get all records matching a lambda expression 
     IEnumerable<TModel> Find(Expression<Func<TModel, bool>> predicate); 

     // Get the a single matching record or null 
     TModel SingleOrDefault(Expression<Func<TModel, bool>> predicate); 

     // Add single record 
     void Add(TModel entity); 

     // Add multiple records 
     void AddRange(IEnumerable<TModel> entities); 

     // Remove records 
     void Remove(TModel entity); 

     // remove multiple records 
     void RemoveRange(IEnumerable<TModel> entities); 
    } 
} 

ですが、私はそう

using System; 

namespace App.Repositories.Contracts 
{ 
    public interface IUnitOfWork : IDisposable 
    { 
     IUserRepository Users { get; } 
     IAddressRepository Addresses { get; } 
    } 
} 
のようなリポジトリと対話するための IUnitOfWorkを作成

次に、このインターフェイスをEntity Framework liに実装しましたこのKE:

using App.Contexts; 
using App.Repositories.Contracts; 
using App.Repositories.Entity; 

namespace App.Repositories 
{ 
    public class UnitOfWork : IUnitOfWork 
    { 
     private readonly AppContext _context; 
     public IUserRepository Users { get; private set; } 
     public IAddressRepository Addresses { get; private set; } 

     public UnitOfWork(AppContext context) 
     { 
      _context = context; 

      Users = new UserRepository(_context); 
      Addresses = new AddressRepository(_context); 
     } 

     public UnitOfWork() : this(new AppContext()) 
     { 
     } 

     public int Save() 
     { 
      return _context.SaveChanges(); 
     } 

     public void Dispose() 
     { 
      _context.Dispose(); 
     } 
    } 
} 

私は今、この

using(var repository = new UnitOfWork()) 
{ 
    repository.Users.Add(new User(... User One ...)) 
    repository.Save(); 

    repository.Addresses(new Address(... Address For User One ...)) 
    repository.Save(); 

    repository.Users.Add(new User(... User Two...)) 
    repository.Save(); 

    repository.Addresses(new Address(... Address For User Two...)) 
    repository.Save(); 
} 

のようなリポジトリを使用することができるよ、私はすべてが良い場合にのみ、その後、ロールバックそれ以外の場合はコミットして、データベースのトランザクションを使用できるようにしたいです。

私の最初の取り組みは、UnitOfWorkクラスにBeginTransaction()という新しいメソッドを追加することです。しかし、私のコードをEntity Frameworkにのみ結合します。

ここでは、BeginTransaction(),Commit()Rollback()メソッドを提供する新しいインターフェイスを作成して、任意のORMの実装を書くことを考えています。

すなわち

namespace Support.Contracts 
{ 
    public IRepositoryDatabase 
    { 
     SomethingToReturn BeginTransaction(); 

     void Commit(); 
     void Rollback(); 
    } 
} 

質問は、私が正しく実装することができますので、私は戻って私のUnitOfWorkにIRepositoryDatabaseを結ぶだろうかありますか? BeginTransaction()は何を返す必要がありますか?

+0

EFは、(デフォルトの保存上のすべての変更されたエンティティまたはnoneで保存されます)コールからUnitOfWorkの実装を消費する方法であります。 –

+0

ですが、どのようにトランザクションを開始しますか? –

+2

エンティティを10個変更し、その後Save()を呼び出します。 EFによって1つのトランザクションに書き込まれます(エラーが発生した場合は何も書き込まれません)。 –

答えて

0

Sir Rufoのコメントは正しいが、あなたはEFの独立したソリューションを望んでいると言いましたが、ORMから離れて抽象化するのは残酷ですが、依然として取引を処理している場合はTransactionScopeトランザクションの制御を達成する方法は明らかにBeginTransactioncontext.Databaseに入れる前に)。

詳細については、以下の記事を参照してください:https://msdn.microsoft.com/en-us/data/dn456843.aspx

関連ビットが(これは実際には、同様に他のORMで箱から出して動作します)あなたはTransactionScope内のすべての呼び出しを囲むことができます:

using System.Collections.Generic; 
using System.Data.Entity; 
using System.Data.SqlClient; 
using System.Linq; 
using System.Transactions; 

namespace TransactionsExamples 
{ 
    class TransactionsExample 
    { 
     static void UsingTransactionScope() 
     { 
      using (var scope = new TransactionScope(TransactionScopeOption.Required)) 
      { 
       using (var conn = new SqlConnection("...")) 
       { 
        conn.Open(); 

        var sqlCommand = new SqlCommand(); 
        sqlCommand.Connection = conn; 
        sqlCommand.CommandText = 
         @"UPDATE Blogs SET Rating = 5" + 
          " WHERE Name LIKE '%Entity Framework%'"; 
        sqlCommand.ExecuteNonQuery(); 

        using (var context = 
         new BloggingContext(conn, contextOwnsConnection: false)) 
        { 
         var query = context.Posts.Where(p => p.Blog.Rating > 5); 
         foreach (var post in query) 
         { 
          post.Title += "[Cool Blog]"; 
         } 
         context.SaveChanges(); 
        } 
       } 

       scope.Complete(); 
      } 
     } 
    } 
} 

しかし、以下の注意事項を気にする必要があります:

TransactionScopeアプローチにはいくつかの制限はまだあります

は、
  • はあなたが唯一つの接続(クラウドシナリオは、分散トランザクションをサポートしていません)
  • を持っていることを確認しない限り、それは雲のシナ​​リオで使用することはできません
  • 非同期メソッドで動作するように.NET 4.5.1以上が必要です前のセクションのDatabase.UseTransaction()のアプローチと組み合わせることはできません。
  • DDLを発行すると例外がスローされます。データベースイニシャライザのため)、MSDTCサービスを介して分散トランザクションを有効にしていません。
+0

アクションを受け入れるメソッドにコマンドをラップすることはできますか?このようなもの?'public void ExecuteWithInTransaction(アクションアクション) { \t var trans = _context.Database.BeginTransaction(); \t try \t { \t \t actions(); \t \t trans.Commit(); \t} \tキャッチ(例外) \t { \t \t trans.Rollback()。 \t} } ' –

+0

私はあなたができると思いますが、それは非常に非正統的なようであり、あなたがそれをインターフェイスにして何らかの形で実装しない限り、あなたのExecuteWithInTransactionをEFと結び付けています。 –

4

私はそれを行う方法を考え出しました。 (私はそれが正しい方法でやりたかった)

これは私がやったことです、これは同じ事をやってみたい人に役立つことを願っています。

は私がそのように

using Support.Repositories.Contracts; 
using System.Data.Entity; 

namespace Support.Entity.Repositories 
{ 
    public class EntityDatabaseTransaction : IDatabaseTransaction 
    { 
     private DbContextTransaction _transaction; 

     public EntityDatabaseTransaction(DbContext context) 
     { 
      _transaction = context.Database.BeginTransaction(); 
     } 

     public void Commit() 
     { 
      _transaction.Commit(); 
     } 

     public void Rollback() 
     { 
      _transaction.Rollback(); 
     } 

     public void Dispose() 
     { 
      _transaction.Dispose(); 
     } 
    } 
} 

のようなエンティティフレームワークのためのIDatabaseTransactionを実装するとので

using System; 

    namespace Support.Repositories.Contracts 
    { 
     public interface IDatabaseTransaction : IDisposable 
     { 
      void Commit(); 

      void Rollback(); 
     } 
    } 

のような新しいインターフェイスを作成し、私はそう

ように私の IUnitOfWork契約に BeginTransaction()と呼ばれる新しいメソッドを追加しました
using System; 

namespace App.Repositories.Contracts 
{ 
    public interface IUnitOfWork : IDisposable 
    { 
     IDatabaseTransaction BeginTrainsaction(); 
     IUserRepository Users { get; } 
     IAddressRepository Addresses { get; } 
    } 
} 

は、最後に、以下のエンティティ

using App.Contexts; 
using App.Repositories.Contracts; 
using App.Repositories.Entity; 
using Support.Repositories; 


namespace App.Repositories 
{ 
    public class UnitOfWork : IUnitOfWork 
    { 
     private readonly AppContext _context; 
     public IUserRepository Users { get; private set; } 
     public IAddressRepository Addresses { get; private set; } 

     public UnitOfWork(AppContext context) 
     { 
      _context = context; 

      Users = new UserRepository(_context); 
      Addresses = new AddressRepository(_context); 
     } 

     public UnitOfWork() : this(new AppContext()) 
     { 
     } 

     public int Save() 
     { 
      return _context.SaveChanges(); 
     } 

     public void Dispose() 
     { 
      _context.Dispose(); 
     } 

     public IDatabaseTransaction BeginTransaction() 
     { 
      return new EntityDatabaseTransaction(_context); 
     } 
    } 
} 

のための私のUnitOfwork実装されており、ここで私は私のコントローラ

using(var unitOfWork = new UnitOfWork()) 
using(var transaction = new unitOfWork.BeginTransaction()) 
{ 
    try 
    { 
     repository.Users.Add(new User(... User One ...)) 
     repository.Save(); 

     repository.Addresses(new Address(... Address For User One ...)) 
     repository.Save(); 

     repository.Users.Add(new User(... User Two...)) 
     repository.Save(); 

     repository.Addresses(new Address(... Address For User Two...)) 
     repository.Save(); 
     transaction.Commit(); 
    } 
    catch(Exception) 
    { 
      transaction.Rollback(); 
    } 

} 
+0

下の作業例で、 'unitOfWork'変数名で' repository'を変更するべきではありませんか? –

+0

作業格子! – Neo

関連する問題