Efcore: Better way to get application services from within a derived DbContext instance

Created on 5 Dec 2016  路  9Comments  路  Source: dotnet/efcore

I am trying to get a service from within a derived DbContext using the following code:

((IInfrastructure<IServiceProvider>)this).GetService<IMyService>();

The IMyService was previously registered in ConfigureServices in the Startup class. However, the call returns null. I am assuming this is because EF creates a service scope.

My question is how can I get a service that was registered in the Startup class from within a derived DbContext instance. Or, if that makes more sense, how do I register a service in EF's service scope.

closed-fixed type-enhancement

Most helpful comment

@ajcvickers IMHO, not being able to access the application services from EF code is really annoying, as it makes a bunch of scenarios really hard to implement. For instance, you can't create a IModelCustomizer that relies on a service defined in Startup.ConfigureServices, as EF uses its own internal service provider.

@ajcvickers was there a particular reason not to make the application service provider accessible from EF's infrastructure stuff?

All 9 comments

@alaatm Add a constructor parameter for the service so that it is constructor-injected into the context in the normal way.

@ajcvickers IMHO, not being able to access the application services from EF code is really annoying, as it makes a bunch of scenarios really hard to implement. For instance, you can't create a IModelCustomizer that relies on a service defined in Startup.ConfigureServices, as EF uses its own internal service provider.

@ajcvickers was there a particular reason not to make the application service provider accessible from EF's infrastructure stuff?

@ajcvickers I was hoping there was a way to use the internal service provider for this instead of using constructor params.

@alaatm @PinpointTownes If you need to use application services as dependencies for replacements for some of EF's internal services, then you can merge the service provider's together. See this blog post for more details: https://blog.oneunicorn.com/2016/10/27/dependency-injection-in-ef-core-1-1/. Note that this does not usually require that you use the context as a service locator--it would still be better to use constructor injection for your dependencies.

@ajcvickers thanks for the link!

I'm not sure I like the "replace the internal service provider" approach, as it would force the users of my library to do that in their own code... and as mentioned, it has important implications for scoping and caching.

Concerning the new ReplaceService option, I suppose it doesn't resolve the replaced service in the context of the application service?

@PinpointTownes This is the usual way to replace the internal service provider for ASP.NET apps:
C# services .AddEntityFrameworkInMemoryDatabase() .AddDbContext<MyContext>((p, b) => b .UseInMemoryDatabase() .UseInternalServiceProvider(p));

With regards to ReplaceService, EF has no knowledge of the application service provider unless it passed to EF using UseInternalServiceProvider.

@ajcvickers yeah. But as I mentioned in my (edited) message, as a library writer, I can't assume the internal service provider has been replaced by the application service provider.

Anyway, it's good to know a few options exist (even if none is ideal).

@PinpointTownes Can you explain what you are trying to do? Typically as a library writer you cannot assume that the application is using D.I. at all.

@ajcvickers sure, where's the big picture:

Historically, OpenIddict (an ASP.NET Core library that issues security tokens) used to depend on ASP.NET Core Identity. To make things easier for most users, it comes with a generic OpenIddictDbContext that inherits from IdentityDbContext and adds a bunch of entity sets. Since the plan is to decouple the entire library from Identity, this inheritance has to disappear.

I could of course update OpenIddictDbContext to inherit directly from DbContext, but then, users couldn't make their ApplicationDbContext inherit from both IdentityDbContext and OpenIddictDbContext at the same time (and using OpenIddict + ASP.NET Core Identity is definitely a scenario I want to preserve).

The other option is to remove OpenIddictDbContext and dynamically register the OpenIddict entity sets in the application context using IModelCustomizer. This way, people can directly inherit from IdentityDbContext while still be able to include the OpenIddict sets with a single of code:

services.AddOpenIddict<CustomApplicationEntity, CustomAuthorizationEntity, CustomScopeEntity, CustomTokenEntity>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(configuration["Data:DefaultConnection:ConnectionString"])
        .UseOpenIddictModels());

The problem is that my custom "customizer" relies on a DI-injected component to resolve the generic models that are registered when calling AddOpenIddict<...> (CustomApplicationEntity, CustomAuthorizationEntity, CustomScopeEntity and CustomTokenEntity in the snippet).

So currently, I have to repeat the generic arguments so UseOpenIddictModels() can flow them up to the IModelCustomizer.

services.AddOpenIddict<CustomApplicationEntity, CustomAuthorizationEntity, CustomScopeEntity, CustomTokenEntity>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(configuration["Data:DefaultConnection:ConnectionString"])
        .UseOpenIddictModels<CustomApplicationEntity, CustomAuthorizationEntity, CustomScopeEntity, CustomTokenEntity>());
Was this page helpful?
0 / 5 - 0 ratings