Ответ 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 на этом этапе и рекомендую вам прочитать эти две статьи здесь и здесь.