簡體   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