I am working on a legacy Asp.Net MVC 5 project. We do not have a proper data access layer, and our business layer classes directly interact with the DbContext, which is injected.
I am facing a problem, the essence of which I have captured in a simple example.
I have classes ServiceA
, ServiceB
, and ServiceC
(I have omitted interfaces for this example).
They all receive the same DbContext
instance in the constructor via DI. In ServiceC
, I would like to wrap a transaction around several database operations, which are performed by the other services. This works, since the services all receive the same instance of the DbContext
.
public class ServiceC
{
public ServiceC(DbContext context, ServiceA serviceA, ServiceB serviceB)
{
_context = context;
_serviceA = serviceA;
_serviceB = serviceB;
}
public DoSomething()
{
using var transaction = context.Database.BeginTransaction()
try
{
_serviceA.DoSomethingThatInvolvesDbContext1();
_serviceB.DoSomethingThatInvovlesDbContext2();
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
}
The problem I see with this design, is that ServiceC
is designed in such a way, that it requires ServiceA
and ServiceB
to share the same DbContext
instance. If I decide to configure my DI container differently, it fails to work.
Isn’t it a poor design, if the implementation relies on a specific DI container configuration?
The only other thing I could think of, is to introduce factories for the services:
public class ServiceC
{
public ServiceC(DbContext context, IServiceAFactory serviceA, IServiceBFactory serviceB)
{
_context = context;
_serviceA = serviceAFactory.Get(context);
_serviceB = serviceBFactory.Get(context);
}
// Rest of implementation
}
But this seems overly complex..
2
Answers
It is a bit hard to advice without knowing the whole context but.
Technically if you register your DBContext as Scoped as well as all 3 services you will inject the same instance of DBContext to all of them when you resolve top one. The only thing you need to ensure is to create a scope before. Depending on usage that can be already true.
Other option is passing of DBContext to service methods as parameter. What looks for me as more reliable solution.
Or you can refactor.
Manual resolution from your example works as well.
You should configure your DI services in a way that makes sense for your application.
Broadly speaking, the correct way to configure dbcontexts is to have them constructed as
Scoped
dependencies, which means you will get a new instance of the dbcontext for each request but the same instance will be shared by all services within the request lifecycle.So the simple answer here is stick to using scoped-level db contexts. Singletons/Transient dependencies to not make much sense for db contexts.