У меня есть сценарий использования, в котором я хочу создать экземпляры репозитория с помощью внедрения зависимостей .NET Core, но мне нужно изменить один из параметров конструктора во время выполнения.Чтобы быть точным, параметр, который должен быть определен во время выполнения, является «соединением с базой данных», которое будет указывать на ту или иную базу данных, выбранную вызывающей стороной.Этот тип, кстати, не зарегистрирован в контейнере DI, но все остальные.
Вызывающая сторона будет использовать тип фабрики хранилища для создания хранилища с желаемым соединением.
Это выглядит примерно так:
class ARepository : IARepository
{
public ARepository(IService1 svc1, IService2 svc2, IConnection connection) { }
public IEnumerable<Data> GetData() { }
}
class RepositoryFactory : IRepositoryFactory
{
public RepositoryFactory(IServiceProvider serviceProvider) =>
_serviceProvider = serviceProvider;
public IConnection CreateAlwaysFresh<TRepository>() =>
this.Create<TRepository>(new FreshButExpensiveConnection());
public IConnection CreatePossiblyStale<TRepository>() =>
return this.Create<TRepository>(new PossiblyStaleButCheapConnection());
private IConnection Create<TRepository>(IConnection conn)
{
// Fails because TRepository will be an interface, not the actual type
// that I want to create (see code of AService below)
return ActivatorUtilities.CreateInstance<TRepository>(_serviceProvider,conn);
// Fails because IConnection is not registered, which is normal
// because I want to use the instance held in parameter conn
return _serviceProvider.GetService<TRepository>();
}
}
Были зарегистрированы следующие типы:
services.AddTransient<IARepository, ARepository>(); // Probably not needed
services.AddTransient<IService1, Service1>();
services.AddTransient<IService2, Service2>();
services.AddTransient<IRepositoryFactory, RepositoryFactory>();
И фабрика будет использоваться таким образом:
class AService
{
public AService(IRepositoryFactory factory)
{
_factory = factory;
}
public void ExecuteCriticalAction()
{
var repo = _factory.CreateAlwaysFresh<IARepository>();
// Gets the freshest data because repo was created using
// AlwaysFresh connection
var data = repo.GetData();
// Do something critical with data
}
public void ExecuteRegularAction()
{
var repo = _factory.CreatePossiblyStale<IARepository>();
// May get slightly stale data because repo was created using
// PossiblyStale connection
var data = repo.GetData();
// Do something which won't suffer is data is slightly stale
}
}
Одна из причин, по которой я сохранил весь код на основе интерфейсов, это, конечно, модульное тестирование.Однако, как вы можете видеть из псевдо-реализации RepositoryFactory.Create<TRepository>
, это также проблема, потому что я достигаю точки, где мне нужно либо:
определить конкретный тип, связанный сIARepository
в контейнере DI для передачи его в ActivatorUtilities
для создания его экземпляра с использованием требуемого значения IConnection
при разрешении других параметров конструктора с помощью IServiceProvider
или
как-то сказать IServiceProvider
использовать конкретный экземпляр IConnection
при получении определенной услуги
Возможно ли это вообще с помощью .NET Core DI?
(Бонусный вопрос: должен ли я использовать другой, более простой подход?)
Обновление: Я немного отредактировал пример кода, чтобы, надеюсь, сделать мои намерения более ясными.Идея состоит в том, чтобы позволить одному и тому же хранилищу, точно одному и тому же коду, использовать разные соединения (которые настраиваются во время запуска приложения) в зависимости от конкретных потребностей вызывающей стороны .Подводя итог:
- ответственность Репозитария состоит в том, чтобы выполнять правильные запросы к Соединению, когда запрашивается действие.
- Вызывающий объект будет воздействовать на данные, возвращаемые репозиторием
- однако , вызывающей стороне может потребоваться, чтобы Репозиторий выполнил свои запросы к определенному Соединению (которое в этом примере контролирует свежесть данных)
Несколько обходных путей подошлик проблеме внедрения правильного соединения на фабрике:
- добавьте изменяемое свойство Connection в репозитории и установите его сразу после создания => что меня больше всего беспокоит это решение, так это то, что оно делает его оченьлегко забыть установить соединение, например, в тестовом коде.Он также оставляет дверь открытой для изменения свойства хранилища, которое должно быть неизменным.
- не вставляет Соединение в класс, а вместо этого передает его как параметр метода => это делает для менее элегантного API, поскольку каждый метод теперь будет иметь «дополнительный» параметр, который мог бы просто быть предоставлен классу для начала, а дополнительный параметр - всего лишь «деталь реализации»