Home > Enterprise >  How can I intercept the ModelBuilder instance in a DbContext?
How can I intercept the ModelBuilder instance in a DbContext?

Time:10-18

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 the OnModelCreating 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 the OnModelCreating 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 the ModelBuilder instance, then you can use the ModelBuilder.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)...
        }
    }
}
  • Related