Pergunta

Estou implementando um serviço Rest usando ServiceStack.Usamos o padrão de repositório e conectamos automaticamente os repositórios aos serviços via IOC.

Atualmente, temos uma abordagem ingênua onde um modelo de banco de dados é emparelhado com um repositório.Isto significa que sempre que mais de uma entidade for manipulada em um serviço, nenhum limite transacional será usado.Os repositórios são invocados sequencialmente:se uma ou mais etapas falharem, será necessário "reverter" o banco de dados ao seu estado inicial, manualmente.Na pior das hipóteses, se o thread de solicitação morrer ou se ocorrer uma exceção não verificada (por exemplo, OutOfMemoryException), o banco de dados ficará em um estado inconsistente.

Tenho um conjunto de soluções hipotéticas, mas não considero nenhuma adequada:

  1. Abra uma conexão e inicie uma transação no nível de serviço.Invoque repositórios, passando-lhes a conexão.Obviamente, isso está errado, pois vai contra todas as diretrizes de design do ddd.A questão toda é que as camadas superiores ignoram completamente a persistência concreta.Além disso, isso atrapalharia os testes unitários.
  2. Faça com que o primeiro repositório inicie uma transação.Outros repositórios dependentes seriam invocados, mas passando a conexão já aberta.Isso também parece um design ruim.
  3. Definindo agregados.Não sou um grande fã disso, pois não sou um especialista em modelagem de domínio e sinto que, ao introduzir agregações, posso introduzir erros de design.Uma vantagem do modelo atual é que ele é simples.

Alguém tem sugestões para este problema?desde já, obrigado

Foi útil?

Solução

Você pode usar uma classe pass through normalmente chamada UnitOfWork, onde você abrirá e fechará a "conexão".Procure por “Unidade de trabalho” você encontrará muitos exemplos.Você pode personalizar o snippet abaixo para incluir transações.

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); }
    }
}

então você pode fazer várias transações como abaixo

 using (_unitOfWork)
 {
     var p = _unitOfWork.ProductRepository.SingleOrDefault(a => a.id ==1);
     _unitOfWork.CartRepository.Add(p);
     _unitOfWork.Save();  
 }

Outras dicas

Para usar Transações de maneira eficaz com Ormlite, você precisa criar uma classe DBManager personalizada que possa conter o objeto de conexão para cada thread (use um ThreadStatic).Então você pode usar este DBManager personalizado em seu repositório para chamar diferentes funções ormlite.

Parte do código que uso é (você precisa modificar o código para funcionar corretamente):

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
        {
        }
    }
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top