Как внедрить зависимости, когда эти зависимости нуждаются во времени выполнения? - PullRequest
1 голос
/ 12 марта 2020

Я реализую приложение ASP. NET MVC, и мне необходимо реализовать шаблон Unit Of Work с репозиториями . Моя реализация разработана следующим образом:

  • Объект UnitOfWork отвечает за выдачу COMMIT с и ROLLBACK с при необходимости.
  • Объект UnitOfWork содержит Transaction свойство, полученное из внутреннего соединения с БД. Этот объект обеспечивает атомарность операций внутри UnitOfWork.
  • . Объект UnitOfWork содержит репозитории как свойства, внедренные во время выполнения.
  • Каждому репозиторию необходимо предоставить объект IDbTransaction что UnitOfWork создано для поддержки атомарности.

Так что теперь я нахожусь в странной ситуации, когда в UnitOfWork приходится вводить репозитории, которым нужно свойство UnitOfWork, чтобы быть воплощенным в жизнь. Итак, мой вопрос: как мне это сделать? Или, может быть, что-то в дизайне нужно изменить?

Я сейчас использую SQL Сервер и использую Dapper для вызовов SQL. Кроме того, я думаю об использовании Autofa c в качестве 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);
    }
}

Ответы [ 2 ]

1 голос
/ 12 марта 2020

Я нахожусь в странной ситуации, когда в UnitOfWork приходится внедрять репозитории, которым требуется свойство самого UnitOfWork для создания экземпляра.

Похоже, у вас есть циклическая зависимость, а круговая зависимость - это всегда плохой дизайн, и вы должны его сломать. Если вы следовали принципу единой ответственности , этого не должно происходить. Если это происходит, у службы может быть слишком много обязанностей, и вам следует разбить ее на несколько служб, или иногда это происходит из-за того, что у вас слишком маленькая служба, и эти службы следует воссоединить.

В вашем случае, похоже, IUnitOfWork слишком много ответственности. Какова цель этого сервиса? каковы его обязанности?

Для меня этот сервис не должен иметь никаких репозиториев, этот сервис не нужен. Если какой-либо другой сервис нуждается в таком репозитории, он просто должен добавить зависимость от него. Также хранилище не обязательно должно зависеть от IUnitOfWork, а только от IDbTransaction. Кроме того, IDbTransaction и IDbConnection должны быть настроены в вашем инжекторе зависимостей. Если по какой-либо причине эти службы должны быть созданы 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>();
1 голос
/ 12 марта 2020

Я рекомендую вам 3 разные вещи.

  1. Запуск, фиксация и откат ваших транзакций данных в хранилище, в котором вы устанавливаете UnitOfWork - по крайней мере, я рекомендую

  2. Создайте класс Service, где вы можете создать экземпляр UnitOfWork и передать ему экземпляр или DBContext в репозитории, которые вы задействуете в транзакциях

  3. Создать экземпляр репозитория в класс UnitOfWork, который знает текущий DBContext, затем вы можете получить доступ из UnitOfWork к операциям с репозиторием, а также запускать и завершать транзакции в том же контексте. Более рекомендуется

Что-то вроде:

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