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

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

问题描述

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

 services.AddDbContext(x => x.UseSqlServer(connectionString));services.AddTransient();services.AddTransient, ModelValidator>();

ValidationService 如下:

公共接口 IValidationService {任务<列表<错误>>ValidateAsync (T 模型);}公共类验证服务:IValidationService {私有只读 IServiceProvider _provider;公共验证服务(IServiceProvider 提供者){_provider = 提供者;}公共异步任务<列表<错误>>ValidateAsync(T 模型){IValidator<T>验证器 = _provider.GetRequiredService>();return await validator.ValidateAsync(model);}}

ModelValidator 如下:

公共类 ModelValidator : AbstractValidator{公共模型验证器(上下文上下文){//一些使用上下文的代码}}

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

List错误 = 等待 _validator.ValidateAsync(order);

我收到错误:

<块引用>

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

知道为什么在 ModelValidator 中使用 Context 时会出现此错误.

如何解决这个问题?

更新

所以我把代码改成:

services.AddScoped();services.AddScoped, ModelValidator>();

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

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

所以我有配置方法:

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

SeedData 扩展名是:

public static class DataSeedExtensions {私有静态 IServiceProvider _provider;公共静态无效种子数据(这个 IApplicationBuilder 构建器){_provider = builder.ApplicationServices;_type = 类型;使用 (Context context = (Context)_provider.GetService()) {等待 context.Database.MigrateAsync();//插入数据代码}}

我错过了什么?

更新 - 一个可能的解决方案

将我的 Seed 方法更改为以下似乎有效:

using (IServiceScope scope =_provider.GetRequiredService().CreateScope()) {上下文上下文 = _provider.GetService<上下文>();//在数据库中插入数据}

解决方案

ASP.NET Core 2.1 的更新

在 ASP.NET Core 2.1 中,方法略有变化.通用方法与2.0类似,只是方法名称和返回类型有所改变.

public static void Main(string[] args){CreateWebHostBuilder(args).建造().种子();}公共静态 IWebHostBuilder CreateWebHostBuilder(string[] args){返回新的 WebHostBuilder()...;//不要在这里调用 .Build()}

适用于 ASP.NET Core 2.0

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

以下答案表明在调用任何 dotnet ef xxx 命令时会应用迁移和播种.

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

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

<块引用>

 公共类程序{public static void Main(string[] args){var host = BuildWebHost(args);主机.运行();}//工具将使用它来获取应用程序服务公共静态 IWebHost BuildWebHost(string[] args) =>新的 WebHostBuilder().UseKestrel().UseContentRoot(Directory.GetCurrentDirectory()).UseIISIntegration().UseStartup<启动>().建造();}

将其替换为旧的 Main 方法

public static void Main(string[] args){var host = BuildWebHost(args).种子();主机.运行();}

其中 Seed 是扩展方法:

public static IWebHost Seed(这个 IWebHost webhost){使用 (var scope = webhost.Services.GetService().CreateScope()){//或者解析 UserManager 并传递它,如果只是认为你想要播种的是用户使用 (var dbContext = scope.ServiceProvider.GetRequiredService()){SeedData.SeedAsync(dbContext).GetAwaiter().GetResult();}}}公共静态类 SeedData{公共静态异步任务 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(它解析作用域服务).

本质上它归结为创建一个新的作用域,解析您需要的类型并在完成后再次处理该作用域.

//serviceProvider 是来自 Configure(IApplicationBuilder app) 方法的 app.ApplicationServices使用 (var serviceScope = serviceProvider.GetRequiredService().CreateScope()){var db = serviceScope.ServiceProvider.GetService();如果(等待 db.Database.EnsureCreatedAsync()){等待种子数据库(db);}}

通过 app.ApplicationServices.GetService<MyService>() 直接解析服务的原因之一是 ApplicationServices 是应用程序(或生命周期)范围提供者和此处解析的服务一直有效,直到应用程序关闭.

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

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

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

所以永远不要在应用程序启动时使用上面首先创建一个范围并从中解析的模式来解析范围服务.

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>();

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);
    }
}

And the ModelValidator is as follows:

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

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

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

I get the error:

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'.

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

How to fix this?

UPDATE

So I changed the code to:

services.AddScoped<IValidationService, ValidationService>();

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

But I get the same error ...

UPDATE - Seed Data Code inside Configure method on Startup

So on Configure method I have:

if (hostingEnvironment.IsDevelopment())
  applicationBuilder.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
    }
}

What am I missing?

UPDATE - A possible solution

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
}

解决方案

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
}

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.

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

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

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();
  }

Replace this in your old Main method

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

    host.Run();
}

Where Seed is an extension method:

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 = ... });
    }
}

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);
    }
}

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.

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.

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天全站免登陆