-
Notifications
You must be signed in to change notification settings - Fork 3.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reduce magic in the interaction of EF Core and DI #4668
Comments
👍 The suggested changes are really good! |
@Muchiachio Yes, if we do this it should fix that issue. This was one of the motivating factors behind the discussion that led to this item being created. However, please leave that issue open for now because we aren't fully decided on the complete direction to take. |
I have done some investigation and prototyping and have the following proposal: DbContext would have four constructors: protected DbContext(
[CanBeNull] ILoggerFactory loggerFactory = null,
[CanBeNull] IMemoryCache memoryCache = null) This constructor:
protected DbContext(
[NotNull] IServiceProvider internalServicesProvider
[CanBeNull] ILoggerFactory loggerFactory = null,
[CanBeNull] IMemoryCache memoryCache = null) This constructor:
public DbContext(
[NotNull] DbContextOptions options,
[CanBeNull] ILoggerFactory loggerFactory = null,
[CanBeNull] IMemoryCache memoryCache = null) This constructor:
public DbContext(
[NotNull] IServiceProvider internalServicesProvider)
[NotNull] DbContextOptions options,
[CanBeNull] ILoggerFactory loggerFactory = null,
[CanBeNull] IMemoryCache memoryCache = null) This constructor:
AddDbContext would now hang directly off ServcieCollection: Before: services
.AddEntityFramework()
.AddSqlServer()
.AddDbContext<BloggingContext>(options => options.UseSqlServer(connection)); Now: services
.AddDbContext<BloggingContext>(options => options.UseSqlServer(connection)); AddDbContext will now not add any internal EF services to the application D.I. container. It will also not do any magic to get that service provider into the context. Instead it will just:
We should probably move the AddEntityFramework and AddMyProvider methods to a different namespace since most of the time they will not be used. Notes:
|
👍 |
We also need to be cognizant of how our design-time will work. Currently, serviceCollection.AddSingleton<DbContextOptions, DbContextOptions<TContext>>(); Then at design-time, we get the list of var contextTypes = from options in serviceProvider.GetServices<DbContextOptions>()
let genericOptions = options.GetType() // DbContextOptions<TContext>
select genericOptions.GenericTypeArguments[0]; // TContext |
See issue #4668 for full description of changes. This change removes EntityFrameworkServicesBuilder since it is no longer needed for AddDbContext and moves all EF methods that add services to IServiceCollection. Providers will need to react to this by moving AddMyProvider methods to be extension methods of IServiceCollection.
See issue #4668. This is an update following a discussion yesterday that moves all configuration onto DbContextOptions and makes this the only object passed to the DbContext constructor. This allows the builder pattern that already exists for DbContextOptions to be used for external services and for setting the internal service provider. AddDbContext does this automatically for external services, but a DbContextOptions object can also be easily built and either configured in DI or passed directly to the context with new. In addition, the UseMyProvider methods have been updated to use nested closure for provider-specific configuration. This makes for a very nice chaining story. There is a new overload of AddDbContext that makes the application service provider available to teh configuration delegate. This is rarely needed, but one use is to set the application service provider to be used as the internal EF service provider if this is needed/desired. Still to do: - Look at negative cases when services are not setup and create better exception messages. - Rename AddSqlServer, etc to AddEntityFrameworkSqlServer, etc. - Extract diagnostics as one of the external services on DbContextOptions.
See issue #4668 for full description of changes. This change removes EntityFrameworkServicesBuilder since it is no longer needed for AddDbContext and moves all EF methods that add services to IServiceCollection. Providers will need to react to this by moving AddMyProvider methods to be extension methods of IServiceCollection.
See issue #4668. This is an update following a discussion yesterday that moves all configuration onto DbContextOptions and makes this the only object passed to the DbContext constructor. This allows the builder pattern that already exists for DbContextOptions to be used for external services and for setting the internal service provider. AddDbContext does this automatically for external services, but a DbContextOptions object can also be easily built and either configured in DI or passed directly to the context with new. In addition, the UseMyProvider methods have been updated to use nested closure for provider-specific configuration. This makes for a very nice chaining story. There is a new overload of AddDbContext that makes the application service provider available to teh configuration delegate. This is rarely needed, but one use is to set the application service provider to be used as the internal EF service provider if this is needed/desired. Still to do: - Look at negative cases when services are not setup and create better exception messages. - Rename AddSqlServer, etc to AddEntityFrameworkSqlServer, etc. - Extract diagnostics as one of the external services on DbContextOptions.
TL;DR
This is a proposal to remove the magic that allows derived
DbContext
types with default or parameter-less constructors to be used with DI. The general idea is to embrace the constructor injection pattern when using DI so that it becomes very clear what services are being injected and also so that the non-DI usages can be clearly differentiated.Details
Currently we allow derived
DbContexts
that are supposed to be resolved from DI (e.g. the default application context in an ASP.NET Core application) to have parameter-less constructors. We actually went out of our way to enable this through some complex logic inDbContextActivator
and theAddDbContext()
method.While this allows removing a lot of boilerplate code from derived
DbContext
constructors, it also has some undesired consequences:OnConfiguring()
method) and then creating the context instance without DI (i.e. using thenew
keyword) or with DI, which in the worst cases will fail, and in some of the better cases may cause performance issues.DbContext
depends on (and everything downstream needs to use service locator pattern), let alone the fact that it is sharing the application'sIServiceProvider
to resolve its own internal services.We have started brainstorming and we are going to continue discussing some ideas on how to improve this. Here are some initial thoughts around the alternative of removing the magic. The main disadvantage of this approach is that derived DbContext will need to define a constructor to be used with DI (until now we were getting away with default constructors). It seems that the level of control and clarity about what is going on you gain might be worth the pain though.
The changes would involve all of the following:
DbContextActivator
and the delegate registration that uses it inAddDbContext()
.DbContext
constructors to include arguments for all the shared application services EF would use within aDbContext
, including logging, diagnostics and memory cache.DbContextOptions
argument to be able to resolve context options that are configured externally.IServiceProvider
argument for cases in which a specific service provider (could be the application’s one or a different one) is configured externally.AddDbContext()
method but do less magic in it, e.g. it should just be a shortcut for setting up the context as a scoped service and to setup its options. Also consider extendingAddDbContext()
to make it easier for customers to bring their own derivedDbContextOptions
type and get those injected instead of the defaultDbContextOptions
orDbContextOptions<TContext>
(perhaps an extra overload with a second generic argument could be enough).Some extra potential benefits of this change would be:
DbContext
constructor, including any services necessary to flow configuration details into the instances.The text was updated successfully, but these errors were encountered: