リポジトリデザイン:トランザクションを共有する
質問
ServiceStackを使用してRESTサービスを実装しています。 IOCを介してリポジトリパターンと自動配線リポジトリをサービスに使用します。
現在、1つのDBモデルが1つのリポジトリとペアになっている素朴なアプローチがあります。つまり、1つのサービスで複数のエンティティが操作されると、トランザクションの境界は使用されません。リポジトリは順次呼び出されます。途中で1つ以上のステップが失敗すると、手動でDBを初期状態に "Rollback"する必要があります。最悪のケースシナリオ、リクエストスレッドがダイの場合、または未チェックの例外が発生した場合(たとえば、OutOfMemoryException)データベースは矛盾した状態に残ります。
私は一組の仮説解決を持っていますが、十分なものとしては何も関係ありません:
- 接続を開き、サービスレベルでトランザクションを開始します。リポジトリを呼び出して、接続を渡します。これはすべてのDDD設計ガイドラインに反しているので明らかに間違っています。全体の点は、コンクリートの持続性について上層が完全に無知であることです。さらに、それは単体テストを台無しにするでしょう。
- 最初のリポジトリがトランザクションを開始しています。他の従属リポジトリは呼び出されますが、すでに開かれた接続を渡します。これは悪いデザインのように聞こえます。
- 集計を定義します。私はドメインモデリングエキスパートではないので、私はこれの素晴らしいファンではありません。現在のモデルの利点はそれが単純であるということです。
誰かがこの問題に対する提案を持っていますか? 事前にありがとう
解決
通常UnitOfWorkと呼ばれるパススルークラスを使用できます。ここで、「接続」を開閉します。「作業単位」を検索するには、多くの例があります。下記のスニペットをカスタマイズしてトランザクションを含めることができます。
public class UnitOfWork : IUnitOfWork
{
readonly CompanyDbContext _context;
public UnitOfWork()
{
_context = new CompanyDbContext ();
}
private bool _disposed;
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_context.Dispose();
}
}
_disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public void Save()
{
_context.SaveChanges();
}
public IProductRepository ProductRepository
{
get { return new ProductRepository(_context); }
}
public ICartRepository CartRepository
{
get { return new CartRepository(_context); }
}
}
.
その後、以下のような複数のトランザクションを実行できます。
using (_unitOfWork)
{
var p = _unitOfWork.ProductRepository.SingleOrDefault(a => a.id ==1);
_unitOfWork.CartRepository.Add(p);
_unitOfWork.Save();
}
. 他のヒント
Ormliteでトランザクションを効果的に使用するには、各スレッドの接続オブジェクトを保持できるカスタムDBManagerクラスを作成する必要があります(ThreadStaticを使用)。その後、リポジトリ内のこのカスタムDBManagerを使用して、異なるORMLite関数を呼び出すことができます。
私が使用するコードの一部(コードを正しく機能するように変更する必要があります):
public class ThreadSpecificDBManager : IDisposable, IDBManager
{
[ThreadStatic]
private static int connectionCount = 0;
[ThreadStatic]
private static int transactionCount = 0;
[ThreadStatic]
private static IDbConnection connection = null;
public string ConnectionString { get; set; }
public IDbConnection Connection { get { EnsureOpenConnection(); return connection; } }
static ThreadSpecificDBManager()
{
}
private void EnsureOpenConnection()
{
if ((connection == null) || (connection.State == ConnectionState.Closed))
{
OrmLiteConfig.TSTransaction = null;
transactionCount = 0;
connectionCount = 0;
connection = (DbConnection)ConnectionString.OpenDbConnection();
//if (ConfigBase.EnableWebProfiler == true)
// connection = new ProfiledDbConnection((DbConnection)connection, MiniProfiler.Current);
}
}
public ThreadSpecificDBManager(string connectionString)
{
ConnectionString = connectionString;
connectionCount++;
EnsureOpenConnection();
}
public void Dispose()
{
if (transactionCount > 0)
{
//Log.Error("Uncommitted Transaction is left");
}
connectionCount--;
if (connectionCount < 1)
{
if ((connection != null) && (connection.State == ConnectionState.Open))
connection.Close();
if (connection != null)
connection.Dispose();
connection = null;
}
}
public void BeginTransaction()
{
if (transactionCount == 0)
{
//Log.SqlBeginTransaction(0, true);
OrmLiteConfig.TSTransaction = Connection.BeginTransaction();
}
else
{
//Log.SqlBeginTransaction(transactionCount, false);
}
transactionCount = transactionCount + 1;
}
public void RollbackTransaction()
{
try
{
if (transactionCount == 0)
{
//Log.SqlError("Transaction Rollback called without a begin transaction call.");
return;
}
if (OrmLiteConfig.TSTransaction == null)
{
//Log.SqlError("Transaction is not saved in the Thread Safe variable- so it cannot be rollbacked.");
throw new Exception("Transaction is not saved in the Thread Safe variable- so it cannot be rollbacked.");
}
if (transactionCount == 1)
{
transactionCount = 0;
try
{
//Log.SqlRollbackTransaction(transactionCount, true);
OrmLiteConfig.TSTransaction.Rollback();
}
catch (Exception ex1)
{
//Log.SqlError(ex1,"Error when rolling back the transaction");
}
OrmLiteConfig.TSTransaction.Dispose();
OrmLiteConfig.TSTransaction = null;
}
else
{
//Log.SqlRollbackTransaction(transactionCount, false);
transactionCount = transactionCount - 1;
}
}
finally
{
}
}
public void CommitTransaction()
{
try
{
if (transactionCount == 0)
{
//Log.SqlError("Transaction Rollback called without a begin transaction call.");
return;
}
if (OrmLiteConfig.TSTransaction == null)
{
//Log.SqlError("Transaction is not saved in the Thread Safe variable- so it cannot be rollbacked.");
throw new Exception("Transaction is not saved in the Thread Safe variable- so it cannot be rollbacked.");
}
if (transactionCount == 1)
{
//Log.SqlCommitTransaction(transactionCount,true);
transactionCount = 0;
OrmLiteConfig.TSTransaction.Commit();
OrmLiteConfig.TSTransaction.Dispose();
OrmLiteConfig.TSTransaction = null;
}
else
{
//Log.SqlCommitTransaction(transactionCount, false);
transactionCount = transactionCount - 1 ;
}
}
finally
{
}
}
}
.