注入DbContext时无法访问ASP.NET Core中的已处置对象 [英] Cannot access a disposed object in ASP.NET Core when injecting DbContext

查看:50
本文介绍了注入DbContext时无法访问ASP.NET Core中的已处置对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在ASP.NET Core项目上,我在启动时有以下内容:

On an ASP.NET Core project I have the following on Startup:

  services.AddDbContext<Context>(x => x.UseSqlServer(connectionString));

  services.AddTransient<IValidationService, ValidationService>();

  services.AddTransient<IValidator<Model>, ModelValidator>();

ValidationService如下:

The ValidationService is as follows:

public interface IValidationService {
    Task<List<Error>> ValidateAsync<T>(T model);
}

public class ValidationService : IValidationService {
    private readonly IServiceProvider _provider;

    public ValidationService(IServiceProvider provider) {
        _provider = provider;
    }

    public async Task<List<Error>> ValidateAsync<T>(T model) {
        IValidator<T> validator = _provider.GetRequiredService<IValidator<T>>();

        return await validator.ValidateAsync(model);
    }
}

ModelValidator如下:

And the ModelValidator is as follows:

public class ModelValidator : AbstractValidator<Model> {
  public ModelValidator(Context context) {
    // Some code using context
  }
}

当我在控制器中注入IValidationService并将其用作:

When I inject a IValidationService in a controller and use it as:

List<Error> errors = await _validator.ValidateAsync(order);    

我得到了错误:

System.ObjectDisposedException:无法访问已处置的对象.一种 导致此错误的常见原因是处理已解决的上下文 从依赖项注入,然后稍后尝试使用相同的 您的应用程序中其他位置的上下文实例.这可能是你 在上下文上调用Dispose()或将上下文包装在 使用语句.如果使用依赖项注入,则应 让依赖项注入容器负责处理上下文 实例.对象名称:上下文".

System.ObjectDisposedException: Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur is you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances. Object name: 'Context'.

任何想法,为什么在ModelValidator中使用Context时都会出现此错误.

Any idea why I am having this error when using Context inside ModelValidator.

该如何解决?

更新

所以我将代码更改为:

services.AddScoped<IValidationService, ValidationService>();

services.AddScoped<IValidator<Model>, ModelValidator>();

但是我得到了同样的错误...

But I get the same error ...

更新-启动时Configure方法中的种子数据代码

所以在Configure方法上,我有:

So on Configure method I have:

if (hostingEnvironment.IsDevelopment())
  applicationBuilder.SeedData();

SeedData扩展名是:

And the SeedData extension is:

public static class DataSeedExtensions {
    private static IServiceProvider _provider;

    public static void SeedData(this IApplicationBuilder builder) { 
        _provider = builder.ApplicationServices;
        _type = type;

        using (Context context = (Context)_provider.GetService<Context>()) {
            await context.Database.MigrateAsync();
            // Insert data code
    }
}

我想念什么?

更新-可能的解决方案

将我的Seed方法更改为以下方法似乎可行:

Changing my Seed method to the following seems to work:

using (IServiceScope scope = 
    _provider.GetRequiredService<IServiceScopeFactory>().CreateScope()) {
    Context context = _provider.GetService<Context>();
    // Insert data in database
}

推荐答案

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.

这篇关于注入DbContext时无法访问ASP.NET Core中的已处置对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆