web-dev-qa-db-ja.com

DbContextの注入時にASP.NET Coreの破棄されたオブジェクトにアクセスできません

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を使用しているときにこのエラーが発生する理由は何でもあります。

これを修正する方法は?

[〜#〜] update [〜#〜]

そこで、コードを次のように変更しました。

services.AddScoped<IValidationService, ValidationService>();

services.AddScoped<IValidator<Model>, ModelValidator>();

しかし、私は同じエラーを受け取ります...

PDATE-Seed起動時のConfigureメソッド内のデータコード

だから私が持っている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
    }
}

私は何が欠けていますか?

更新-可能な解決策

My Seedメソッドを次のように変更すると動作するようです:

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

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に適用されます

アプリケーションの起動中にリクエストがないため、RequestServices(スコープサービスを解決する)がないため、適用する必要があるASP.NET CoreアプリケーションにEntity Framework Coreをシードする方法に関する準公式のパターンがあります。

本質的には、新しいスコープの作成に要約され、必要なタイプを解決し、終了したら再びスコープを破棄します。

_// 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>()を介してサービスを直接解決する理由の1つは、ApplicationServicesがアプリケーション(またはライフタイム)スコーププロバイダーであり、ここで解決されたサービスがアプリケーションがシャットダウンされるまで存続することです。

通常、スコープコンテナは、オブジェクトが既に存在する場合、その親コン​​テナから解決します。したがって、アプリケーションでこの方法でDbContextをインスタンス化すると、ApplicationServicesコンテナーで使用可能になり、要求が発生すると、子コンテナーが作成されます。

DbContextを解決するとき、スコープは親コンテナに既に存在するため、スコープとして解決されません。そのため、代わりに親コンテナのインスタンスが返されます。しかし、播種中に廃棄されているため、アクセスできません。

スコープコンテナは、寿命が制限されたシングルトンコンテナです。

したがって、最初にスコープを作成してそこから解決するという上記のパターンを使用して、アプリケーションのスタートアップでスコープ付きサービスを解決しないでください。

18
Tseng

エラーの原因を推測してください:

DIおよび非同期呼び出しを使用しています。コールスタックのどこかにTaskの代わりにvoidを返すと、説明されている動作が得られます。その時点で、呼び出しは終了し、コンテキストは破棄されます。そのため、Taskではなくvoidを返す非同期呼び出しがあるかどうかを確認してください。戻り値を変更すると、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

  }

}

そしてconfigureで:

if (hostingEnvironment.IsDevelopment()){
   await  applicationBuilder.SeedData();
}

このエラーの修正方法に関するブログ投稿: cannot-access-a-disposed-object-in-asp-net-core-when-injecting-dbcontext

51
Peter

Asp.netコアで動作する同様の問題がありました。コントローラに非同期POSTメソッドがあり、それがvoidを返すと、この例外が発生します。POSTメソッドがTASKを返すと、問題は解決しました。

から変更する:

public async void PostAsync([FromBody] Model yourmodel)

public async Task PostAsync([FromBody] Model yourmodel)
13
Yang Zhang

問題は、デフォルトでDBContextがリクエストごとにスコープされることですが、それに依存するものが一時的なスコープであるため、同じスコープを持たず、使用する前にDBContextが破棄される可能性があります

1
Joe Audette

Yang Zhangと同様に、コントローラーの機能を変更する必要がありました。

 public IActionResult MyFunc([FromBody]string apiKey)

に:

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

同じ問題がありました。これが誰かを助けることを願っています。メソッドasyncを作成してTaskを返すことに加えて、メソッドを呼び出している場所でも必ず待機するようにする必要があります。

0
dave_077