繁体   English   中英

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

[英]Cannot access a disposed object in ASP.NET Core when injecting DbContext

在 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() 或将上下文包装在 using 语句中,则可能会发生这种情况。 如果你使用依赖注入,你应该让依赖注入容器来处理上下文实例。 对象名称:“上下文”。

知道为什么在 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
}

只是猜测是什么导致了您的错误:

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

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

更改自:

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

其中 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中的核心应用,应以半官方的模式,因为在应用程序启动时没有要求,因此没有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 ,您还需要确保该方法在您调用它的任何地方都将被等待。

问题是默认情况下 DBContext 的范围是每个请求,但是您有一些依赖于它的东西,它的范围是瞬态的,因此它们没有相同的范围,并且 DBContext 可能会在您完成使用之前被处理掉

与 Yang Zhang 类似,我不得不改变我的控制器功能:

 public IActionResult MyFunc([FromBody]string apiKey)

至:

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

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

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

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

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

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

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

我在不使用await情况下调用 async 方法。

旧代码

var newUser = _repo.Register(newUserToCreate);

修复后

var newUser = await _repo.Register(newUserToCreate);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM