I'm writing a library that's going to provide new APIs around the DbContext
class in Entity Framework Core 5 . I already have a version of these new APIs working, but it requires manual intervention in the final DbContext
implementation, e.g.:
// Code in the library.
public static class AwesomeExtensions
{
public static ModelBuilder AddAwesomeExtensionsSupportingEntities(this ModelBuilder modelBuilder)
{
// Set up custom entities I need to make this work.
return modelBuilder;
}
}
// Code somewhere else.
public class MyBusinessDbContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// I would like to avoid doing this here.
modelBuilder.AddAwesomeExtensionsSupportingEntities();
// Business as usual (database entities).
}
}
After an extended search I haven't found an extension point in the EF Core API that allows me to do this in a non-intrusive way.
This is what I have found so far:
- CustomDbContext class: I could inherit from
DbContext
and override theOnModelCreating
method, but this is not superior to what I'm doing right now. DbContextOptionsBuilder.UseModel
: I thought this may be something I could use but adds too much complexity. By using this API theOnModelCreating
method won't be called by the framework.IEntityTypeConfiguration<TEntity>
: I was rooting for this one but it also requires you to have access to theModelBuilder
instance, then you can use theModelBuilder.ApplyConfigurationsFromAssembly
method.
Ideally, I would like to do this via the DbContextOptionsBuilder
object provided when registering the DbContext
dependency, e.g.:
// Code in some application.
public void AddServices(IServiceCollection services)
{
services.AddDbContext<MyBusinessDbContext>(options =>
{
// The ideal solution.
options.UseAwesomeExtensions();
});
}
If I could only intercept the instance of the ModelBuilder
just before it is provided to the OnModelCreating
method, in a way that does not requires the modification of the DbContext
implementation, that would help me.
Any ideas are welcome.
Thank you.
CodePudding user response:
EF Core service responsible for calling OnModelCreating
is called IModelCustomizer
with single method
public void Customize(ModelBuilder modelBuilder, DbContext context);
Intercepting that method allows you to do what you need. The only problem is that EF Core does not provide an easy way to override existing implementation. The only available method is ReplaceService
which is all or nothing, with the obvious drawback that if you want to just perform pre/post processing of the base implementation, you need to know which is the class you are replacing. And of course some other extension can replace your implementation as well (last wins).
Implementing that correctly requires a bunch of boilerplate for registering custom IDbContextOptionsExtension
to be able to manipulate directly services collection inside ApplyServices
method. If you are interested, you can find examples in other EF Core extension libraries (for instance, LinqKit).
Assuming no other extension is overriding the service in question, and knowing that the default EF Core implementation currently is provided by the ModelCustomizer
class in general or RelationalModelCustomizer
class for relational database (but currently doesn't add anything to the base implementation which simply calls OnModelCreating
), the simplified implementation is a matter of inheriting one of these and replacing the service with your implementation. e.g. something like
namespace Microsoft.EntityFrameworkCore
{
using Infrastructure;
public static class AwesomeDbContextOptionsExtensions
{
public static DbContextOptionsBuilder UseAwesomeExtensions(
this DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.ReplaceService<IModelCustomizer, AwesomeModelCustomizer>();
}
}
namespace Microsoft.EntityFrameworkCore.Infrastructure
{
public class AwesomeModelCustomizer : RelationalModelCustomizer
{
public AwesomeModelCustomizer(ModelCustomizerDependencies dependencies)
: base(dependencies) { }
public override void Customize(ModelBuilder modelBuilder, DbContext context)
{
// Do something before context.OnModelCreating(modelBuilder)...
base.Customize(modelBuilder, context);
// Do something after context.OnModelCreating(modelBuilder)...
}
}
}