注入DbContext時無法訪問ASP.NET Core中的已處置對象

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()或將上下文包裝在using語句中,則可能會發生這種情況。如果使用依賴項注入,則應該讓依賴項注入容器負責處理上下文實例。對象名稱:'上下文'。

知道為什麼我在ModelValidator中使用Context時遇到此錯誤。

如何解決這個問題?

UPDATE

所以我將代碼更改為:

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
}

一般承認的答案

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 Core應用程序中應用Entity Framework Core的半官方模式應該應用,因為在應用程序啟動期間沒有Request,因此沒有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時,它不會被解析為作用域,因為它已經存在於父容器中,因此將返回父容器的實例。但由於它在播種期間被處理掉,因此無法進入。

範圍容器只是具有有限生命週期的單例容器。

因此,永遠不要使用上面的模式來解決應用程序啟動中的作用域服務,首先創建一個作用域並從中進行解析。


熱門答案

只是猜測導致錯誤的原因:

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

有關如何修復此錯誤的博客文章: 無法訪問-do-dispos-object-in-asp-net-core-when-injecting-dbcontext



Related

許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因