Backend

ASP.NET Core - many contexts, many databases

Implementation of the first, working version of the system usually does not involve any major problems - the programmers receive previously prepared documentation, adapt their development environments and start working. After x-hours of development, y-hours of testing and z-hours of conversations with clients, the implementation department receives a green light - we start to implement the service on the production server. Everything went according to plan, the application works flawlessly, and we count the next unique users using the service. Unfortunately, one element was not foreseen - the exponentially growing size of the database.

Our application is starting to slow down significantly, and managing our data source is becoming increasingly difficult. The database, which is the heart of the application, begins to contain a lot of information, both related to the business domain and the operation of the system itself (so-called logs). This begs the question:

Can we divide the database into smaller collections? Are we able to prepare an application model in which we can handle many databases from the code level? Is the implementation of such a solution difficult?

I encourage you to read the following study, in which I will try to answer these questions.

The following solution was developed based on ASP .NET Core 2.x, Entity Framework Core (Code First approach) + Identity.

Let our new application be the simplest possible representation of the discussion forum. Users can create multiple threads, but each thread can contain multiple posts. In addition, we want to be able to track user activity - each possible action is to be registered in the database as a separate entry. The easiest solution to this business problem would be to implement one class that inherits from IdentityDbContext and extends it to our additional entities, i.e. threads, posts and objects that store information about user activity. In our solution, we will create two class implementations that allow interaction with two different databases, inheriting successively from the IdentityDbContext and DbContext classes - one for entities directly related to the business domain, the other for logs related to user actions.
 

Implementation of the model layer

The model layer stores descriptions of objects included in the application along with their configurations. In addition, it includes the implementation of database contexts and files generated by the migration engine, part of Microsoft.EntityFrameworkCore. In order for us to be able to use many different contexts, we need to implement a mechanism that will return the corresponding DbContext object. Let us set up our contexts in turn:

Source code 1. Implementation of the DomainDbContext class, inheriting from the IdentityDbContext, containing threads and posts.

 

Source code 2. Implementation of the ActionLogDbContext class, inheriting from DbContext, containing entries related to user activities.

and factories to which the contexts included in our application will be injected:

Source code 3. The IDbContextFactory interface that will implement subsequent factories that return context objects.

Source code 4. Implementation of the ActionLogDbContextFactory class that returns the ActionLogDbContext object. The ActionLogDbContext object will be injected by the default DI mechanism provided by ASP.NET Core.

Source code 5. Implementation of the DomainDbContextFactory class that returns the DomainContext object. The DomainDbContextFactory object will be injected by the default DI mechanism provided by ASP.NET Core.

Contexts and finished factories, we miss the last part - an object that returns specific contexts:

 

Source code 6. Implementation of the DbContexts class, packaging the available factories. The indexer allows you to download any data source.

Migration configuration

Contexts configured, factories implemented, so the question arises - how to manage multiple contexts, within Entity Framework? For one context, starting and generating database migration is limited to calling two commands:

  1. Add-Migration Initial
  2. Update-Database


The case looks very similar in many contexts. Fortunately, Microsoft provides us with optional parameters for the above commands, thanks to which we can easily generate two separate migrations - one for DomainDbContext, the other for ActionLogDbContext:

Add-Migration Initial

  • Context DomainDbContext
  • OutputDir Migrations / DomainDbContextMIgrations
  • Startup MultipleContextsApp.

Add-Migration Initial

  • Context ActionLogDbContext
  • OutputDir Migrations / ActionLogDbContextMigrations
  • Startup MultipleContextsApp.Web

Before running the above commands, configure the ConfigureServices method from the Startup class, which is located in the web project. The -Startup option allows you to specify a project from which, among other things, access data for databases attached to specific contexts will be downloaded. By default, ASP .NET Core applications retrieve the connection string from the appsettings.json file.

Source code 7. The Startup class used when building a webhost.

As a result of the above activities, Entity Framework will generate the following structure in our layer of the model:

Figure 1. The structure of the project containing the application model after running the Add-migration commands for two different contexts.

We only have to upload the migration to our databases:

Update-Database

  • Context DomainDbContext
  • Startup MultipleContextsApp.Web

Update-Database

  • Context ActionLogDbContext
  • Startup MultipleContextsApp.Web

Application

The DataContexts class can be injected anywhere, depending on the architecture adopted:

Source code 8. The DbContexts object injected by the constructor into the controller.

In the HomeController class, we use data from two contexts - the GetActions method returns all UserAction objects stored in the UserActionLogs table from a database connected with a connection string called DomainDataDbConnection (see Source Code 7.), while the GetThreads method returns all Thread objects stored in the table Threads from a database attached to a connection string named ActionLogDataDbConnection.

Summary

We have created a boilerplate for a web application that uses many data sources. We are able to use our DataContexts object, which stores many different contexts, anywhere. In the above article, the DataContexts object was injected directly into the controller, however in large, commercial applications, objects of this type are usually injected into the Data Access Layer, which in turn provides an interface for handling data to the next layers. This allows the entire application to be kept in a modular form that is simple and pleasant to develop and maintain.

 

Do you have any questions? Do not you know if your database in the application can handle more users? Contact us using the contact form below and get a free analysis.

 

CONTACT US

Do you have a different question?

You did’t find the answer to the question bothering you? Write to us! We can help!

Leaware SA, with its registered office at 86/1 Perkuna, in Warsaw, is the controller of your personal data.

Similar posts

Case Study

Zbigniew Waz

ASK US

Let's stay in touch!

If you want to know more about how we can help you or you like to be on a current basis, please leave us your e-mail.

Leaware SA, with its registered office at 86/1 Perkuna, in Warsaw, is the controller of your personal data.