DbContextをインジェクトするときにASP.NET Coreの破棄されたオブジェクトにアクセスできない

asp.net-core entity-framework-core

質問

ASP.NETコアプロジェクトでは、起動時に次のものがあります。

  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ステートメントでコンテキストをラップしている場合に発生する可能性があります。依存性注入を使用している場合は、依存性注入コンテナにコンテキストインスタンスの破棄を任せる必要があります。オブジェクト名: 'Context'

ModelValidator内でContextを使用するときにこのエラーが発生するのはなぜかという考え。

これを修正するには?

更新

だから私はコードを変更しました:

services.AddScoped<IValidationService, ValidationService>();

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

しかし、私は同じエラーが出ます...

UPDATE - スタートアップ時の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
    }
}

何が足りないの?

UPDATE - 可能な解決策

私のSeedメソッドを以下のように変更するとうまくいくようです。

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コアツール( dotnet ef migrationsなど)が設計時にDbContextと接続文字列を決定する方法にいくつかの変更がありました。

以下の答えは、 dotnet ef xxxコマンドのいずれかを呼び出すときに移行とシードが適用されることを示しています。

EFコアツールのデザインタイムインスタンスを取得するための新しいパターンは、 BuildHostWeb静的メソッドを使用することです。

この発表によれば、EFコアは静的な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 Core 1.xにも適用されます。

ASP.NETコアアプリケーションにEntity Framework Coreをシードする方法については、アプリケーション起動時にリクエストがないため、スコープサービスを解決する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>()を介してサービスを直接解決する理由の1つは、 ApplicationServicesがアプリケーション(または生涯)スコーププロバイダであり、アプリケーションがシャットダウンされるまでここで解決されたサービスが存続することです。

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

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

スコープコンテナは、他には何もなく、寿命が限られたシングルトンコンテナです。

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


人気のある回答

あなたのエラーの原因を推測するには:

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

  }

}

そして、構成:

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

このエラーを修正する方法のブログ記事: can-access-a-deployed-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は合法ですか? はい、理由を学ぶ