4

I'm working on .NET Core Web API and I have one endpoint where I want to run three operations in parallel. All three of them use the same database, so I need three copies of DbContext. I created a simple Factory class, which I later inject into my "Data" class.

Is it possible (if it's, is a good practice), to inject DbContext into my factory class (using built in .NET Core IoC) and when someone calls "CreateMyDbContext" method, just deep clone the one which was injected at the beginning?

EDIT: Here is the example with the DbContext Pool:

public class FooData : IFooData
{
    private readonly Func<DisposableScopedContextWrapper> _func;

    public FooData(Func<DisposableScopedContextWrapper> func)
    {
        _func = func;
    }

    public async Task<List<Apple>> GetApples()
    {
        using (var wrapper = _func())
        {
            var apples = await wrapper.Context.Apples.FromSqlRaw("SELECT.... complicated query").ToListAsync();
            return apples;
        }
    }

    public async Task<List<Orange>> GetOranges()
    {
        using (var wrapper = _func())
        {
            var oranges = await wrapper.Context.Oranges.FromSqlRaw("SELECT.... complicated query").ToListAsync();
            return oranges;
        }
    }
}


public class FooService
{
    private readonly IFooData _fooData;

    public FooData(IFooData fooData)
    {
        _fooData = fooData;
    }

    public async Task<List<Fruit>> GetFruits()
    {
        var appleTask = _fooData.GetApples();
        var orangeTask = _fooData.GetOranges();

        (var result1, var result2) = await (appleTask, orangeTask).WhenAll();

        // ... 
    }
}
Ish Thomas
  • 2,270
  • 2
  • 27
  • 57
  • https://stackoverflow.com/questions/44063832/what-is-the-best-practice-in-ef-core-for-using-parallel-async-calls-with-an-inje – Deepak Mishra May 04 '20 at 21:12
  • Does this answer your question? [What is the best practice in EF Core for using parallel async calls with an Injected DbContext?](https://stackoverflow.com/questions/44063832/what-is-the-best-practice-in-ef-core-for-using-parallel-async-calls-with-an-inje) – Pavel Anikhouski May 05 '20 at 07:29
  • @PavelAnikhouski It looks like I need to use DbContext pool, so I don't think this is the best question/answer. – Ish Thomas May 05 '20 at 14:01

1 Answers1

4

I definitely would not recommend any deepcloning for multiple reasons, one of them being that you will need to figure out a lot of EF internals to make it right, and internals can change (and you will need to spend some time on it).

Second option would be just creating your context manually, which I would recommend against also cause modern infrastructure uses DbContext pooling.

So what you can to register Func<DbContext> (or create your own factory) like this:

 services.AddSingleton<Func<DbContext>>(provider => () => 
        {
            var scope = provider.CreateScope();
            return scope.ServiceProvider.GetRequiredService<DbContext>();
        });    

the issue here is that scope here would not be disposed and you can't (if you have default scope for your DbContext) dispose the scope inside the Func cause your context will be disposed also. So you can try creating some disposable wrapper so you can manually dispose everything like this:

    public class DisposableScopedContextWrapper : IDisposable
    {
        private readonly IServiceScope _scope;
        public DbContext Context { get; }

        public DisposableScopedContextWrapper(IServiceScope scope)
        {
            _scope = scope;
            Context = _scope.ServiceProvider.GetService<DbContext>();
        }

        public void Dispose()
        {
            _scope.Dispose();
        }
    } 

    services.AddSingleton<Func<DisposableScopedContextWrapper>>(provider =>() => 
    {
         var scope = provider.CreateScope();
           return new DisposableScopedContextWrapper(scope);
    });

Inject in your classes Func<DisposableScopedContextWrapper> func and use it

 using (var wrapper = func())
 {
      wrapper.Context...
 }
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • I'm not sure If I fully understood your answer. To be clear, I have 3 tasks and I want to do `await Task.WhenAll(tasks);`. These 3 heavy operations are independent, "read-only", database operations. If not `clone` I thought I'm gonna just use simple Factory that would "new-up" DbContext. That Factory I will inject to DataAccess class. Then in each "heavy method" I would use `using` to create and dispose new DbContext each time. – Ish Thomas May 05 '20 at 03:25
  • *** I'm not sure if "WhenAll" actually does that, but to be clear... I want to run them in parallel – Ish Thomas May 05 '20 at 03:39
  • As I said in my answer, nowdays EF Core uses [DbContext pools](https://stackoverflow.com/questions/48443567/adddbcontext-or-adddbcontextpool)(it reuses them or parts of them after scope is closed) to improve your app's overall performance, so I would not recommend creating new instances manually (via `new DbContext`). – Guru Stron May 05 '20 at 07:26
  • Yeah, that's what confused me. So you're saying that .NET Core can (behind the scenes) use the same DbContext to perform two parallel databases actions at the same time? – Ish Thomas May 05 '20 at 12:59
  • No, i'm not sayng that. I'm saying that it has a pool of contexts, when you resolve one in your code you get one from the pool (if you have "latest" standard setup) and when you are done using it (when the scope ends) it is returned to the pool. Please read the link in my previous comment. Also creating contexts using `new` you can loose some benefits from your DI setup. – Guru Stron May 05 '20 at 13:03
  • I think I got it... but I'm still confusing about when should I dispose that wrapper. I added to my question an example of dummy "Data" and "Service" classes. There is only one object injected into Service class and I think it's possible that the wrapper may be disposed by the "Apple method" while "Orange method" still in progress. Does my example present your suggestion? – Ish Thomas May 05 '20 at 14:24
  • every invocation of `func` should return you new wrapper which you dispose as soon as you no longer need it, exactly as you would do with your context. – Guru Stron May 05 '20 at 14:27