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

本文关键字:Core 释放 对象 NET ASP DbContext 访问 注入 | 更新日期: 2023-09-27 17:59:27

在一个 ASP.NET 核心项目中,我在启动时有以下内容:

  services.AddDbContext<Context>(x => x.UseSqlServer(connectionString));
  services.AddTransient<IValidationService, ValidationService>();
  services.AddTransient<IValidator<Model>, ModelValidator>();

验证服务如下:

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

模型验证器如下:

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

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

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

我收到错误:

System.ObjectDisposedException:无法访问已释放的对象。一个 此错误的常见原因是释放已解决的上下文 从依赖注入,然后尝试使用它 应用程序中其他位置的上下文实例。 这可能会发生在你 在上下文中调用 Dispose((,或将上下文包装在 using 语句。 如果您使用的是依赖注入,则应 让依赖注入容器负责释放上下文 实例。对象名称:"上下文"。

知道为什么我在模型验证器中使用上下文时会出现此错误。

如何解决这个问题?

更新

所以我将代码更改为:

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

我错过了什么?

更新 - 可能的解决方案

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

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

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

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

您正在使用 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();
}

有关如何修复此错误的博客文章:注入 DbContext 时无法访问 ASP.NET Core 中已释放的对象

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

更改自:

public async void PostAsync([FromBody] Model yourmodel)

public async Task PostAsync([FromBody] Model yourmodel)

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 酷睿2.0

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

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

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

根据此公告,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 酷睿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 时,它不会解析为作用域,因为它已存在于父容器中,因此将改为返回父容器的实例。但由于它在播种期间已被处理,因此无法访问。

作用域容器不是别的,而是生存期有限的单例容器。

因此,切勿使用上述模式(首先创建作用域并从中解析(在应用程序启动中解析作用域服务。

如果您

正在使用任何async void请将其替换为async Task

有同样的问题。希望这对某人有所帮助。除了使方法async并返回Task之外,您还需要确保无论在哪里调用该方法,都将

等待该方法。

和张杨类似,我不得不改变我的控制器功能从:

 public IActionResult MyFunc([FromBody]string apiKey)

自:

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

DBContext 默认情况下是按请求限定的,但您有依赖于它的东西作用域为瞬态,因此它们没有相同的作用域,并且 DBContext 可能会在您使用它之前被释放

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

无法访问已释放的对象。此错误的常见原因是释放了通过依赖项注入解析的上下文,然后尝试在应用程序的其他位置使用相同的上下文实例。如果在上下文中调用 Dispose(( 或将上下文包装在 using 语句中,则可能会发生这种情况。如果使用依赖关系注入,则应让依赖关系注入容器负责释放上下文实例。''r'对象名称:"DBContext"。

已经详细解释了。在这里找到它

就我而言,这不是异步问题,但代码有一个

using (DataContext dc=dataContext) {}块,当然,上下文在那之后被处理掉了。

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

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

我在不使用 await 的情况下调用异步方法.

旧代码

var newUser = _repo.Register(newUserToCreate);

修复后

var newUser = await _repo.Register(newUserToCreate);