在 EntityFramework Core 中,我們可以使用屬性或Fluent API來配置模型映射。有一天,我遇到了一個新的需求,有一個系統每天會生成大量數據,每天生成一個新的表存儲數據。例如,數據庫如下所示:
所有表都具有相同的結構。那麼,如何更改映射以避免創建多個模型呢?
在本文中,我將向您展示如何更改映射以處理這種情況。您也可以使用此方法擴展出更多的用法。
創建 .NET Core 3.1 項目
現在,我們可以使用.NET Core 3.1,它是.NET Core的LTS版本,將來可以輕鬆將其升級到.NET 5。
假設您已經在計算機上安裝了最新的.NET Core SDK。如果沒有,則可以從https://dotnet.microsoft.com/download下載。然後,您可以使用dotnet CLI
創建項目。對於此示例,我將使用.NET Core 3.1。
讓我們創建一個名爲DynamicModelDemo
的新.NET Core Console項目:
dotnet new console --name DynamicModelDemo
然後用以下命令創建一個新的解決方案:
dotnet new sln --name DynamicModelDemo
接下來使用以下命令把剛纔創建的項目添加到解決方案:
dotnet sln add "DynamicModelDemo/DynamicModelDemo.csproj"
接下來可以用Visual Studio打開解決方案了。
創建模型
該模型非常簡單。在項目中添加一個名爲ConfigurableEntity.cs
的新文件:
using System;
namespaceDynamicModelDemo
{
publicclassConfigurableEntity
{
publicint Id { get; set; }
publicstring Title { get; set; }
publicstring Content { get; set; }
public DateTime CreateDateTime { get; set; }
}
}
我們將使用CreateDateTime
屬性來確定模型應該映射到哪個表。
添加 EntityFramework Core
導航到項目目錄並使用以下命令添加所需的EF.Core packages:
dotnet add package Microsoft.EntityFrameworkCore.SqlSever
dotnet add package Microsoft.EntityFrameworkCore.Design
如果您還沒有安裝 ef tool,請運行以下命令來安裝:
dotnet tool install --global dotnet-ef
這樣您就可以使用 dotnet ef 工具創建遷移或通過應用遷移來更新數據庫。
創建 DbContext
向項目添加一個名爲DynamicContext.cs
的新類文件。內容如下所示:
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
}
}
目前,這只是EF.Core的基本配置。它使用默認映射,這意味着模型將映射到名爲Entities
的表。那麼,如果我們想基於其CreateDateTime
屬性將模型映射到不同的表,該怎麼辦呢?
您可能知道我們可以使用ToTable()
方法來更改表名,但是如何在OnModelCreating
方法中更改所有模型的表名呢?EF建立模型時,只會執行一次OnModelCreating。所以這種方式是無法實現的。
對於這種情況,我們需要使用IModelCacheKeyFactory
來更改默認映射,通過這個接口我們可以定製模型緩存機制,以便EF能夠根據其屬性創建不同的模型。
IModelCacheKeyFactory
是什麼?
這是微軟官方的文檔解釋:
EF uses
IModelCacheKeyFactory
to generate cache keys for models.
默認情況下,EF假定對於任何給定的上下文類型,模型都是相同的。但是對於我們的方案,模型將有所不同,因爲它映射到了不同的表。因此,我們需要用我們的實現替換IModelCacheKeyFactory
服務,該實現會比較緩存鍵以將模型映射到正確的表。
請注意,該接口通常由數據庫提供程序和其他擴展使用,一般不在應用程序代碼中使用。但是對於我們的場景來說,這是一種可行的方法。
實現IModelCacheKeyFactory
我們需要使用CreateDateTime
來區分表。在DynamicContext
類中添加一個屬性:
public DateTime CreateDateTime { get; set; }
在項目中添加一個名爲DynamicModelCacheKeyFactory.cs
的新類文件。代碼如下所示:
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();
}
}
在生成模型緩存鍵時,此實現將考慮CreateDateTime
屬性。
應用IModelCacheKeyFactory
接下來,我們可以在上下文中註冊新的IModelCacheKeyFactory
:
#region OnConfiguring
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=DynamicContext;Trusted_Connection=True;")
.ReplaceService<IModelCacheKeyFactory, DynamicModelCacheKeyFactory>();
#endregion
這樣我們就可以在OnModelCreating
方法中分別映射表名了:
#region OnModelCreating
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ConfigurableEntity>(b =>
{
b.ToTable(CreateDateTime.ToString("yyyyMMdd"));
b.HasKey(p => p.Id);
});
}
#endregion
CreateDateTime
來自DynamicContext
的屬性。
我們可以在創建DynamicContext
時指定CreateDateTime
屬性:
var context = new DynamicContext { CreateDateTime = datetime };
如果datetime
爲2020/03/27
,則context
的模型將映射到名爲20200327
的表。
創建數據庫
在驗證代碼之前,我們需要首先創建數據庫。但是,EF遷移並不是這種情況的最佳解決方案,因爲隨着時間的流逝,系統將生成更多表。我們只是使用它來創建一些示例表來驗證映射。實際上,系統應該具有另一種每天動態生成表的方式。
運行以下命令以創建第一個遷移:
dotnet ef migrations add InitialCreate
您會看到在Migrations
文件夾中生成了兩個文件。打開xxx_InitialCreate.cs
文件,並通過以下代碼更新Up方法:
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);
});
}
}
所做的更改是爲了確保數據庫中可以有足夠的表進行測試。請注意,我們不應該在生產環境中使用這種方式。
接下來,我們可以使用此命令來創建和更新數據庫:
dotnet ef database update
您會看到它在數據庫中生成了最近30天的表。
驗證映射
現在該驗證新映射了。通過以下代碼更新Program.cs
中的Main
方法:
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}");
}
}
您將會看到如下輸出:
現在,我們可以通過傳遞CreateDateTime
屬性來使用相同的DbContext
來表示不同的模型了。