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. 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 into which the contexts included in our application will be injected:
Source code 3. The IDbContextFactory interface 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.
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:
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:
OutputDir Migrations / DomainDbContextMIgrations
OutputDir Migrations / ActionLogDbContextMigrations
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 is used when building a web host.
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 contains the application model after running the Add-migration commands for two different contexts.
We only have to upload the migration to our databases:
The DataContexts class can be injected anywhere, depending on the architecture adopted:
Source code 8. The DbContexts object is injected by the constructor into the controller.
In the HomeController class, we use data from two contexts – the GetActions method returns all UserActionLog objects stored in the UserActionLogs table from a database connected with a connection string named ActionLogDataDbConnection (see Source Code 7.), while the GetThreads method returns all Thread objects stored in the table Threads from the database attached to the connection string called DomainDataDbConnection.
We have created a boilerplate for a web application that uses many data sources. We can 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 the interface for basic data support to subsequent 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.
About the Authors
Do you want to know how to
estimate the budget needed
for your project?