且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

注入DbContext时无法访问ASP.NET Core中的已处置对象

更新时间:2023-02-17 20:16:36

ASP.NET Core 2.1更新

在ASP.NET Core 2.1中,方法略有变化.常规方法与2.0类似,只是方法名称和返回类型已更改.

Update for ASP.NET Core 2.1

In ASP.NET Core 2.1 the methods changed slightly. The general method is similar to the 2.0, just the methods name and return types have been changed.

public static void Main(string[] args)
{
    CreateWebHostBuilder(args)
        .Build()
        .Seed();
}

public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
    return new WebHostBuilder()
        ...; // Do not call .Build() here
}

适用于ASP.NET Core 2.0

在ASP.NET Core 2.0中,EF Core工具(dotnet ef migrations等)在设计时确定DbContext和连接字符串的方式已发生了一些变化.

Applies for ASP.NET Core 2.0

With ASP.NET Core 2.0 there have been some changes in how EF Core tools (dotnet ef migrations etc.) determine the DbContext and connection string at design time.

以下答案导致调用任何dotnet ef xxx命令时都应用了迁移和种子设置.

The below answer leads that the migrations and seeding are applied when calling any of the dotnet ef xxx commands.

获取EF Core工具设计时间实例的新模式是使用BuildHostWeb静态方法.

The new pattern for getting a design time instance for the EF Core tools is by using an BuildHostWeb static method.

根据此公告,EF Core现在将使用静态BuildWebHost方法它会配置整个应用程序,但不会运行它.

As per this announcement, EF Core will now use the static BuildWebHost method which configures the whole application, but doesn't run it.

  public class Program
  {
      public static void Main(string[] args)
      {
          var host = BuildWebHost(args);

          host.Run();
      }

      // Tools will use this to get application services
      public static IWebHost BuildWebHost(string[] args) =>
          new WebHostBuilder()
              .UseKestrel()
              .UseContentRoot(Directory.GetCurrentDirectory())
              .UseIISIntegration()
              .UseStartup<Startup>()
              .Build();
  }

将其替换为旧的Main方法

public static void Main(string[] args)
{
    var host = BuildWebHost(args)
        .Seed();

    host.Run();
}

种子是一种扩展方法:

public static IWebHost Seed(this IWebHost webhost)
{
    using (var scope = webhost.Services.GetService<IServiceScopeFactory>().CreateScope())
    {
        // alternatively resolve UserManager instead and pass that if only think you want to seed are the users     
        using (var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>()) 
        {
            SeedData.SeedAsync(dbContext).GetAwaiter().GetResult();
        }
    }
}

public static class SeedData
{
    public static async Task SeedAsync(ApplicationDbContext dbContext)
    {
        dbContext.Users.Add(new User { Id = 1, Username = "admin", PasswordHash = ... });
    }
}

旧答案,仍然适用于ASP.NET Core 1.x

有一个半官方的模式介绍了如何在您应该应用的ASP.NET Core应用程序中播种Entity Framework Core,因为在应用程序启动期间没有请求,因此也没有RequestServices(它可以解析作用域服务).

Old Answer, still applies to ASP.NET Core 1.x

There is a semi-official pattern on how to seed Entity Framework Core in ASP.NET Core application you should apply, because during application startup there is no Request and hence no RequestServices (which resolves scoped services).

从本质上讲,它可以归结为创建新的作用域,解决所需的类型并在完成后再次处置该作用域.

In essence it boils down to creating a new scope, resolve the types you need and dispose the scope again once you're finished.

// serviceProvider is app.ApplicationServices from Configure(IApplicationBuilder app) method
using (var serviceScope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
    var db = serviceScope.ServiceProvider.GetService<AppDbContext>();

    if (await db.Database.EnsureCreatedAsync())
    {
        await SeedDatabase(db);
    }
}

直接通过app.ApplicationServices.GetService<MyService>()解析服务的原因之一是ApplicationServices是应用程序(或生命周期)范围提供者,并且在此处解析的服务将保持活动状态,直到应用程序关闭为止.

One of the reasons directly resolving a service via app.ApplicationServices.GetService<MyService>() is that ApplicationServices is the application (or lifetime) scope provider and the services resolved here stay alive until the application is shut down.

通常,如果对象已经存在,则作用域容器将从其父容器中解析.因此,如果您在应用程序中以这种方式实例化DbContext,它将在ApplicationServices容器中可用,并且当发生请求时,将创建一个子容器.

Usually the scoped container will resolve from it's parent container, if the object already exists there. So if you instantiate the DbContext this way in the application, it will be available in ApplicationServices container and when a request happens, a child container will be created.

现在在解析DbContext时,它不会被解析为作用域,因为它已经存在于父容器中,因此将返回父容器的实例.但是由于它已在播种期间被处理掉,因此将无法访问.

Now when resolving the DbContext it won't be resolved as scoped, because it already exists in the parent container, so the instance of the parent container will be returned instead. But since it has been disposed during the seeding, it won't be accessible.

作用域容器就是生命周期有限的单例容器.

A scope container is nothing else then a singleton container with limited lifetime.

因此,切勿使用上述先创建作用域并从中解决作用域的模式在没有应用程序启动的情况下解析作用域服务.

So never resolve scoped services in Application startup w/o using the pattern above of first creating a scope and resolving from it.