きじのしっぽさん作成の逆引きEntity Framework Core 3.1ハンドブック
が届いたのでEntityFramework Coreに再入門してみました。
その過程でちょっと見慣れないエラーに遭遇したので、記録しておきます。
環境は.NET Core 3.1 LTSのMicrosoft.EntityFrameworkCore.InMemory Version 3.1.0です。
目次
再現手順
下記コマンドでGeneric Host(汎用ホスト)なテンプレートを作ります。
dotnet new worker -o EFCore.Sample cd EFCore.Sample dotnet add package Microsoft.EntityFrameworkCore.InMemory
以下のコードを書きました。
public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureServices((hostContext, services) => { var con = hostContext.Configuration.GetConnectionString("DbName"); services.AddDbContext<TodoContext>(opt => opt.UseInMemoryDatabase(con)); services.AddSingleton<IRepository, TodoRepository>(); services.AddHostedService<Worker>(); }); }
public interface IRepository { } public class TodoRepository : IRepository { private readonly TodoContext _db; public TodoRepository(TodoContext db) => _db = db; } public class TodoContext : DbContext { public DbSet<TodoEntity> Todos { get; set; } public TodoContext(DbContextOptions<TodoContext> opt) : base(opt) { } } public class TodoEntity { public int Id { get; set; } }
{ "ConnectionStrings": { "DbName": "test" } }
起動すると...
こんなエラーが出ました。
ログはこんな感じ
System.AggregateException HResult=0x80131500 Message=Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: EFCore.Sample.Services.IRepository`1[EFCore.Sample.Entities.TodoEntity] Lifetime: Singleton ImplementationType: EFCore.Sample.Services.TodoRepository': Cannot consume scoped service 'EFCore.Sample.Contexts.TodoContext' from singleton 'EFCore.Sample.Services.IRepository`1[EFCore.Sample.Entities.TodoEntity]'.) (Error while validating the service descriptor 'ServiceType: Microsoft.Extensions.Hosting.IHostedService Lifetime: Singleton ImplementationType: EFCore.Sample.Worker': Cannot consume scoped service 'EFCore.Sample.Contexts.TodoContext' from singleton 'EFCore.Sample.Services.IRepository`1[EFCore.Sample.Entities.TodoEntity]'.) Source=Microsoft.Extensions.DependencyInjection スタック トレース: ... この例外は、最初にこの呼び出し履歴でスローされました: ... 内部例外 1: InvalidOperationException: Error while validating the service descriptor 'ServiceType: EFCore.Sample.Services.IRepository`1[EFCore.Sample.Entities.TodoEntity] Lifetime: Singleton ImplementationType: EFCore.Sample.Services.TodoRepository': Cannot consume scoped service 'EFCore.Sample.Contexts.TodoContext' from singleton 'EFCore.Sample.Services.IRepository`1[EFCore.Sample.Entities.TodoEntity]'. 内部例外 2: InvalidOperationException: Cannot consume scoped service 'EFCore.Sample.Contexts.TodoContext' from singleton 'EFCore.Sample.Services.IRepository`1[EFCore.Sample.Entities.TodoEntity]'.
このエラーはなんぞ?
調べた結果、本来Singletonで登録しなければいけないサービスを、Singleton以外の生存期間でDI Containerに登録すると発生するらしい。
参考: Cannot Consume Scoped Service From Singleton – A Lesson In ASP.net Core DI Scopes
AddHostedService
なおしかた
仕方ないのでservices.AddDbContext
の第2引数にSingleton Lifetimeを指定する。
これでDbContext
を継承したTodoContext
がSingletonでDI Containerに登録される。
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureServices((hostContext, services) => { var con = hostContext.Configuration.GetConnectionString("DbName"); - services.AddDbContext<TodoContext>(opt => opt.UseInMemoryDatabase(con)); + services.AddDbContext<TodoContext>(opt => opt.UseInMemoryDatabase(con), ServiceLifetime.Singleton); services.AddSingleton<IRepository<TodoEntity>, TodoRepository>(); services.AddHostedService<Worker>(); });
DbContext
をnewして使う場合はIServiceScopeFactory
のCreateScope
でスコープを作ったりして回避するっぽい。
おわり
コンパクトにまとまった良い本でした📘✨