Scalable scenario to configuring Entity Framework DbContext(ASP.Net Core 3.x & MSTest) and creating a flexible Startup class

Tarek Najem
6 min readOct 1, 2020

--

When we are configuring the DbContext in an ASP.NET Core web application, we typically use AddDbContext extension method as follows:

If we take a closer look at the parameter of the AddDbContext extension method, we’ll find that is an action, and through an Action, we can encapsulate a method , delegate, in-line delegate, lambda or etc.
In this case, the Action must construct the DbContext options.
What interests us most is configuring the DbContext according to the our configuration ‘{environment}settings.json’.
How do we implement this scenario and why?

The answer to the question why would we do this:
We want to dramatically simplify and improve the experience for configuring DbContext and make them truly composable, where we can try to create a flexible scenario that automatically configures DbContext so that it can encapsulate a complete feature and provide an instant utility without having to go through various steps on how to manually configure it in different places in the Startup configuration class.

From this point on, we will try to figure out how to implement this scenario and try to simplify all the concepts that we will encounter.

Startup Class:

We will try to start with the main class through which we can start configuring the program that we are working on.
It is definitely a Startup class, and I am aware in advance that everyone who writes ASP.Net Core code is familiar with it, and knows what they are doing in detail, but let’s get over it in a simple and quick way to talk.

Each ASP.NET Core application must have its own configuration code to configure the app’s services and to create the app’s request processing pipeline.
We can do this in two different ways:

1- Configure services without Startup:

by call ConfigureServices and Configure convenience methods on the host builder.

2- Configure services with Startup:

The Startup class name is a convention by ASP.NET Core, we can give any name to the Startup class.
Optionally the Startup class has two methods, the ConfigureServices method that tells ASP.NET Core which features are available and the Configure method that tells it how to use it.

When we start the ASP.NET Core application, the ASP.NET Core creates a new instance of the Startup class and calls the ConfigureServices method to create its services. Then it calls the Configure method that sets up the request pipeline to handle incoming HTTP requests.

The Startup class is typically specified by calling the WebHostBuilderExtensions.UseStartup<TStartup> method on the host builder:

Thus, the Startup class in our program will initially look like this:

But such a DbContext will be found in small programs and sometimes in medium programs, but in large programs it will be difficult to manage the program with one DbContext.
We will see that we have to split a single DbContext into multiple DbContext serving the same specific context, and so we will have a group of DbContext that we always want to configure in all development environments, and we have to take that into account sometimes we also change the entity framework provider.
At first you will think that the matter is simple and what is to change the ConnectionString and everything will be fine, this is what we will also do, but in a different way, which we will see later, but at this moment don’t forget what we said earlier that sometimes we change the data Entity Framework Provider.
Let us give an example to illustrate the problem in a test environment. Usually we need to change the Entity Framework provider to an InMemory provider or a Sqlite provider and in the Sqlite provider there are two possible scenarios either in InMemory or in System File.
However, these providers are not exclusive but are more commonly used in with SQL.
Now let’s get out of this context and talk about the most important pattern to designing such large DbContext, which is Bounded Context.

Domain-Driven DesignBounded Context

According to Martin Fowler:

Bounded Context is a central pattern in Domain-Driven Design. It is the focus of DDD’s strategic design section which is all about dealing with large models and teams. DDD deals with large models by dividing them into different Bounded Contexts and being explicit about their interrelationships.

According to Julie Lerman:

When you’re working with a large model and a large application, there are numerous benefits to designing smaller, more-compact models that are targeted to specific application tasks, rather than having a single model for the entire solution. In this column, I’ll introduce you to a concept from domain-driven design (DDD) — Bounded Context — and show you how to apply it to build a targeted model with EF, focusing on doing this with the greater flexibility of the EF Code First feature. If you’re new to DDD, this a great approach to learn even if you aren’t committing fully to DDD. And if you’re already using DDD, you’ll benefit by seeing how you can use EF while following DDD practices.

Accordingly to this pattern we have to split a single DbContext into multiple DbContexts, our new Startup class will become:

We will notice from the above that any small change can lead to a major modification in our code.
So now we’ll use some design patterns and OOP principles and create some classes that will help us facilitate this process.

To make the our application settings more organized, and to provide strongly typed access to groups of related settings, we will use the Options pattern in ASP.NET Core.
We will create two new classes to configure the connection string settings and the entity framework provider settings:

To use the current structure. We have to make a little change to our appsettings.json and appsettings.Development.json. This is shown below.

When we need to access the strongly typed settings we just need to inject an instance of an IOptions<> class into the constructor of our consuming class, and let dependency injection handle the rest:

Because we need to provide a high level of flexibility in our code when we configure our DbContext and we need to separate the object’s construction from the objects themselves, so we go to using Factory Pattern.

This factory is responsible for creating the classes that will configure DbContext by calling the GetConfigurer method, and we will get an IDbContextConfigurer instance that contains the Configure method to initialize the DbContext.

To make Configure method more flexible, we have followed the Single Responsibility Principle (SRP). So we’ve created some new classes.

The main task of this simple design is to read the configuration from appsetting.json or the current environment setting file, by Options Pattern that we talked about earlier and convert it into extensions that we can apply to the data provider, so if we want to add a new provider, we have to add another class for this provider, but don’t forget to add a new implementation to the IDbContextConfigurer interface or inherit from the DbContextConfigurer base class, For example: MySqlProviderConnectionOptions

After completing this scenario we have to inject all the classes in Dependency Injection in the Startup class. You will notice here that all the new methods that we have added to Startup class are virtual methods. This is to make this class overridable in the integration test app of MSTest unit testing, which we will add later.

In the end, we will add the DbContexts that we used in our discussion, which are the simplest form of identification for this type of classes.

Here we will add a controller so that we do a simple test of this scenario.

After testing the program with Postman, we will get this result:

In this post, we will not talk about the unit test or integration test because it is outside the scope of this topic, but we will show the necessary classes to run this test.

integration-settings.json

After inheriting from the Startup class that we created earlier and override necessary methods , IntegrationStartup will become:

IntegrationStartup.cs

The necessary classes to run this test.

Helper.cs

Created a IntegrationWebApplicationFactory independently of the test classes by inheriting from WebApplicationFactory.

IntegrationWebApplicationFactory.cs

A class to test our controller class to make sure everything works well.

DbContextsControllerTest.cs

Conclusion

In this post, I showed a new way to configure our application with the startup class using a flexible way to implement this configuration.

I hope you have liked the article, Please share your opinions in the comments section below.
You can find the source code for this demo on GitHub.

--

--