2つの理由から、アプリケーションでリポジトリデザインパターンを利用しようとしています。
ある時点でEntityFrameworkを使用しないことにした場合に備えて、アプリケーションをEntityから切り離すのが好きです。
モデルと相互作用するロジックを再利用できるようにしたい
リポジトリパターンを正常にセットアップして使用しました。ただし、トランザクションという1つの複雑さを処理する必要があります。
トランザクションを使用して、リポジトリに対して複数の呼び出しを行ってから、コミットまたはロールバックできるようにしたい。
これが私のリポジトリインターフェースです
_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);
}
}
_
次に、そのようなEntityFrameworkの実装を作成します
_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);
}
}
}
_
今、私はIUnitOfWork
を作成して、そのようにリポジトリと対話します
_using System;
namespace App.Repositories.Contracts
{
public interface IUnitOfWork : IDisposable
{
IUserRepository Users { get; }
IAddressRepository Addresses { get; }
}
}
_
次に、次のようにEntityFrameworkにこのインターフェイスを実装しました。
_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();
}
_
今、私はデータベーストランザクションを使用できるようにしたいので、すべてが良好な場合にのみ、それ以外の場合はロールバックをコミットします。
私の最初の見解は、BeginTransaction()
という新しいメソッドをUnitOfWork
クラスに追加することです。ただし、コードはEntityFrameworkのみに結合します。
今、私はBeginTransaction()
、Commit()
およびRollback()
メソッドを提供する新しいインターフェースを作成することを考えています。これにより、任意のORMの実装を作成できます。
つまり.
_namespace Support.Contracts
{
public IRepositoryDatabase
{
SomethingToReturn BeginTransaction();
void Commit();
void Rollback();
}
}
_
問題は、正しく実装できるように、IRepositoryDatabase
をUnitOfWorkにどのように結び付けるかです。そして、BeginTransaction()
は何を返す必要がありますか?
私はそれを行う方法を考え出したと思います。 (私はそれを正しい方法でやったことを願っています)
これが私がしたことです、これが同じことをしようとしている誰かに役立つことを願っています。
私はそのような新しいインターフェースを作成しました
_using System;
namespace Support.Repositories.Contracts
{
public interface IDatabaseTransaction : IDisposable
{
void Commit();
void Rollback();
}
}
_
次に、そのようなEntityFrameworkにIDatabaseTransaction
を実装しました
_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();
}
}
}
_
次に、BeginTransaction()
という新しいメソッドをIUnitOfWork
コントラクトに追加しました。
_using System;
namespace App.Repositories.Contracts
{
public interface IUnitOfWork : IDisposable
{
IDatabaseTransaction BeginTransaction();
IUserRepository Users { get; }
IAddressRepository Addresses { get; }
}
}
_
最後に、エンティティのUnitOfwork
実装を以下に示します。
_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();
}
}
_
Rufo卿のコメントは正しいですが、EFに依存しないソリューションが必要だとおっしゃっていました。通常、ORMから抽象化するのはやり過ぎですが、それでもトランザクションを自分で処理することにしている場合は、TransactionScope
を使用できます(これは明らかに、_context.Database
_にBeginTransaction
を含める前に、トランザクションを制御する方法でした。
詳細については、次の記事を参照してください。 https://msdn.Microsoft.com/en-us/data/dn456843.aspx
関連するビットは、すべての呼び出しをTransactionScope
で囲むことができることです(これは、他のORMでも実際に箱から出して機能します)。
_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
アプローチにはまだいくつかの制限があります。
Database.UseTransaction()
アプローチと組み合わせることはできません