【问题标题】:EF Core Data Context injection across Projects跨项目的 EF Core 数据上下文注入
【发布时间】: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 而不是关系数据库,那么过渡会更容易。它还可以简化单元测试。

标签: c# .net-core dependency-injection entity-framework-core


【解决方案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 中添加越来越多的配置代码
猜你喜欢
  • 2018-06-07
  • 2021-03-22
  • 1970-01-01
  • 1970-01-01
  • 2018-06-19
  • 2020-11-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多