Search code examples
c#dependency-injectionasp.net-mvc-5inversion-of-controlautofac

How to inject dependencies when those dependencies need a runtime value?


I'm implementing an ASP.NET MVC application and need to implement the Unit Of Work with repositories pattern. My implementation is designed as follows:

  • The UnitOfWork object is in charge of issuing COMMITs and ROLLBACKs as necessary.
  • The UnitOfWork object contains a Transaction property, obtaied from the internal DB connection. This object provides atomicity to the operations inside the UnitOfWork.
  • The UnitOfWork object contains the repositories as properties injected at runtime.
  • Each repository needs to be provided the IDbTransaction object that UnitOfWork created to support atomicity.

So now I find myself in the strange situation of, in UnitOfWork, having to inject repositories that need a property of UnitOfWork itself in order to be instantiated. So my question is: How do I do this? Or maybe something in the design has to be changed?

I'm using SQL Server at the moment and I use Dapper for the SQL calls. Also, I'm thinking of using Autofac as DI framework.

What I've done so far is to implement UOW and a sample repository. Code as follows.

IRepository.cs:

public interface IRepository<TObj, TKey>
{
    Task<TObj> DetallesAsync(TKey id);
    Task<TKey> AgregarAsync(TObj obj);
}

DbRepository.cs:

public abstract class DbRepository
{
    private readonly IDbConnection _connection;
    private readonly IDbTransaction _transaction;

    protected IDbConnection Connection
    {
        get => _connection;
    }

    protected IDbTransaction Transaction
    {
        get => _transaction;
    }

    public DbRepository(IDbTransaction transaction)
    {
        _transaction = transaction;
        _connection = _transaction.Connection;
    }
}

RolRepository.cs:

public class MSSQLRolRepository : DbRepository, IRolRepository
{
    public MSSQLRolRepository(IDbTransaction transaction)
        : base(transaction)
    {

    }

    public async Task<int> AgregarAsync(Rol obj)
    {
        var result = await Connection.ExecuteScalarAsync<int>(MSSQLQueries.RolAgregar, param: obj, transaction: Transaction);
        return result;
    }

    public async Task<Rol> DetallesAsync(int id)
    {
        var param = new { Id = id };
        var result = await Connection.QuerySingleOrDefaultAsync<Rol>(MSSQLQueries.RolDetalles, param: param, transaction: Transaction);
        return result;
    }

    public async Task<Rol> DetallesPorNombreAsync(string nombre)
    {
        var param = new { Nombre = nombre };
        var result = await Connection.QuerySingleOrDefaultAsync<Rol>(MSSQLQueries.RolDetallesPorNombre, param: param, transaction: Transaction);
        return result;
    }

    public async Task<Rol[]> ListarAsync(int pagina, int itemsPorPagina)
    {
        var param = new { Pagina = pagina, ItemsPorPagina = itemsPorPagina };
        var result = await Connection.QueryAsync<Rol>(MSSQLQueries.RolListar, param: param, transaction: Transaction);
        return result.ToArray();
    }

    public async Task<Rol[]> ListarTodosAsync()
    {
        var result = await Connection.QueryAsync<Rol>(MSSQLQueries.RolListar, transaction: Transaction);
        return result.ToArray();
    }
}

IUnitOfWork.cs:

public interface IUnitOfWork : IDisposable
{
    IDbTransaction Transaction { get; }
    IDenunciaRepository DenunciaRepository { get; }
    IUsuarioRepository UsuarioRepository { get; }
    IRolRepository RolRepository { get; }
    void Commit();
    void Rollback();
}

MSSQLUnitOfWork.cs:

public class MSSQLUnitOfWork : IUnitOfWork
{
    private bool _already_disposed = false;
    private IDbConnection _connection;
    private IDbTransaction _transaction;
    private IDenunciaRepository _denuncia_repository;
    private IUsuarioRepository _usuario_repository;
    private IRolRepository _rol_repository;

    public IDbTransaction Transaction
    {
        get => _transaction;
    }

    public IDenunciaRepository DenunciaRepository
    {
        get => _denuncia_repository;
    }

    public IUsuarioRepository UsuarioRepository
    {
        get => _usuario_repository;
    }

    public IRolRepository RolRepository
    {
        get => _rol_repository;
    }

    public MSSQLUnitOfWork()
    {
        var connection_string = ConfigurationManager.ConnectionStrings["MSSQL"].ConnectionString;
        _connection = new SqlConnection(connection_string);
        _connection.Open();
        _transaction = _connection.BeginTransaction();
        //TODO: Crear repos con transacción
    }

    public void Commit()
    {
        _transaction.Commit();
    }

    public void Rollback()
    {
        _transaction.Rollback();
    }

    protected virtual void Dispose(bool disposeManagedObjects)
    {
        if (!_already_disposed)
        {
            if (disposeManagedObjects)
            {
                _transaction?.Dispose();
                _connection?.Dispose();
            }
            _already_disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
    }
}

Solution

  • I recommend you 3 different things.

    1. Start, Commit and Rollback your data transactions within the repository where you are instantiatign the UnitOfWork - the least I recommend

    2. Create a Service class where you can create an instance of UnitOfWork and pass it the instance or DBContext to the Repositories that you involve in the transactions

    3. Create Repository instance within the UnitOfWork class tha knows the current DBContext then you can access from UnitOfWork the repository operations and starts and ends the transactions in the same context. More recommended

    Something like:

    UnitOfWorkInstance.MyRepositoryA.AddAsync(...);
    UnitOfWorkInstance.MyRepositoryB.AddAsync(...);
    UnitOfWorkInstance.Commit();