使用EF.Core將同一模型映射到多個表

在 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 };

如果datetime2020/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來表示不同的模型了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章