How to Dynamically Register Entities in DbContext by Extending ModelBuilder?

How can we make it easier for ourselves by implementing “dynamically register entities” functionality in our applications? One thing I often see, when helping others extend solutions or when I am asked to help optimize a project is that each and every entity in the solution is registered manually. It can turn your DbContext file into a quite big file if you got a lot of entities. To void this and help ourselves save some time (and lines), we can make an extension for ModelBuilder to dynamically register entities in DbContext.

By doing this we are sure that all entities in the future will get registered automatically when doing migrations and database updates. By the end of this guide, you will be able to extend ModelBuilder to automatically register your entities in the database using ModelBuilder.

What is ModelBuilder?

ModelBuilder is a class that lives in Microsoft.EntityFrameworkCore. It provides a simple API surface for configuring an IMutableModel that will help define the shape of your entities in an application. This includes relationships between the entities and how they are mapped to the database.

Microsoft.EntityFrameworkCore Namespace
Explore all classes and interfaces of the Microsoft.EntityFrameworkCore namespace.

In C# we use ModelBuilder to construct a model for a context by overriding OnModelCreating (this is not a necessary thing in all projects) on your derived context.

What is a C# Entity?

When talking about C# an entity is a collection of fields and associated database operations. Entities in an application are fundamental to the application as they define data models. When they are added to DbContext they will be added as tables when you do a migration and update the database.

If you are ready, then let’s move on and write some code to make our lives easier when adding new models/entities to an application.

Dynamically Register Entities – But how?

In a traditional small project where we know that we won’t get a lot of entities, it makes fine sense just to register the entities manually in our DbContext class. We would just create a new DbSet for each of the models in our application, it would like something like what I have added below:

public class DatabaseContext : DbContext
{
    public DatabaseContext(DbContextOptions options) : base(options) 
    {
        <...>
    }

    public DbSet<Book> Books { get; set; }
    public DbSet<Author> Authors { get; set; }
    <...>
}

Create an abstract class as the base model

This model is the one we instruct the ModelBuilder extension to include when it dynamically registers entities during the startup of the application.

Let’s create a new abstract class named BaseModel. I always put in the common properties for each model to avoid duplicate properties in my other models. TheBaseModel I have created for this guide looks like the following below (it only contains an id), you can add or remove properties to this base class if you want to.

public abstract class BaseModel
{
   public GUID Id { get; set; }
}

When creating new models in your application, you can now simply inherit this abstract class named BaseModel. Let’s take a look at the Author model and see what it looks like:

public class Author : BaseModel
{
   public string? FirstName { get; set; }
   public string? LastName { get; set; }
   public string? Genre { get; set; }
   public GUID BookId { get; set; }
   public List<Book> Books { get; set; }
}

This will automatically make the Author have an ID of type GUID and it will automatically/dynamically be registered as an entity in our ModelBuilder in a moment.

Create ModelBuilder extension to dynamically register entities

To dynamically register entities, we have to make an extension to ModelBuilder and use Reflection to find all the models using BaseModel and add them to DbContext as an entity at runtime. Type represents type declarations (the reflection).

Tech with Christian - Private Site Access
public static class ModelBuilderExtensions {
  public static void RegisterAllEntities<BaseModel>(this ModelBuilder modelBuilder, params Assembly[] assemblies) {
    IEnumerable<Type> types = assemblies.SelectMany(a => a.GetExportedTypes()).Where(c => c.IsClass && !c.IsAbstract && c.IsPublic &&
      typeof (BaseModel).IsAssignableFrom(c));
    foreach(Type type in types)
    modelBuilder.Entity(type);
  }
}

Okay, so what is happening in the code above?

  • We create an extension of ModelBuilder named RegisterAllEntities. It takes in the ModelBuilder and assemblies as parameters.
  • We then create an IEnumerable with Type and insert all models inheriting the abstract class named BaseModel.
  • We then add each model found in the assemblies as an entity.

Be careful how you use BaseModel now as it will include the model as an entity when doing migrations etc… some models are not meant for inclusion as an entity/table in the database. They are only in the application for moving data around.

Register all entities in OnModelCreating

Finally, we can now add our ModelBuilder extension to OnModelCreating in DbContext to dynamically register entities in the project at runtime. This is done in three lines as shown below:

protected override void OnModelCreating(ModelBuilder modelBuilder) {
  base.OnModelCreating(modelBuilder);
  var entitiesAssembly = typeof (BaseModel).Assembly;
  modelBuilder.RegisterAllEntities<BaseModel>(entitiesAssembly);
}

When you add a new migration now, the models inheriting the abstract class BaseModel will now be added as entities and also registered as entities during runtime.

Summary

This was a short guide (the first one in a long time). You are now able to dynamically register entities in your C# application in a nice, clean and easy way without having your DbContext file getting huge. At the same time, you will be able to avoid duplicating properties/fields in your models.

I hope you learned something new or had your issue solved by reading this article. If you got any questions, please let me know in the comments below. I will answer them as fast as possible. Until next time – Happy coding!

You've successfully subscribed to Tech with Christian
Great! Next, complete checkout to get full access to all premium content.
Error! Could not sign up. invalid link.
Welcome back! You've successfully signed in.
Error! Could not sign in. Please try again.
Success! Your account is fully activated, you now have access to all content.
Error! Stripe checkout failed.
Success! Your billing info is updated.
Error! Billing info update failed.