当这些依赖项需要运行时值时如何注入依赖项?

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

我正在实施一个 ASP.NET MVC 应用程序并且需要实施 存储库工作单元 模式。我的实现是这样设计的:

所以现在我发现自己处于一种奇怪的情况,在 UnitOfWork 中,必须注入需要 UnitOfWork 本身的 属性 的存储库才能被实例化。所以我的问题是:我该怎么做?或者设计中的某些内容必须更改?

我目前正在使用 SQL 服务器,我使用 Dapper 进行 SQL 调用。另外,我正在考虑使用 Autofac 作为 DI 框架。

到目前为止,我所做的是实施 UOW 和示例存储库。代码如下

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

我向您推荐 3 种不同的东西。

  1. 在您实例化 UnitOfWork 的存储库中开始、提交和回滚您的数据事务——我至少推荐

  2. 创建服务class,您可以在其中创建 UnitOfWork 的实例并将其实例或 DBContext 传递给事务中涉及的存储库

  3. 在 UnitOfWork 中创建存储库实例 class 知道当前的 DBContext 然后您可以从 UnitOfWork 访问存储库操作并在同一上下文中启动和结束事务。更多推荐

类似于:

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

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.

看起来你有一个循环依赖,循环依赖总是一个糟糕的设计,你应该打破它。如果您关注 Single Responsibility Principle,则不会发生这种情况。如果发生这种情况,服务可能有太多的责任,你应该在多个服务中打破它,或者有时是因为你的服务太少,这些服务应该重新组合。

在你的情况下,IUnitOfWork 似乎负有太多责任。这项服务的目标是什么?它的职责是什么?

对我来说,这个服务不应该有任何存储库,这个服务不需要任何。如果任何其他服务需要这样的存储库,他们只需添加对它的依赖。此外,存储库不需要依赖 IUnitOfWork,而只依赖 IDbTransaction。此外 IDbTransactionIDbConnection 应该在你的依赖注入器中配置。如果出于任何原因需要 IUnitOfWork 实例化这些服务,您可以执行类似

的操作
builder.RegisterType<MSSQLUnitOfWork>()
       .As<IUnitOfWork>()
       .InstancePerLifetimeScope()
builder.Register(c => c.Resolve<IUnitOfWork>().Transation)
       .As<IDbTransaction>();
builder.Register(c => c.Resolve<IUnitOfWork>().Connection)
       .As<IDbConnection>();