我正在使用ServiceStack实现Rest服务。我们使用存储库模式,并通过IOC将存储库自动连接到服务中。
目前,我们有一种简单的方法,将一个数据库模型与一个存储库配对。这意味着,无论何时在一个服务中操作多个实体,都不会使用事务边界。存储库是按顺序调用的:如果一个或多个步骤失败,则必须手动将数据库"回滚"到其初始状态。最坏的情况是,如果请求线程死亡,或者发生未检查的异常(例如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中有效地使用Transactions,您需要创建自定义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
{
}
}
}