SB2055 SB2055 - 2 months ago 28
C# Question

Where should I create my DbContext among my services?

My application is built around "Services" like this:

public class ProfileService : BaseService {

private CommService _commService;

public ProfileService(CommService commService) {
_commService = commService;
}

public ApiResponseDto GetProfile(string profileUsername) {
using (var db = new appContext()){ db.DoStuff(); }
}
}


What I would like to do is push the
db
instantiation into
BaseService
, but I don't want to create a
dbContext
and incur the cost of such when I don't need it. So I'm thinking about doing something like this:

public class BaseService {
public AppContext _db;

public AppContext db(){
return _db ?? new AppContext();
}
}


And then all of my methods will access the db via
db().DoStuff()
.

I don't like the idea of parentheses everywhere, but I like the idea of cleaning up my services footprints more.

My question is - if I create an instance of
DbContext
and don't use it, is there any cost? Or is the object just instantiated for future use? I hate to ask for opinions here as I know it's not allowed, but is this a step in the right direction of keeping things DRY?

Answer

Unit of Work Pattern

DbContext is effectively an implementation of the 'unit of work' pattern - once the DbContext is created, all changed done to the DbSet are then persisted in one go when you call SaveChanges.

So the further question you need to answer in order to properly answer your question is: What is the scope of the changes that make up your unit of work? In other words - what set of changes need to be made atomically - all succeed, or all fail?

A practical (if example of this - say you have an API endpoint that exposes an operation allowing the client to submit an order. The controller uses OrderService to submit the order, and then InventoryService to update the inventory associated with the items in the order. If each service has their own DbContext, you have a risk that the OrderService will succeed to persist the order submission, but the InventoryService will fail to persist the inventory update.

Dependency Injection

To combat this, a common pattern is to create a context per-request and let your IoC container create and dispose the context, and make it available to inject into services per request. This blog post gives a few options for DbContext management, and includes an example of configuring Ninject to do it.

What this means is your ctor will look like:

public ProfileService(CommService commService, AppContext context) {
    _commService = commService;
    _context = context;
}

And you can safely use the context there without having to worry about how it was created or where it came from.

Medhi's DbScopeFactory

However, my preferred approach for more complex applications is an excellent open source library documented up here: http://mehdi.me/ambient-dbcontext-in-ef6/. Injecting DbContext per request will work fine for simpler applications, but as your application gets more involved (e.g. multiple Contexts per application, multiple databases etc.), the finer grain control offered by his IDbContextScopeFactory is invaluable.

Edit to Add - Pros and Cons of Injection vs Construction

Following your comment asking for pros/cons of the approach you proposed, I'd say that generally, injection of dependencies (including DbContext) is a far more flexible and powerful approach, and can still achieve the goal of ensuring your devs don't have to be concerned with dbcontext lifecycle management.

The pros and cons are generally the same for all instances of dependency injection not just db context, but here are a few concrete issues with constructing the context within the service (even in a base service):

  • each service will have its own instance of the dbcontext - this can lead to consistency problems where your unit of work spans tasks carried out by multiple services (see example above)
  • It will be much more difficult to unit test your services, as they are constructing their own dependency. Injecting the dbcontext means you can mock it in your unit tests and test functionality without hitting the database
  • It introduces unmanaged state into your services - if you are using dependency injection, you want the IoC container to manage the lifecycle of services. When your service has no per-request dependencies, the IoC container will create a single instance of the service for the whole application, which means your dbcontext saved to the private member will be used for all requests/threads - this can be a big problem and should be avoided.
    • (Note: this is less of an issue if you are not using DI and constructing new instances of the services within controllers, but then you are losing the benefits of DI at the controller level as well...)
  • All services are now locked to using the same DbContext instance - what if, in the future you decide to split your database and some services need to access a different DbContext? Would you create two different BaseServices? Or pass in configuration data to allow the base service to switch? DI would take care of that, because you would just register the two different Context classes, and then the container would provide each service with the context it needs.
  • Are you returning IQueryables anywhere? If you are, then you run a risk that the IQueryable will cause the Db to hit even after the DbContext has gone out of scope - it may have been disposed by the garbage collector and will not be available.

From a dev perspective, I think nothing is simpler than the DI approach - simply specify the DbContext in your constructor, and let the DI container container take care of the rest.

If you are using DbContext per request, you don't even have to create or dispose the context, and you can be confident that IQueryables will be resolvable at any point in the request call stack.

If you use Mehdi's approach, you do have to create a DbContextScope, but that approach is more appropriate if you are going down a repository pattern path and want explicit control over the context scope.

As you can see, I'm far less concerned about the computational cost of constructing a DbContext when it's not needed (as far as I can tell, it's a fairly low cost until you actually use it to hit the db), and more concerned about the application architecture that permits unit testing and decoupling from dependencies.

Comments