In EntityFramework Core, we can use to configure the mapping model attributes or Fluent API. One day, I met a new demand, the system will generate a large amount of data every day, create a new table to store data every day. For example, a database as follows:
All tables have the same structure. So, how to change the map in order to avoid creating multiple models do?
In this article, I'll show you how to change the mapping to handle this situation. You can also use this method to extend more usage.
Creating .NET Core 3.1 project
Now, we can use the .NET Core 3.1, it is a LTS version of .NET Core in the future you can easily upgrade it to .NET 5.
Assuming you have installed the latest .NET Core SDK on your computer. If not, you can download from https://dotnet.microsoft.com/download. You can then use dotnet CLI
to create a project. For this example, I'll use .NET Core 3.1.
Let's create a file called DynamicModelDemo
new .NET Core Console project:
dotnet new console --name DynamicModelDemo
Then create a new solution with the following command:
dotnet new sln --name DynamicModelDemo
Next, use the following command to add the item you just created to the solution:
dotnet sln add "DynamicModelDemo/DynamicModelDemo.csproj"
Then you can open the solution with Visual Studio.
Create a model
The model is very simple. Add a project named in the ConfigurableEntity.cs
new file:
using System;
namespaceDynamicModelDemo
{
publicclassConfigurableEntity
{
publicint Id { get; set; }
publicstring Title { get; set; }
publicstring Content { get; set; }
public DateTime CreateDateTime { get; set; }
}
}
We will use the CreateDateTime
property to determine which model should be mapped to tables.
Add EntityFramework Core
Navigate to the project directory and add the required EF.Core packages using the following command:
dotnet add package Microsoft.EntityFrameworkCore.SqlSever
dotnet add package Microsoft.EntityFrameworkCore.Design
If you have not installed ef tool, run the following command to install:
dotnet tool install --global dotnet-ef
This allows you to create or migrate applications to update the database by using dotnet ef migration tool.
Creating DbContext
Add to a project called DynamicContext.cs
the new class file. Shown as follows:
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using System;
namespaceDynamicModelDemo
{
publicclassDynamicContext : DbContext
{
public DbSet<ConfigurableEntity> Entities { get; set; }
#region OnConfiguring
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=DynamicContext;Trusted_Connection=True;");
#endregion
#region OnModelCreating
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ConfigurableEntity>(b =>
{
b.HasKey(p => p.Id);
});
}
#endregion
}
}
At present, this is only the basic configuration of EF.Core. It uses the default mapping, which means that the model will be mapped to the name Entities
of the table. So, if we want based on their CreateDateTime
map the model to a different table properties, how to do it?
You may know that we can use ToTable()
to change the name of the table method, but how to OnModelCreating
change the table names of all model approach it? When the EF model, only once OnModelCreating. So in this way it can not be achieved.
In this case, we need IModelCacheKeyFactory
to change the default mapping, through which we can customize the model caching mechanism so that EF can create different models according to their attributes.
IModelCacheKeyFactory
What is?
This is Microsoft's official documentation explains:
EF uses
IModelCacheKeyFactory
to generate cache keys for models.
By default, EF is assumed that for any given type of context model is the same. But for our program, the model will be different as it is mapped to a different table. Therefore, we need to replace our implementation IModelCacheKeyFactory
services, the implementation would be more cache key to map the model to the correct table.
Note that this procedure typically provides the interface and other extended by the database, the application code is generally not used. But for our scenario, this is a viable approach.
achieveIModelCacheKeyFactory
We need CreateDateTime
to distinguish between tables. In DynamicContext
adding a property class:
public DateTime CreateDateTime { get; set; }
Add a project named in the DynamicModelCacheKeyFactory.cs
new class file. Code as follows:
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespaceDynamicModelDemo
{
publicclassDynamicModelCacheKeyFactory : IModelCacheKeyFactory
{
public object Create(DbContext context)
=> context is DynamicContext dynamicContext
? (context.GetType(), dynamicContext.CreateDateTime)
: (object)context.GetType();
}
}
While creating a model cache key, this implementation will be considered CreateDateTime
property.
applicationIModelCacheKeyFactory
Next, we can register a new context IModelCacheKeyFactory
:
#region OnConfiguring
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=DynamicContext;Trusted_Connection=True;")
.ReplaceService<IModelCacheKeyFactory, DynamicModelCacheKeyFactory>();
#endregion
So that we can in OnModelCreating
each mapping table name of the method:
#region OnModelCreating
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ConfigurableEntity>(b =>
{
b.ToTable(CreateDateTime.ToString("yyyyMMdd"));
b.HasKey(p => p.Id);
});
}
#endregion
CreateDateTime
From DynamicContext
the property.
We can create DynamicContext
a specified time CreateDateTime
attributes:
var context = new DynamicContext { CreateDateTime = datetime };
If datetime
is 2020/03/27
, then context
the model will be mapped to the name 20200327
of the table.
Create a database
Before verification code, we need to create the database. However, EF migration is not the best solution for this situation, because as time goes by, the system will generate more tables. We just use it to create some sample to verify the mapping table. Indeed, the system should have another dynamically generated tables daily basis.
Run the following command to create the first migration:
dotnet ef migrations add InitialCreate
You will see Migrations
generates two files in the folder. Open the xxx_InitialCreate.cs
file, and update Up method with the following code:
protected override void Up(MigrationBuilder migrationBuilder)
{
for (int i = 0; i < 30; i++)
{
var index = i;
migrationBuilder.CreateTable(
name: DateTime.Now.AddDays(-index).ToString("yyyyMMdd"),
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Title = table.Column<string>(nullable: true),
Content = table.Column<string>(nullable: true),
CreateDateTime = table.Column<DateTime>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey($"PK_{DateTime.Now.AddDays(-index):yyyyMMdd}", x => x.Id);
});
}
}
Changes is to ensure that the database can have enough tables for testing. Please note that we should not use this approach in a production environment .
Next, we can use this command to create and update the database:
dotnet ef database update
You will see that it generates the last 30 days of the table in the database.
Verify Mapping
Now verify the new map. By updating the code Program.cs
in Main
the method:
static void Main(string[] args)
{
DateTime datetime1 = DateTime.Now;
using (var context = new DynamicContext { CreateDateTime = datetime1 })
{
context.Entities.Add(new ConfigurableEntity { Title = "Great News One", Content = $"Hello World! I am the news of {datetime1}", CreateDateTime = datetime1 });
context.SaveChanges();
}
DateTime datetime2 = DateTime.Now.AddDays(-1);
using (var context = new DynamicContext { CreateDateTime = datetime2 })
{
context.Entities.Add(new ConfigurableEntity { Title = "Great News Two", Content = $"Hello World! I am the news of {datetime2}", CreateDateTime = datetime2 });
context.SaveChanges();
}
using (var context = new DynamicContext { CreateDateTime = datetime1 })
{
var entity = context.Entities.Single();
// Writes news of today
Console.WriteLine($"{entity.Title} {entity.Content} {entity.CreateDateTime}");
}
using (var context = new DynamicContext { CreateDateTime = datetime2 })
{
var entity = context.Entities.Single();
// Writes news of yesterday
Console.WriteLine($"{entity.Title} {entity.Content} {entity.CreateDateTime}");
}
}
You will see the following output:
Now, we can transfer CreateDateTime
property to use the same DbContext
to represent the different models.