What is the best practice for creating dbcontext (EF core) if the parent object is a singleton?

botframework c# dbcontext dependency-injection entity-framework-core

Question

I use Virtual Assistant. I am going to use Entity Framework Core for saving state. I met a problem. IStorage interface injects like a singleton because other objects which use this interface injects as a singleton.

        services.AddSingleton<IStorage>(new MyDbStorage()); //<-- my storage with dbcontext
        services.AddSingleton<UserState>();          //This class has ctor with IStorage param
        services.AddSingleton<ConversationState>();  //And this class has ctor with IStorage param
        services.AddSingleton(sp =>
        {
            var userState = sp.GetService<UserState>();
            var conversationState = sp.GetService<ConversationState>();
            return new BotStateSet(userState, conversationState);
        });

See on github for details. I know, the global dbcontext is a bad idea. Could you suggest me some options for creating dbcontext? I think about DbContextFactory, but I don't know how exactly create it.

UPDATE I have updated code. See the first line.

1
0
9/16/2019 4:48:07 AM

Accepted Answer

First of all do not abstract MyDbContext with interface, otherwise you won't be able to access EF fetures or will try hard to hide EntityFramework namespace. Use AddDbContext extension. This way DbContext will be registered as Scoped what is the best lifetime for it.

Inject IServiceProvider into your singleton UserState and ConversationState, then create scope and resolve MyDbContext instance within this scope on demand only.

public class UserState
{
    private readonly IServiceProvider _provider;

    public UserState(IServiceProvider provider)
    {
        _provider = provider;
    }

    public async Task DoWorkAsync()
    {
        using (var scope = _provider.CreateScope())
        using (var ctx = scope.ServiceProvider.GetService<MyDbContext>())
        {
            // do work
            await ctx.SaveChangesAsync();
        }
    }
}

But if work done by UserState and ConversationState should be performed as single transaction (that's mean on same DbContext instance), it's better to resolve MyDbContext in BotStateSet and pass in as argument into methods of UserState and ConversationState.

I'm not familiar with Virtual Assistant, but if it possible to use Transient lifetime for your state classes (at least for UserState and ConversationState) it will allow you to inject MyDbContext into constructor avoiding passing it as argument.

1
9/15/2019 7:13:24 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow