EF core (code first) 通过自定义 Migration History 实现多租户使用同一数据库时更新数据库结构

前言

写这篇文章的原因,其实由于我写EF core 实现多租户的时候,遇到的问题。

具体文章的链接:

Asp.net core下利用EF core实现从数据实现多租户(1)

Asp.net core下利用EF core实现从数据实现多租户(2) : 按表分离   (主要关联文章)

这里我遇到的最主要问题是:由于多租户的表使用的是同一个数据库。由于这个原因,无法通过 Database.EnsureCreated() 自动创建多个结构相同但名字不同的表。

所以我在文中提到,需要自己跑脚本去创建多有的表。

虽然我依然认为在多租户的情况下使用sql管理表是更可靠的方案,但如果可以利用EF core原生提供的Migration机制,在运行时自动创建和更新数据表结构,那更加友好。

实现的思路

其实我们都知道,EF core (code first) 会在数据库中生成唯一一个 __EFMigrationHistory 表,数据库的版本记录在这里。

在我们文章的场景下,由于有多个租户同时使用,同一个表结构(Products)会出现多次,那么意思就是一个 __EFMigrationHistory 无法同时记录多个租户的数据表版本。

好了,既然问题的关键已经知道了,我们可以在这里先把答案揭晓,在下问在详细说明实现方法:

图中可以看到,我们自定义MigrationHistory表,并且在一个数据下,同时出现了store1和store2的 MigrationHistory 表。

实施

项目介绍

这是一个多租户系统,具体来说就是根据不同的租户,创建相同的所有数据表。

项目依赖:

1. .net core app 3.1。在机器上安装好.net core SDK, 版本3.1

2. Mysql. 使用 Pomelo.EntityFrameworkCore.MySql 包

3. EF core,Microsoft.EntityFrameworkCore, 版本3.1.1。这里必须要用3.1的,因为ef core3.0是面向.net standard 2.1.

4. EF core design, Microsoft.EntityFrameworkCore.Design, 版本 3.1.1

5. dotnet-ef tool, 版本 3.1.1

关键的对象:

1. MigrationsAssembly, 利用此类去实现创建对应的Migration单元。

2. Migration files, 这里指的是一批Migration相关的文件,利用执行dotnet-ef 命令生成具体的文件,从而真正地去创建和更新数据库。

实施步骤

1. 运行dotnet-ef命令,生成Migration files

命令:

1 dotnet-ef migrations add init

执行后,会在项目中的Migrations文件夹下生成多个*.cs文件,其实他们也是可执行C#对象

机构如下:

这3个文件中,主要起作用的是*_init.cs这个文件

打开之后我们需要对他进行修改

 1 using Microsoft.EntityFrameworkCore.Metadata;
 2 using Microsoft.EntityFrameworkCore.Migrations;
 3
 4 namespace kiwiho.Course.MultipleTenancy.EFcore.Api.Migrations
 5 {
 6     public partial class init : Migration
 7     {
 8         private readonly string prefix;
 9         public init(string prefix)
10         {
11             if (string.IsNullOrEmpty(prefix))
12             {
13                 throw new System.ArgumentNullException();
14             }
15             this.prefix = prefix;
16         }
17
18         protected override void Up(MigrationBuilder migrationBuilder)
19         {
20             migrationBuilder.CreateTable(
21                 name: prefix + "_Products",
22                 columns: table => new
23                 {
24                     Id = table.Column<int>(nullable: false)
25                         .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
26                     Name = table.Column<string>(maxLength: 50, nullable: false),
27                     Category = table.Column<string>(maxLength: 50, nullable: true),
28                     Price = table.Column<double>(nullable: true)
29                 },
30                 constraints: table =>
31                 {
32                     table.PrimaryKey("PK__Products", x => x.Id);
33                 });
34         }
35
36         protected override void Down(MigrationBuilder migrationBuilder)
37         {
38             migrationBuilder.DropTable(
39                 name: prefix + "_Products");
40         }
41     }
42 }

init migration

这里修改的主要是:

1.1 新增构造函数,并且在里面添加一个 prefix 参数。

1.2 在Up方法中,对table Name进行修改,把prefix变量加在_Product前面(第21行)

1.3 在Down方法中,对table Name进行修改,把prefix变量加在_Product前面 (第39行)

2. 创建 MigrationByTenantAssembly 文件。

由于上一步讲Migration file的构造函数修改了,理论上EF core已经五法通过默认的方式成功执行改Migration file了

 1 using System;
 2 using System.Reflection;
 3 using Microsoft.EntityFrameworkCore;
 4 using Microsoft.EntityFrameworkCore.Diagnostics;
 5 using Microsoft.EntityFrameworkCore.Infrastructure;
 6 using Microsoft.EntityFrameworkCore.Migrations;
 7 using Microsoft.EntityFrameworkCore.Migrations.Internal;
 8
 9 namespace kiwiho.Course.MultipleTenancy.EFcore.Api.Infrastructure
10 {
11     public class MigrationByTenantAssembly : MigrationsAssembly
12     {
13         private readonly DbContext context;
14
15         public MigrationByTenantAssembly(ICurrentDbContext currentContext,
16               IDbContextOptions options, IMigrationsIdGenerator idGenerator,
17               IDiagnosticsLogger<DbLoggerCategory.Migrations> logger)
18           : base(currentContext, options, idGenerator, logger)
19         {
20             context = currentContext.Context;
21         }
22
23         public override Migration CreateMigration(TypeInfo migrationClass,
24               string activeProvider)
25         {
26             if (activeProvider == null)
27                 throw new ArgumentNullException($"{nameof(activeProvider)} argument is null");
28
29             var hasCtorWithSchema = migrationClass
30                     .GetConstructor(new[] { typeof(string) }) != null;
31
32             if (hasCtorWithSchema && context is ITenantDbContext tenantDbContext)
33             {
34                 var instance = (Migration)Activator.CreateInstance(migrationClass.AsType(), tenantDbContext?.TenantInfo?.Name);
35                 instance.ActiveProvider = activeProvider;
36                 return instance;
37             }
38
39             return base.CreateMigration(migrationClass, activeProvider);
40         }
41     }
42 }

MigrationByTenantAssembly

这个类中没有什么特别的,关键在于29~37行。首先需要判断目标 Migration 对象的是否有一个构造函数的参数有且仅有一个string 类型

判断DbContext是否有实现ITenantDbContext接口。

利用 Activator 创建 Migration 实例(把tenant Name传进构造函数)

3. 在 MultipleTenancyExtension 类的AddDatabase方法中,添加自定义MigrationHistory表名

 1 var dbOptionBuilder = options.UseMySql(resolver.GetConnection(), builder =>
 2 {
 3     if (option.Type == ConnectionResolverType.ByTabel)
 4     {
 5         builder.MigrationsHistoryTable(${tenantInfo.Name}__EFMigrationsHistory");
 6     }
 7 });
 8
 9
10 dbOptionBuilder.ReplaceService<Microsoft.EntityFrameworkCore.Migrations.IMigrationsAssembly, MigrationByTenantAssembly>();

最关键的一点是第5行,调用 MigrationsHistoryTable 设置MigrationHistory表名

另外一点是第10行,用 MigrationByTenantAssembly 类替换 EF core 中默认的实现(IMigrationsAssembly接口)

4. 在ProductController的构造函数中,修改成如下

Database.Migrate 的作用主要是在运行时可以执行数据库的创建和更新

1 public ProductController(StoreDbContext storeDbContext)
2 {
3     this.storeDbContext = storeDbContext;
4     this.storeDbContext.Database.Migrate();
5 }

查看效果

调用接口

跟系列文章一样,我们先调用创建product的接口分别在store1和store2中添加记录。

下面是store1 的查询结果

store2的查询结果

查看数据库验证数据

数据库的表结构

store1_Products 表数据

store2_Products 表数据

总结

本文中我们介绍了ef core 的code first模式下是如何更新数据库的,并且通过添加 Migration 对象的构造函数 ,自行添加了必要参数。

通过替换EF core中默认的 IMigrationsAssembly 实现, MigrationByTenantAssembly 中自定对Migration对象实例化。

替换EF core中默认的MigrationHistory最终实现需求。

本文虽然只是一个示例,但是却可以在真实项目中使用相同的手段以实现需求。不过还是那句话,对于多租户情况下,我推荐使用db first模式。

关于代码

代码已经传上github,请查看EF_code_first的分支的代码。

https://github.com/woailibain/EFCore.MultipleTenancyDemo/tree/EF_code_first

原文地址:https://www.cnblogs.com/woailibian/p/12319369.html

时间: 2024-12-20 08:23:54

EF core (code first) 通过自定义 Migration History 实现多租户使用同一数据库时更新数据库结构的相关文章

ef core code first 模式提示&quot;可能会导致循环或多重级联路径&quot;问题

执行命令 dotnet ef mirations add "xxxxxx" dotnet ef database update 报错 将 FOREIGN KEY 约束 'FK_SkuPropertyItem_Sku_SkuId' 引入表 'SkuPropertyItem' 可能会导致循环或多重级联路径.请指定 ON DELETE NO ACTION 或 ON UPDATE NO ACTION,或修改其他 FOREIGN KEY 约束.无法创建约束.请参阅前面的错误消息 修改项目定义的d

EF数据迁移(当模型改变时更新数据库)

https://msdn.microsoft.com/zh-CN/data/jj591621 Enable-Migrations Add-Migration 名称 Update-Database –Verbose

EF Core 2.0中如何手动映射数据库的视图为实体

由于Scaffold-DbContext指令目前还不支持自动映射数据库中的视图为实体,所以当我们想使用EF Core来读取数据库视图数据的时候,我们需要手动去做映射,本文介绍如何在EF Core中手动映射数据库的视图为实体. 假设我们在SQL Server中有如下数据库视图[dbo].[V_Person]: CREATE VIEW [dbo].[V_Person] AS SELECT ID, Code, Name, CreateTime, UpdateTime FROM dbo.Person G

EntityFramework Core Code First 已有数据库

问题场景:我已经有一个数据库,想用 EF core Code First,怎么办? 首先,可以参考微软的API文档:通过现有数据库在 ASP.NET Core 上开始使用 EF Core, 这一步可以将数据库表转换成对象,并生成DBContext.这时候只要在Startup中配置DBContext,就能够访问数据库了. 接下来,我们根据业务需求需要修改表,按照Code First逻辑,只需要修改对应的实体,然后使用 Add-Migration命令就可以了.这时候,我们会发现迁移命令生成的文件竟然

EF Core怎么只Update实体的部分列数据

下面是EF Core中的一个Person实体: public partial class Person { public int Id { get; set; } public string Code { get; set; } public string Name { get; set; } public DateTime? CreateTime { get; set; } public DateTime? UpdateTime { get; set; } } 其中我们通过Fluent API

【从0开始.NET CORE认证】-2 使用.Net Core Identity和EF Core

回顾 朋友们,距离上次从0开始.NET CORE认证-1发布已经过去一周了,上次第一篇文章,其实并没有涉及到Net Core Identity,就是简单的搭了一个项目,让大家对Identity中各种术语有个理解,明白他们出现的位置,已经他们出现能够达到某种功能.以及出现的位置顺序不同,则会出现什么不同的情况. 回顾一下上次写的主要的知识点 Authentication和Authorization 是什么,怎么解释他们 Claim和ClaimType又是什么,能举例子说明吗? ClaimsIden

EF Core 2.0使用MsSql/Mysql实现DB First和Code First

参考地址 Entity Framework官网 ASP.NET Core MVC 和 EF Core - 教程系列 环境 Visual Studio 2017 最新版本的.NET Core 2.0 SDK 最新版本的 Windows PowerShell 开始搭建 1.在 Visual Studio 2017 中创建新项目 "文件">"新建">"项目" 从左侧菜单中选择"已安装">"模板"

EF Core 数据库迁移(Migration)

工具与环境介绍 1.开发环境为vs 2015 2.mysql EF Core支持采用  Pomelo.EntityFrameworkCore.MySql   源代码地址(https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql) 场景 设计两张表 用户表(user)和发帖表(user) 一个用户对应多个用户 Coding Begin 1.新建项目(新建一个空console项目) 2.添加Nuget.config 增加

.Net core使用EF Core Migration做数据库升级

---恢复内容开始--- (1)VS Code下创建含有授权功能的并且使用localdb作为数据库的命令 dotnet new -au individual -uld --name identitySample identitySample为项目名称 (2)创建完成以后会在项目的appsettings.json文件下多出localdb的数据库连接字符串 (3)执行dotnet ef database update 可以将当前项目Migrations下的命令执行实体生成对应的表结构; 插播一下可以