我有一个用例,其中我想使用.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>
的伪实现中可以看出,这也是一个问题,因为我到达了需要两者之一的位置:
在DI容器中确定与IARepository
关联的concret类型,以将其传递给ActivatorUtilities
,以便在解析时使用IConnection
的期望值创建它的实例其他带有IServiceProvider
或
以某种方式告诉IServiceProvider
在获得特定服务时使用IConnection
的特定实例
使用.NET Core DI完全可行吗?
(奖励问题:我应该使用其他更简单的方法吗?)
更新:我对示例代码进行了一些编辑,以期使我的意图更加清楚。这个想法是允许相同的存储库,完全相同的代码根据调用方的特定需求使用不同的连接(在应用程序启动期间配置)。总结一下:
有几种解决方法来解决在工厂中注入正确连接的问题:
答案 0 :(得分:0)
由于DI不会创建IConnection
,因此您可以将其从存储库构造函数中删除,并将其作为属性,然后在工厂中,可以在创建后分配其值:
interface IARepository
{
IConnection Connection { set; }
}
class ARepository : IARepository
{
public IConnection Connection { private get; set; }
public ARepository(IService1 svc1, IService2 svc2)
{ /* ... */ }
}
class RepositoryFactory : IRepositoryFactory
{
/* ... */
private IConnection Create<TRepository>(IConnection conn)
where TRepository : IARepository
{
var svc = _serviceProvider.GetService<TRepository>();
svc.Connection = conn;
return svc;
}
}
答案 1 :(得分:0)
问题在于根本没有注册并尝试注入IRepository
。根据您自己的规范,无法通过依赖项注入来创建存储库,因为传递给它的连接在运行时会有所不同。这样,您应该创建一个工厂(已经完成),然后注册并注入它。然后,将连接传递到您的Create
方法。
public TRepository Create<TRepository>(IConnection conn)
where TRepository : IRepository, new()
{
return new TRepository(conn);
}
您可能想在此处执行某种实例定位器模式。例如,您可以将创建的实例存储在以连接为键的ConcurrentDictionary
中。然后,您将从字典返回。如果存储库实际上在竞争条件下被实例化了多次,这可能不是什么大问题-应该只是相当小的对象分配。但是,访问SemaphoreSlim
时可以使用ConcurrentDictionary
创建锁,以防止这种情况。
您尚未提供有关您的特定用例的大量信息,因此我还将添加一个潜在的替代解决方案。为此,必须通过config或其他方式定义连接。如果确实是 runtime 提供的,则此方法将无效。您可以为服务注册提供操作,例如:
services.AddScoped<IRepository, ARepository>(p => {
// create your connection here
// `p` is an instance of `IServiceProvider`, so you can do service lookups
return new ARepository(conn);
});