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

Posted

技术标签:

【中文标题】注入 DbContext 时无法访问 ASP.NET Core 中已释放的对象【英文标题】:Cannot access a disposed object in ASP.NET Core when injecting DbContext 【发布时间】:2016-12-06 20:08:55 【问题描述】:

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

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

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

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

所以我有配置方法:

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
    

我错过了什么?

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

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

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

【问题讨论】:

您是否在启动时为您的数据库播种?如果是这样,如果您在启动期间(即在 Configure 方法中)解析 DbContext 并在那里使用它来播种数据,则显示您的代码存在缺陷,而在此之前没有创建范围提供程序 哇,当我从 Startup 中删除我的种子代码时,我不再遇到问题了......奇怪的是,当我开始使用 ValidationService 时,我才收到错误消息。你有什么建议来解决这个问题? 我的意思是,我按照 ASP.NET Core 的建议在配置中播种数据......还有其他选择吗?为什么在特别使用 ValidationService 时会失败? 发布您的代码,我认为您没有为播种创建范围 @janhartmann 检查我的更新......它似乎工作。 【参考方案1】:

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

您正在使用 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

【讨论】:

“如果在调用堆栈中的某个位置返回 void 而不是 Task,你会得到所描述的行为。”谢谢! :) 非常感谢,我头疼了一阵子! Configure方法中await apllicationBuilder.SeedData()方法本身不是异步的,返回void时,如何调用await apllicationBuilder.SeedData() 取决于版本,我在 Core 2.1 中尝试过,如上所述需要服务范围。 .... 你猜对了。我会永远自己想出这个:)【参考方案2】:

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

更改自:

public async void PostAsync([FromBody] Model yourmodel)

public async Task PostAsync([FromBody] Model yourmodel)

【讨论】:

你拯救了我的一天!谢谢。 也可以在我的 Get 方法中使用! 为我工作:D @Barnsley 我认为这是因为 await 仅适用于 Task。如果返回 void,框架将不会在内部处理它。【参考方案3】:

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

Seed 是一个扩展方法:

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(解析范围服务)。

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

// 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&lt;MyService&gt;() 直接解析服务的原因之一是ApplicationServices 是应用程序(或生命周期)范围提供程序,并且此处解析的服务在应用程序关闭之前一直保持活动状态。

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

现在在解析 DbContext 时,它不会被解析为作用域,因为它已经存在于父容器中,因此将返回父容器的实例。但由于它已在播种过程中被丢弃,因此无法访问。

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

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

【讨论】:

【参考方案4】:

如果您使用任何async void,请将其替换为async Task

【讨论】:

【参考方案5】:

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

【讨论】:

这对我来说是个问题!我忘了在我的控制器中一直添加等待(它首先经过多个层,然后在数据层中抛出异常,所以它非常隐藏)。谢谢!【参考方案6】:

问题是默认情况下 DBContext 是每个请求的范围,但是你有依赖于它的东西作为瞬态,所以它们没有相同的范围,并且 DBContext 可能在你完成使用它之前就被释放

【讨论】:

我将 AddTransient 更改为 AddScoped 并得到同样的错误...知道为什么吗? 在这种情况下有什么解决办法? 在这种情况下将 DbContext 设置为 Transient 不是正确的解决方案。但是,在查看源代码后,我注意到异步任务和非异步任务之间存在差异。此外,如果您使用异步策略 dbcontext 类,请确保在控制器方法和任何与 dbcontext 交互的类上维护异步任务。经验法则是,如果您在 dbcontext 中的任何位置使用异步任务,则必须将异步任务冒泡到控制器和服务层。我希望这可以帮助任何面临这个问题的人。【参考方案7】:

和张扬一样,我不得不改变我的控制器功能 来自:

 public IActionResult MyFunc([FromBody]string apiKey)

收件人:

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

【讨论】:

【参考方案8】:

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

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

已经详细解释了。找到它here

【讨论】:

【参考方案9】:

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

【讨论】:

【参考方案10】:

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

【讨论】:

【参考方案11】:

我遇到了类似的错误,后来能够解决它。

我在调用异步方法时没有使用await

旧代码

var newUser = _repo.Register(newUserToCreate);

已修复

var newUser = await _repo.Register(newUserToCreate);

【讨论】:

以上是关于注入 DbContext 时无法访问 ASP.NET Core 中已释放的对象的主要内容,如果未能解决你的问题,请参考以下文章

C#:使用 IQueryable 注入 DbContext 时,无法访问 ASP.NET Core 中的已处置对象

访问 DBContext 而不一直注入它

注入 dbContext 时返回“找不到与给定参数匹配的构造函数”

如何将 DBContext 注入 HTTPModule

autofac 多个dbcontext 上下文不一致

将 DbContext 与依赖注入一起使用