Подтвердить что ты не робот

Зависимость впрыска в единицу работы с использованием репозиториев

Я хочу создать единицу рабочего класса, который обертывает репозитории аналогично this.

Проблема, с которой я столкнулась, заключается в попытке реализовать инъекцию зависимостей, заменив общие репозитории в этом примере на интерфейс IRepository. В uow в связанной статье они используют геттеры для проверки того, создан ли репозиторий, а если нет, то создайте его.

public GenericRepository<Department> DepartmentRepository
{
    get
    {
        if (this.departmentRepository == null)
        {
            this.departmentRepository = new GenericRepository<Department>(context);
        }
        return departmentRepository;
    }
}

Что сильно связано.

Я вижу два пути.

  • Использовать инъекцию конструктора.
  • Используйте инъекцию установщика.

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

Проблема с 2 заключается в том, что было бы легко забыть устанавливать и заканчивать нулевыми исключениями ссылки.

Есть ли какие-либо лучшие практики в этом сценарии? И есть ли другие варианты, которые я пропустил?

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

4b9b3361

Ответ 1

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

Это гарантирует, что

  • ваш UnitOfWork не нужно изменять для каждого нового Repository
  • вы не используете локатор сервисов (многие считают, что anti-pattern)

Это лучше всего продемонстрировать с помощью некоторого кода - я использую SimpleInjector, поэтому примеры основаны на этом:

Начиная с абстракции Repository:

public interface IRepository 
{
    void Submit();
}
public interface IRepository<T> :IRepository where T : class { }
public abstract class GenericRepository<T> : IRepository<T> where T : class { }

и UnitOfWork

public interface IUnitOfWork
{
    void Register(IRepository repository);
    void Commit();
}

Каждый Repository должен регистрироваться с помощью UnitOfWork, и это можно сделать, изменив абстрактный родительский класс GenericRepository, чтобы убедиться в его выполнении:

public abstract class GenericRepository<T> : IRepository<T> where T : class
{
    public GenericRepository(IUnitOfWork unitOfWork)
    {
        unitOfWork.Register(this);
    }
}

Каждый вещественный Repository наследует от GenericRepository:

public class Department { }
public class Student { }

public class DepartmentRepository : GenericRepository<Department> 
{
    public DepartmentRepository(IUnitOfWork unitOfWork): base(unitOfWork) { }
}

public class StudentRepository : GenericRepository<Student>
{
    public StudentRepository(IUnitOfWork unitOfWork) : base(unitOfWork) { }
}

Добавьте в физическую реализацию UnitOfWork, и все установлено:

public class UnitOfWork : IUnitOfWork
{
    private readonly Dictionary<string, IRepository> _repositories;
    public UnitOfWork()
    {
        _repositories = new Dictionary<string, IRepository>();
    }

    public void Register(IRepository repository)
    {
        _repositories.Add(repository.GetType().Name, repository);
    }

    public void Commit()
    {
        _repositories.ToList().ForEach(x => x.Value.Submit());
    }
}

Регистрация контейнера может быть настроена так, чтобы автоматически выбирать все определенные экземпляры IRepository и регистрировать их с помощью ресурса lifetime, чтобы гарантировать, что все они выживут на протяжении всей вашей транзакции:

public static class BootStrapper
{
    public static void Configure(Container container)
    {
        var lifetimeScope = new LifetimeScopeLifestyle();

        container.Register<IUnitOfWork, UnitOfWork>(lifetimeScope);

        container.RegisterManyForOpenGeneric(
            typeof(IRepository<>),
            lifetimeScope,
            typeof(IRepository<>).Assembly);
    }
}

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

Чтобы проверить все это - добавьте эти классы

public class SomeActivity
{
    public SomeActivity(IRepository<Department> departments) { }
}

public class MainActivity
{
    private readonly IUnitOfWork _unitOfWork;
    public MainActivity(IUnitOfWork unitOfWork, SomeActivity activity) 
    {
        _unitOfWork = unitOfWork;
    }

    public void test()
    {
        _unitOfWork.Commit();
    }
}

Добавьте эти строки в BootStrapper.Configure()

//register the test classes
container.Register<SomeActivity>();
container.Register<MainActivity>();

Поместите точку останова на строку кода:

_repositories.ToList().ForEach(x => x.Value.Submit());

И, наконец, запустите этот тестовый код консоли:

class Program
{
    static void Main(string[] args)
    {
        Container container = new Container();
        BootStrapper.Configure(container);
        container.Verify();
        using (container.BeginLifetimeScope())
        {
            MainActivity entryPoint = container.GetInstance<MainActivity>();
            entryPoint.test();
        }
    }
}

Вы обнаружите, что код останавливается в точке останова, и у вас есть один активный экземпляр IRepository готовый и ожидающий Submit() любых изменений в базе данных.

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

Ответ 2

Вместо того, чтобы вводить экземпляры репозитория, вводится один объект factory, который будет отвечать за создание этих экземпляров. Затем ваши геттеры будут использовать этот factory.

Ответ 3

Мое решение - UnitOfWork, все еще ответственное за создание репозитория, но я сделал метод GetRepository() в UnitOfWork для этого.

public interface IUnitOfWork : IDisposable
{
    T GetRepository<T>() where T : class;
    void Save();
}

public class UnitOfWork : IUnitOfWork
{
    private Model1 db;

    public UnitOfWork() :  this(new Model1()) { }

    public UnitOfWork(TSRModel1 dbContext)
    {
        db = dbContext;
    }

    public T GetRepository<T>() where T : class
    {          
        var result = (T)Activator.CreateInstance(typeof(T), db);
        if (result != null)
        {
            return result;
        }
        return null;
    }

    public void Save()
    {
        db.SaveChanges();
    }

    private bool disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                db.Dispose();
            }
        }
        this.disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

public class TestRepository : GenericRepository<Test>, ITestRepository
{
    public TestRepository(Model1 db)
       : base(db)
    {
    }
}

public class TestManager: ITestManager
{
    private IUnitOfWork unitOfWork;
    private ITestRepository testRepository;
    public TestManager(IUnitOfWork unitOfWork)
    {
        this.unitOfWork = unitOfWork;
        testRepository = unitOfWork.GetRepository<TestRepository>();
    }

}