我注册了一个依赖项,如下所示:
interface IDependency { }
class DependencyImpl : IDependency { }
启动:
services.AddScoped<IDependency, DependencyImpl>();
这可以按预期工作,因为我确实想在我的Web API请求范围内重用同一实例。
但是,在一项后台服务中,我想告诉您它将解析为哪个实例:
class MyBackgroundService
{
private readonly IServiceScopeFactory _scopeFactory; // set in ctor
public void DoStuff()
{
var itens = GetItens();
var dependencyInstance = new DependencyImpl();
Parallel.ForEach(itens, (item) =>
{
using(var scope = _scopeFactory.CreateScope())
{
scope.SwapDependencyForThisScopeOnly<IDependency>( () => dependencyInstance ); // something like this
var someOtherService = scope.ServiceProvider.GetRequiredService<ItemService(); // resolve subsequent services with provided dependencyInstance
someOtherService.Process(item);
}
});
}
}
我不能重用同一作用域,因为ItemService(和/或它的依赖项)使用了其他无法共享的作用域服务。我也不想替换整个应用程序的依赖关系解析。
可以在这里做我想做的事吗?有道理吗?
我正在使用带有默认IoC容器的dotnet core 2.2。
编辑以回复@Steven::DependencyImpl包含有关如何处理项目的配置。其中之一包括相对昂贵的查询。 DependencyImpl在图中也被多次注入。因此,当前,它仅读取一次配置,将其缓存在私有属性中,并在后续读取时使用缓存的版本。因为我知道在这里将对所有ituse重用相同的配置,所以我想避免在每次并行执行时再次读取该配置。
我的实际依赖关系与此类似:
interface IDependency
{
Task<Configuration> GetConfigurationAsync();
}
class DependencyImpl : IDependency
{
private readonly Configuration _configuration;
private readonly DbContext _dbContext;
ctor(DbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<Configuration> GetConfigurationAsync()
{
if(_configuration is null)
{
// read configurations
}
return _configuration;
}
}
我知道,我的课程也不是线程安全的。我必须在开始时强制读取和/或在此处添加一些线程安全性。
此外,那些处理曾经在Web请求的生存期内发生,并且后台服务是新的东西。我宁愿更改尽可能少的现有代码,因为没有适当的测试,当然还有时间限制。
答案 0 :(得分:1)
通常,在应用程序运行时更改注册对象图的结构不是一个好主意。对于大多数容器而言,这不仅难以实现,而且还容易产生难以发现的细微问题。因此,我建议对您的设计进行一些小的更改,以免您所面临的问题。
与其尝试从整体上改变依赖关系,不如使用在不同线程上加载的数据预先填充现有的依赖关系。
这可以使用以下抽象/实现对完成:
public interface IConfigurationProvider
{
Task<Configuration> GetConfigurationAsync();
}
public sealed class DatabaseConfigurationProvider : IConfigurationProvider
{
private readonly DbContext _dbContext;
public DatabaseConfigurationProvider(DbContext dbContext)
{
_dbContext = dbContext;
}
public Configuration Configuration { get; set; }
public async Task<Configuration> GetConfigurationAsync()
{
if (Configuration is null)
{
await // read configurations
}
return Configuration;
}
}
在Configuration
实现上通知公众DatabaseConfigurationProvider
,在IConfigurationProvider
接口上 not 。
这是我要介绍的解决方案的核心。允许您的Composition Root设置值,而不会污染您的应用程序抽象,因为应用程序代码不需要覆盖Configuration
对象;只有“合成根”才需要。
有了这个抽象/实现对,后台服务可以看起来像这样:
class MyBackgroundService
{
private readonly IServiceScopeFactory _scopeFactory; // set in ctor
public Task DoStuff()
{
var itens = GetItens();
// Create a scope for the root operation.
using (var scope = _scopeFactory.CreateScope())
{
// Resolve the IConfigurationProvider first to load
// the configuration once eagerly.
var provider = scope.ServiceProvider
.GetRequiredService<IConfigurationProvider>();
var configuration = await provider.GetConfigurationAsync();
Parallel.ForEach(itens, (item) => Process(configuration, item));
}
}
private void Process(Configuration configuration, Item item)
{
// Create a new scope per thread
using (var scope = _scopeFactory.CreateScope())
{
// Request the configuration implementation that allows
// setting the configuration.
var provider = scope.ServiceProvider
.GetRequiredService<DatabaseConfigurationProvider>();
// Set the configuration object for the duration of the scope
provider.Configuration = configuration;
// Resolve an object graph that depends on IConfigurationProvider.
var service = scope.ServiceProvider.GetRequiredService<ItemService>();
service.Process(item);
}
}
}
要实现此目的,您需要以下DI配置:
services.AddScoped<DatabaseConfigurationProvider>();
services.AddScoped<IConfigurationProvider>(
p => p.GetRequiredService<DatabaseConfigurationProvider>());
此先前的配置两次注册DatabaseConfigurationProvider
:一次用于其具体类型,一次用于其接口。接口注册将转发呼叫并直接解析具体类型。这是在使用MS.DI容器时必须应用的特殊“技巧”,以防止在单个作用域内获取两个单独的DatabaseConfigurationProvider
实例。那将完全破坏该实现的正确性。
答案 1 :(得分:0)
创建一个扩展IDependency的接口,该接口仅适用于您需要请求的更快的实现,例如IFasterDependency。然后注册IFasterDependency。这样,您的更快的类仍然是IDependency对象,并且不会破坏太多现有代码,但是您现在可以自由地请求它。
public interface IDependency
{
// Actual, useful interface definition
}
public interface IFasterDependency : IDependency
{
// You don't actually have to define anything here
}
public class SlowClass : IDependency
{
}
// FasterClass is now a IDependencyObject, but has its own interface
// so you can register it in your dependency injection
public class FasterClass : IFasterDependency
{
}