在运行时通过.Net Core DI容器更改服务被注入的作用域或瞬态?

4
我们的应用程序中有几个类依赖于Entity Framework 6。因此,我们将我们的DbContext注入到各个区域中。然而,某些模块实现了多线程方法,需要将DbContext作为瞬态服务注入以防止任何线程问题。其他模块可以通过在任何子模块或接收相同共享DbContext的模块上调用SaveChanges来一起被串联并整体保存。然而,这种方法需要将DbContext添加为作用域服务。

除了构建一个仅仅从我的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);
    }
}

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


子类化上下文的示例可能看起来像什么?这个行得通吗? - mjwills
是的,但我正在寻找更具动态性的东西,可以通过传递参数来指示一个类应该利用共享上下文。 - JD Davis
一个短暂的数据库上下文并不意味着它是线程安全的。最好让那些数据访问层方法以线程安全的方式使用数据库上下文,或者在你的数据库上下文类中使用内部同步。 - kovac
@swdon 在某些情况下,我们正在执行高度并发的操作,这些操作在数据库上完全独立运行。 (想象一下运行报告,报告中的每个小部件都是自主执行、自包含且并行运行的)。 - JD Davis
1个回答

2
我认为你问题的核心在于以下观察结果:
某些模块实现了多线程方法,需要将DbContext注入为瞬态服务以防止任何线程问题。
这意味着你的应用程序代码本身负责处理多线程性; 你可能正在启动新的线程或任务。这是你应该避免的事情。
相反,只有你的组合根应该知道多线程,应该启动新线程。这样可以集中线程安全的知识。但不仅如此,许多组件都不是线程安全的,只有组合根应该知道哪些组件是和哪些不是。组件本身应该始终按顺序调用其依赖项,并假定该依赖项只有一个实例。
这意味着当你开始并行操作时,应该返回到组合根,让它解析一个新的对象图。组合根然后可以决定将新实例注入到图中的组件中(例如你的DbContext)。
当您应用这种工作方式时,您将不再需要具有短暂和范围版本的DbContext
有关更多信息,请参见:在多线程应用程序中使用DI。我的书 DI PP&P 中包含一些解释此内容的材料。

你大部分是正确的。我们有两个不同的用例需要动态地启动数据库上下文。在一个案例中,我们正在执行高并发操作,需要重复保存信息到 DbContext。当共享单个上下文来执行此操作时,我们会遇到问题。第二种情况是多个并发异步保存,它们不需要按任何特定顺序完成。但是,如果这些调用不是单独等待(而是作为批处理),我们将从 EF6 中收到并发异常。 - JD Davis
除此之外,我们还有一些实现了IRepository<TEntity>接口的类,其中包含一个SaveAsync方法。如果我们想执行多个操作并且将批处理保存,我们只需要在一个存储库中调用SaveAsync,所有共享相同作用域上下文的存储库也会得到保存。 - JD Davis
我看了一下你的网站。在你的看法中,构建一个代理,根据使用情况提供作用域或瞬态上下文,是否是这里正确的方法?我已经更新了原始问题并添加了更多细节。 - JD Davis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接