为什么 IConfiguration 对象注入 DbContext 后为 null? [ASP.NET 核心 3.1]

Why is the IConfiguration object null after injecting it to DbContext? [ASP.NET Core 3.1]

在发布这个问题之前,我浏览了多个相似的帖子。我的问题集中在“尽管应用了常见且可能正确的解决方案,但为什么会发生这种情况?”。

我正在开发 .NET Core 3.1 网络应用程序。我有一个名为 'SkipQContext' 的 DbContext。我正在尝试使用 Configuration 对象从 SkipQContext 文件中的 appsettings.json 访问连接字符串。

为此,我将 IConfiguration 作为服务注入 SkipQContext 构造函数。

构造函数:

private readonly string ConnectionString;

public SkipQContext()
{ 
}

public SkipQContext(DbContextOptions<SkipQContext> options, IConfiguration configuration)
    : base(options)
{
    ConnectionString = configuration.GetConnectionString("DefaultConnection");
}

我也在ConfigureServices方法中注册了Startupclass。

services.AddSingleton(Configuration);

现在,当我在我的存储库 classes 之一中实例化 SkipQContext 时,会调用 SkipQContext 的默认构造函数。当我尝试使用它获取数据时,我得到“IConfiguration 对象为空。”

我在 ConfigureServices 方法中应用了断点,可以看到 IConfiguration 对象具有连接字符串值。

我的第一个问题是,当我在 ConfigureServices 中注册它并在 SkipQContext 构造函数中注入它时,为什么它在 SkipQContext 中为空?网上有多个答案表明这是正确的方法。

此外,我在想,我可能没有正确实例化 SkipQContext。正如我的声明:

SkipQContext db = new SkipQContext();

命中 SkipQContext 的默认构造函数,它是空的,而不是注入 IConfiguration 的重载构造函数。

P.S。如果最后一个问题是愚蠢的。我对 .NET Core 中的依赖注入是如何工作的还是有点不清楚。

无需将配置实例化为单例,WebHost 的默认构建器已经在请求中注入配置,您的启动 Class 应该如下所示

    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {

            string conn = Configuration.GetConnectionString("NAME OF YOUR CONNECTION STRING IN THE application.json FILE");
            

            services.AddDbContext<CLASSOFURDBCONTEXT>(config =>
            {
                config.UseSqlServer(conn);
            });

        }
    }

并且您的 dbcontext 应该具有以下构造函数

        public YourDbContext(DbContextOptions<YourDbContext> options) : base(options)
        {
        }

那么您只需要在控制器或服务中调用 DbContext,DI 将完成剩下的工作

根据您的 IConfiguration 抛出空引用异常的原因,我可以想到 2 种可能性。要么你需要做另一种实例化,就像这样

services.AddSingleton<IConfiguration,Configuration>();

或者可能是因为您没有在 DbContext 本身中使用 DI,所以您不需要执行 new YourContextDbContext()。你应该只是简单地将它放在服务或控制器的构造函数中,它应该“神奇地”工作,而你实际上不需要创建它的实例。

Also, I am thinking, I might not be instantiating the SkipQContext rightly. As my statement:

SkipQContext db = new SkipQContext();

hits the default constructor of SkipQContext which is empty and not the overloaded constructor where IConfiguration is injected.

你是对的,这不是依赖注入的工作方式。每当您执行 new Something 时,您就会 明确地 绕过依赖注入。关于依赖注入的要点是,具有依赖关系的组件(例如数据库上下文)不需要自己创建该依赖关系,甚至 知道 如何自己创建该依赖关系。

当您调用 new SkipQContext() 时,您 显式创建该依赖关系,因此您 紧密地 耦合到 SkipQContext 以及上下文正常工作所需的任何内容(在本例中,它需要 DbContextOptionsIConfiguration)。相反,您想要的是组件对其依赖项 loosely coupled 。因此,您只需声明您 需要 的依赖项,并要求某人或其他人为您完成这些依赖项。这正是依赖注入的用武之地:

有了依赖注入,你就有了一个“依赖注入容器”,它负责创建你或某些组件可能需要的所有依赖。您在 Startup.ConfigureServices 的中央位置配置容器,然后您可以通过服务的构造函数简单地声明您需要的依赖项。但是为了让容器向该服务提供这些依赖项,服务必须由容器本身创建。

所以你将看到的是,你基本上必须通过依赖注入来消耗所有内容。但这也使您在不使用依赖注入时很容易意识到:每当您编写 new Something 时,容器就不会创建某些东西,因此不会自动实现其依赖关系。取决于那东西可能是你想要的,也可能不是(例如,创建一个 List<string> 或一个 DTO 对象是你想直接做的事情,创建一个服务或具有其他依赖关系的东西可能不是).

回到您的问题,为了让 DI 容器处理 SkipQContext 的构造函数中的依赖项,您必须让 DI 容器为您创建该上下文。所以你不能用 new 创建它,而是你必须 depend 将它添加到你需要它的任何组件的构造函数中。

例如如果你有一个控制器,只需将它添加为依赖项:

public class HomeController : Controller
{
    private readonly SkipQContext _db;

    public HomeController(SkipQContext db)
    {
        _db = db;
    }

    public async Task<IActionResult> Index()
    {
        var items = await _db.Items.ToListAsync();
        return View(new IndexViewModel
        {
            Items = items,
        });
    }
}

关于您的数据库上下文的最后一点说明:如果您使用 DI 容器 正确地 注册数据库上下文,那么它已经使用获取的 DbContextOptions 配置传递给构造函数。这些选项还将包括上下文打开数据库连接所需的连接字符串。所以你不需要手动传递 IConfiguration 或在那里提取连接字符串。它会由 EF Core 自动为您完成。

正确的上下文设置可能如下所示(在 ConfigureServices 中):

services.AddDbContext<SkipQContext>(options =>
    options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));