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:
and factories to which the contexts included in our application will be injected:
Contexts and finished factories, we miss the last part - an object that returns specific contexts:
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.
As a result of the above activities, Entity Framework will generate the following structure in our layer of the model:
We only have to upload the migration to our databases:
The DataContexts class can be injected anywhere, depending on the architecture adopted:
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 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 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.