跨项目的 EF Core 数据上下文注入

Posted

技术标签:

【中文标题】跨项目的 EF Core 数据上下文注入【英文标题】:EF Core Data Context injection across Projects 【发布时间】:2021-12-28 08:33:57 【问题描述】:

我有项目 A,它是一个类库,项目 B 使用 A。项目 A 是一个通用帮助库,可以跨项目(例如 B)使用。

EF Core 数据上下文和数据实体需要在项目 B 中定义(因为它们可能因项目而异),但我需要将数据上下文注入到项目 A 中服务类的构造函数中(以通用方式处理所有内容) )。 在项目 B 中,我有数据上下文

public class MyDataContext : DbContext
    
        public MyDataContext(DbContextOptions<MyDataContext> options): base(options)
        
        

        public DbSet<Test> Tests  get; set; 

    

在项目 A 中,我有实现 IUnitOfWork 的 UnitOfWork 类。在它的构造函数中,我需要注入数据上下文。但是,由于项目 A 不能引用项目 B(项目 A 是通用的),我不能在参数列表中使用数据上下文的实际名称。由于datacontext继承自DbContext,所以我尝试了

public UnitOfWork(DbContext dc)...

在项目B的启动中,我有

services.AddDbContext<MyDataContext>(options =>
     
          options.UseSqlServer("...<the connection string> ...");
     );
services.AddScoped<IUnitOfWork, UnitOfWork>();

一切都可以编译,但在运行时需要创建 UnitOfWork 时,我收到错误

System.AggregateException: Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: IUnitOfWork Lifetime: Scoped ImplementationType: UnitOfWork': Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContext' while attempting to activate 'UnitOfWork'.)

内部异常是

InvalidOperationException: Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContext' while attempting to activate 'UnitOfWork'.

非常感谢任何帮助。

编辑 我被要求在 cmets 中提供 UnitOfWork 类的详细信息。所以这里是

public class UnitOfWork : IUnitOfWork
    
        private readonly DbContext dc;
        private readonly IServiceProvider serviceProvider;

        public UnitOfWork(DbContext dc, IServiceProvider serviceProvider)
        
            this.dc = dc;
            this.serviceProvider = serviceProvider;
        
        public void BeginTransaction()
        
            dc.Database.BeginTransaction();
        

        public void BeginTransaction(IsolationLevel isolationLevel)
        
            dc.Database.BeginTransaction(isolationLevel);
        

        public void CommitTransaction()
        
            dc.Database.CommitTransaction();
        

        public void RollbackTransaction()
        
            dc.Database.RollbackTransaction();
        

        public bool IsTransactionActive()
        
            return dc.Database.CurrentTransaction != null;
        

        public async Task<bool> SaveAsync()
        
            return await dc.SaveChangesAsync() > 0;
        

        public bool Save()
        
            return dc.SaveChanges() > 0;
        

    

【问题讨论】:

可以添加工作单元类吗? 原始问题已编辑以包含详细信息 你破坏了 EF Core。您的问题不是 DI,而是使用 EF Core 的完全错误的方式。 DbContext 已经是一个多实体的工作单元。 DbSet 已经是一个存储库。绝对没有理由使用显式事务。 DbContext 缓存所有更改。如果您不致电SaveChanges,则所有更改都将被丢弃。如果这样做,所有更改都将使用内部事务进行持久化。在您加载数据或调用 SaveChanges 之前,无需连接到数据库 这里真正的解决方案是删除此代码并简单地使用MyDataContext 它应该工作的方式。将其注入到您的类中,并且只调用一次SaveChanges 以保留所有缓存的更改。如果您不这样做,则在处理MyDataContext 后,所有更改都将被丢弃。你的“工作单元”类不能做的事情,因为它没有实现IDisposable IMO 在某些情况下,在应用程序中抽象 EF Core 是个好主意。如果他决定使用 NoSQL 而不是关系数据库,那么过渡会更容易。它还可以简化单元测试。 【参考方案1】:

您的UnitOfWork 服务依赖于DbContext 类型,而不是注册到DI 中的派生MyDataContext 类型。 所以你有两个选择:

你可以像这样修改 UnitOfWork 注册(告诉 IoC 容器用 MyDataContext 实例化 UnitOfWork):

services.AddScoped<IUnitOfWork>(srp => new UnitOfWork(srp.GetRequiredService<MyDataContext>(), srp));

或者您也可以将DbContext 注册到 DI 中,因此 DI 容器知道当有人请求 DbContext 时它应该返回 MyDbContext:

services.AddScoped<DbContext, MyDataContext>();

请注意,ServiceProvider 字段似乎未在您的 UnitOfWork 类中使用。

【讨论】:

DbContext 是所有 EF Core 上下文的基类。大多数应用程序都有多个上下文,因此注册DbContext 将不起作用。DbContext 本质上是 DDD 术语中的有界上下文,而不是数据库模型。它包含服务特定用例或场景所需的实体。 问题在于“工作单元”类本身,没有 DI 魔法可以解决根本问题 他的 MyDataContext 类是 DbContext 类的派生类型。他的异常发生了,因为他没有在 DI 中注册为 DbContext,因此无法实例化 UnitOfWork。上面的解决方案解决了这个确切的问题。如果您有多个 DbContext 类,那么您可能也应该有多个 UnitOfWork 类,并且上面的第二个解决方案应该仍然有效。尽管如此,该问题并未提及多个 DbContext 派生类。 为什么它是 DI 魔法?将服务注册到 DI 是依赖注入最基本的概念。 我了解您不喜欢 UnitOfWork 实现,但这个问题确实无关紧要。您可以有任何其他服务类在其构造函数中请求 DbContext(基类),并且注册此类的方式是我在回答中所写的方式。【参考方案2】:

解决方案是进行两项更改。首先是按照@fbede 的建议显式注册服务

services.AddScoped<DbContext, MyDataContext>();

现在,当我们这样做时,我们失去了通过 AddDbContext 扩展方法设置 DbContextOptionsBuilder 选项的便利性。

所以我们需要重写datacontext中的OnConfiguring方法来设置我们需要的配置选项。例如:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    
        optionsBuilder.UseSqlServer(configuration.GetConnectionString(<key>));
    

当然IConfiguration是在MyDataContext构造函数中注入的

【讨论】:

您也可以尝试注册 unitOfWork 类的修改方法我提到的替代解决方案。这样您就不必修改 DbContext 实现。 您必须进行此类更改的事实应该是一个很大的红色警告信号。您的 DbContext 被硬编码为使用 SQL Server。您没有对存储技术进行抽象,而是对其进行了硬编码。您必须使用实际的数据库,而不是通过使用例如内存提供程序或内存数据库(例如内存模式中的 SQLite)来简化测试。或者在你的 DbContext 中添加越来越多的配置代码

以上是关于跨项目的 EF Core 数据上下文注入的主要内容,如果未能解决你的问题,请参考以下文章

ABP Framework:移除 EF Core Migrations 项目,统一数据上下文

EF Core 上下文不包含添加实体的更改

EF Core 3.1 在特定数据库上下文上添加迁移

[EF Core]数据迁移

.NET使用一行命令轻松生成EF Core项目框架

如何在 EF Core 中实例化 DbContext