首页 > 解决方案 > 在运行时通过 .Net Core DI 容器更改服务是作为作用域注入还是瞬态注入?

问题描述

我们的应用程序中有几个依赖于 Entity Framework 6 的类。因此,我们将我们注入DbContext到各个领域。但是,某些模块实现了多线程方法,这些方法需要DbContext作为临时服务注入以防止任何线程问题。其他模块可以通过简单地调用SaveChanges任何接收相同 shared 的子模块或模块来串在一起并批量保存DbContext。但是,这种方法需要将DbContext其添加为范围服务。

除了构建一些简单地从 my 继承的子类或接口之外,DbContext还有什么方法可以动态确定一个类是获得给定服务的作用域版本还是瞬态版本?

子类上下文的示例可能类似于

public class TransientDbContext : DbContext {}
public class ScopedDbContext : DbContext {}

// in services
services.AddTransient<TransientDbContext>();
services.AddScoped<ScopedDbContext>();

哪个有效,但我正在寻找更动态的东西,我可能会传递一个参数来指示一个类应该使用共享上下文。

对于一些额外的上下文,图像我有以下接口

public interface IRepository<TEntity> 
{
    void Add(TEntity entity);
    Task SaveAsync(CancellationToken token = default);
}

public interface IUserManager 
{
    Task AddAsync(User user, bool commitChanges = true, CancellationToken = default);
}

public interface IUserPhoneNumberManager 
{
    Task AddAsync(UserPhoneNumber number, bool commitChanges, CancellationToken token = default)
}

在幕后,我可能有以下具体实现

public class UserRepository<User> : IRepository<User>
{
    private readonly DbContext _dbContext;
    public UserRepository(DbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public void Add(User entity) 
    {
        _dbContext.Users.Add(entity);
    }

    public Task SaveAsync(CancellationToken token = default)
    {
        return _dbContext.SaveChangesAsync(token);
    }
}

public class UserPhoneNumberRepository<UserPhoneNumber> : IRepository<UserPhoneNumber>
{
    private readonly DbContext _dbContext;
    public UserRepository(DbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public void Add(UserPhoneNumber entity) 
    {
        _dbContext.UserPhoneNumbers.Add(entity);
    }

    public Task SaveAsync(CancellationToken token = default)
    {
        return _dbContext.SaveChangesAsync(token);
    }
}

现在在某些情况下,我希望底层存储库被注入一个单一范围的上下文,而其他时候我想要一个瞬态上下文。这些瞬态上下文在使用时显然会提交自己的更改。但是作用域上下文会将它们的更改作为一个单一的单元提交。

标签: c#entity-frameworkasp.net-coredependency-injection

解决方案


我认为您问题的核心在于以下观察:

某些模块实现了需要将 DbContext 作为临时服务注入的多线程方法,以防止任何线程问题。

这意味着您的应用程序代码本身负责处理多线程;您可能正在开始新的线程或任务。这是你应该防止的事情。

相反,只有您的Composition Root才应该知道多头性,并且应该衍生出新的线程。这集中了有关线程安全的知识。但不仅如此,许多组件不是线程安全的,只有组合根应该知道哪些组件是和不是。组件本身应该始终以顺序方式调用其依赖项,并假设该依赖项只有一个实例。

这意味着当您开始并行操作时,您应该返回到组合根,让它解析一个新的对象图。然后,Composition Root 可以决定将组件的新实例注入到图中(例如 your DbContext)。

当您应用这种工作方式时,您将不再需要您的临时版本和范围版本DbContext

有关更多信息,请参阅:在多线程应用程序中使用 DI。我的书DI PP&P确实包含一些解释这一点的材料。


推荐阅读