Reputation: 4731
My understanding is that one key advantage of using dependency injection is that you don't tightly bind to types, so you can later swap out pieces of your architecture with less work than otherwise. If that's the case, why is it that I see code like this:
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
...
}
The method I'm curious about is:
public static IServiceCollection AddDbContext<TContext>([NotNullAttribute] this IServiceCollection serviceCollection, [CanBeNullAttribute] Action<DbContextOptionsBuilder> optionsAction = null, ServiceLifetime contextLifetime = ServiceLifetime.Scoped, ServiceLifetime optionsLifetime = ServiceLifetime.Scoped) where TContext : DbContext;
So, when I want to access this type, I place an AppDbContext class type in my constructor, not an IAppDbContext interface. But why? Is this too much abstraction? And if so, why bother registering anything to the container with interfaces?
Upvotes: 2
Views: 5418
Reputation: 1969
To resolve this, use one of the overloads having 2 generic type arguments, which allow you to specify both the service interface/class you want to register as well as the DbContext derived class implementing it.
services.AddDbContext<IMyDbContext, MyDbContext>(options =>
options.UseSqlServer(...));
Upvotes: 10
Reputation: 56869
So, when I want to access this type, I place an AppDbContext class type in my constructor, not an IAppDbContext interface. But why? Is this too much abstraction?
The generic type constraint indicates that any DbContext
can be registered. ApplicationDbContext
is a concrete implementation of that abstraction. Adding yet another abstraction doesn't serve to accomplish anything.
And if so, why bother registering anything to the container with interfaces?
We don't register interfaces with the DI container, we register abstractions. Abstractions include both abstract classes and interfaces and either serve the purpose of decoupling that we are after when using dependency injection.
In this particular case, there are other goals.
DbContext
. This makes it easily possible to use more than one database context within an application.DbContext
needs to be registered with the right lifetime so it is disposed after every request, and registering it with the DI container is the easiest way to accomplish that.DbContext
must also be a single instance for the request. Opening more than one instance can cause synchronization issues with the database or even runtime errors. Registering with the DI container is a convenient way to get per request lifetime.That said, this is a special case and by no means is the only way to get the job done - if you are fanatical about ensuring that it is registered as an abstraction instead of a concrete type, you could always use the strategy pattern to do so (although I would argue it is over the top if you don't actually have a need to switch between databases at runtime).
For the rest of the application, indeed the best practice is to register as the abstraction rather than the concrete type to allow for easy swapping/mocking of the dependency.
Upvotes: 3