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

时间:2016-08-01 16:44:49

标签: asp.net-core entity-framework-core

在ASP.NET Core项目中,我在Startup上有以下内容:

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

  services.AddTransient<IValidationService, ValidationService>();

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

ValidationService如下:

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如下:

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

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

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

我收到错误:

  

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

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

如何解决这个问题?

更新

所以我将代码更改为:

services.AddScoped<IValidationService, ValidationService>();

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

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

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

所以在Configure方法中我有:

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

SeedData扩展名为:

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

我错过了什么?

更新 - 可能的解决方案

将我的种子方法更改为以下内容似乎有效:

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

11 个答案:

答案 0 :(得分:42)

只是猜测导致错误的原因:

您正在使用DI和异步调用。如果调用堆栈中的某个位置返回void而不是Task,则会得到所描述的行为。此时呼叫结束并且上下文被处理掉。因此,请检查是否有异步调用返回void而不是Task。如果更改返回值,则objectdisposedexception可能已修复。

public static class DataSeedExtensions {
private static IServiceProvider _provider;

public static async Task SeedData(this IApplicationBuilder builder) { //This line of code

  _provider = builder.ApplicationServices;
  _type = type;

  using (Context context = (Context)_provider.GetService<Context>()) {

    await context.Database.MigrateAsync();
    // Insert data code

  }

}

在配置中:

if (hostingEnvironment.IsDevelopment()){
   await  applicationBuilder.SeedData();
}

关于如何解决此错误的博文:cannot-access-a-disposed-object-in-asp-net-core-when-injecting-dbcontext

答案 1 :(得分:18)

ASP.NET Core 2.1的更新

在ASP.NET Core 2.1中,方法略有改变。一般方法类似于2.0,只是方法名称和返回类型已被更改。

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和连接字符串的方式发生了一些变化。

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

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

根据this announcement,EF Core现在将使用静态BuildWebHost方法来配置整个应用程序,但不会运行它。

  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核心应用程序中应用实体框架核心的半官方模式应该应用,因为在应用程序启动期间没有请求,因此没有RequestServices(它解决了作用域服务)。

从本质上讲,它归结为创建一个新范围,解决您需要的类型,并在您完成后再次处理范围。

// 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是应用程序(或生命周期)范围提供程序,此处解析的服务在应用程序关闭之前保持活动状态。

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

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

范围容器就是生命有限的单例容器。

因此,永远不要使用上面首先创建范围并从中解析的模式来解决应用程序启动中的范围服务。

答案 2 :(得分:10)

我在使用asp.net核心时遇到了类似的问题。我的控制器中有一个异步POST方法,当它返回void时我会有这个异常。在我更改POST方法后返回TASK问题解决了。

更改自:

public async void PostAsync([FromBody] Model yourmodel)

public async Task PostAsync([FromBody] Model yourmodel)

答案 3 :(得分:4)

如果您使用的是任何async void,请替换为async Task

答案 4 :(得分:1)

问题是默认情况下DBContext的每个请求都是作用域的,但你有一些依赖于它的作用是瞬态的,因此它们没有相同的作用域,并且在使用它之前可能会丢弃DBContext

答案 5 :(得分:0)

类似于张扬,我必须更改控制器功能 发件人:

 public IActionResult MyFunc([FromBody]string apiKey)

收件人:

 public async Task<IActionResult> MyFunc([FromBody]string apiKey)

答案 6 :(得分:0)

有同样的问题。希望这对某人有帮助。除了使方法async并返回Task之外,您还需要确保在调用该方法的任何地方都可以等待该方法。

答案 7 :(得分:0)

在我的情况下,控制器方法是异步的,它返回一个任务,但在其中有2个await调用。第一次等待调用从服务中获取一些数据,第二次等待调用使用EF写入数据库。我不得不从第二个呼叫中删除等待,只有这样它才起作用。我没有从方法签名中删除异步/等待。我只是在不等待的情况下调用了第二种方法。

答案 8 :(得分:0)

我想与那些试图在其控制器中启动后台任务的人分享我的解决方案。这意味着您要启动任务,并且不想等待审计日志到数据库之类的结果。如果您正在创建任务并尝试在该任务中执行数据库操作,则会收到此错误;

无法访问已处置的对象。导致此错误的常见原因是,处理从依赖项注入中解析出来的上下文,然后稍后尝试在应用程序中的其他位置使用相同的上下文实例。如果在上下文上调用Dispose()或将上下文包装在using语句中,则可能会发生这种情况。如果使用依赖注入,则应让依赖注入容器来处理上下文实例。\ r \ n对象名称:“ DBContext”。

已经详细解释了。找到here

答案 9 :(得分:0)

就我而言,这不是异步问题,但是代码中有一个
using (DataContext dc=dataContext) {}
块,当然,上下文是在那之后处理的。

答案 10 :(得分:-1)

我遇到了类似的错误,后来得以解决。 我在不使用等待的情况下被称为异步方法。

旧代码 var newUser = _repo.Register(newUserToCreate);

已修复 var newUser =等待_repo.Register(newUserToCreate);