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.
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).
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 namedBaseModel
. - 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!