Вопрос

Я внедряю службу Rest с помощью ServiceStack.Мы используем шаблон репозитория и автоматически подключаем репозитории к сервисам через IOC.

В настоящее время у нас есть наивный подход, при котором одна модель БД сопряжена с одним репозиторием.Это означает, что всякий раз, когда в одном сервисе манипулируют более чем одним объектом, границы транзакций не используются.Репозитории вызываются последовательно:если один или несколько шагов на этом пути завершаются неудачей, необходимо вручную "откатить" базу данных к ее исходному состоянию.В худшем случае, если поток запроса завершится или возникнет непроверенное исключение (например, OutOfMemoryException), база данных останется в несогласованном состоянии.

У меня есть набор гипотетических решений, но я не считаю ни одно из них адекватным:

  1. Откройте соединение и начните транзакцию на Уровне обслуживания.Вызывайте репозитории, передавая им соединение.Это явно неправильно, поскольку противоречит всем рекомендациям по проектированию ddd.Весь смысл в том, чтобы верхние слои были в полном неведении относительно стойкости бетона.Более того, это испортило бы модульное тестирование.
  2. Попросите первый репозиторий запустить транзакцию.Будут вызваны другие зависимые репозитории, но передающие уже открытое соединение.Это также звучит как плохой дизайн.
  3. Определение агрегатов.Я не большой поклонник этого, поскольку я не эксперт по моделированию предметной области, и я чувствую, что, вводя агрегаты, я могу привести к ошибкам проектирования.Одним из преимуществ текущей модели является то, что она проста.

У кого-нибудь есть предложения по этой проблеме?Заранее благодарю

Это было полезно?

Решение

Вы можете использовать сквозной класс, обычно называемый 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
        {
        }
    }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top