Дизайн репозитория:Совместное использование транзакции
Вопрос
Я внедряю службу Rest с помощью ServiceStack.Мы используем шаблон репозитория и автоматически подключаем репозитории к сервисам через 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, вам необходимо создать пользовательский класс DBManager, который может содержать объект connection для каждого потока (используйте 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
{
}
}
}