1. 程式人生 > >Asp.net core下利用EF core實現從資料實現多租戶(3): 按Schema分離 附加:EF Migration 操作

Asp.net core下利用EF core實現從資料實現多租戶(3): 按Schema分離 附加:EF Migration 操作

前言

前段時間寫了EF core實現多租戶的文章,實現了根據資料庫,資料表進行多租戶資料隔離。

今天開始寫按照Schema分離的文章。

其實還有一種,是通過在資料表內新增一個欄位做多租戶的,但是這種模式我不打算講了。

 

如果大家看了文章感覺完全銜接不上,可以先看看前面的系列文章:

Asp.net core下利用EF core實現從資料實現多租戶(1)

Asp.net core下利用EF core實現從資料實現多租戶(2) : 按表分離

EF core (code first) 通過自定義 Migration History 實現多租戶使用同一資料庫時更新資料庫結構

 

關於EF core自動遷移:

之前的文章有朋友留言,覺得EF core不使用自動遷移就偏離了EF core的設計初衷。其實我覺得技術是實施的手段,而不是束縛專案的絆腳石。

1. 首先EF core並不是只有code first模式,

2. 其次EF core對db first模式支援很好,對於一些經歷幾年發展的專案會更加友好,因為對舊資料庫進行O/R不是1,2周可以完成的,

3. 再次在以往的EF migration經驗中,即使專案完全按照code first模式發展,但實際上更新資料庫的並不是通過的Web專案,而是通過一個控制檯,裡面包含了Migration檔案、資料遷移、結構校驗資料校驗。

    這個控制檯,一般通過CI/CD執行或手動執行。這是由於資料量、系統結構(例如多租戶等)決定的。

所以,EF core的自動遷移不是這個系列文章的主線主分支

如果閣下想參考自動遷移的實施步驟,歡迎檢視我的另一篇文章,是根據本文背景實現的自動遷移實施步驟:

EF core (code first) 通過自動遷移實現多租戶資料分離 :按Schema分離資料

 

實施

專案介紹

本專案是用系列文章的主分支程式碼進行修改的。目前專案主要支援使用MySql,通過分庫,分表實現多租戶。

本文需要實現分Schema,MySql不能實現,所以引入了MSSqlServer。

 

專案依賴

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

2. Mysql. 使用 Pomelo.EntityFrameworkCore.MySql 包, 版本3.1.1

3. MS Sql Server. 使用 Microsoft.EntityFrameworkCore.SqlServer 包,版本3.1.1  (本文新增的依賴)

3. EF core,Microsoft.EntityFrameworkCore, 版本3.1.1。這裡必須要用3.1的,因為ef core3.0是面向.net standard 2.1.  

 

 實施步驟

1. 由於我們引入了MsSql,所以要對 MultipleTenancyExtension 進行修改,對立面的所有方法都要新增db型別進行傳參。

修改 AddDatabase 方法,立面需要對sql server和MySql進行判斷

 1 internal static IServiceCollection AddDatabase<TDbContext>(this IServiceCollection services,
 2                 ConnectionResolverOption option)
 3             where TDbContext : DbContext, ITenantDbContext
 4 {
 5     services.AddSingleton(option);
 6 
 7     services.AddScoped<TenantInfo>();
 8     services.AddScoped<ISqlConnectionResolver, TenantSqlConnectionResolver>();
 9     
10     services.AddDbContext<TDbContext>((serviceProvider, options) =>
11     {
12         var dbContextManager = serviceProvider.GetService<IDbContextManager>();
13         var resolver = serviceProvider.GetRequiredService<ISqlConnectionResolver>();
14 
15         DbContextOptionsBuilder dbOptionBuilder = null;
16         switch (option.DBType)
17         {
18             case DatabaseIntegration.SqlServer:
19                 dbOptionBuilder = options.UseSqlServer(resolver.GetConnection());
20                 break;
21             case DatabaseIntegration.Mysql:
22                 dbOptionBuilder = options.UseMySql(resolver.GetConnection());
23                 break;
24             default:
25                 throw new System.NotSupportedException("db type not supported");
26         }
27         if (option.Type == ConnectionResolverType.ByTabel || option.Type == ConnectionResolverType.BySchema)
28         {
29             dbOptionBuilder.ReplaceService<IModelCacheKeyFactory, TenantModelCacheKeyFactory<TDbContext>>();
30         }
31     });
32 
33     return services;
34 }
修改 AddDatabase 方法

新增2個 AddTenantDatabasePerSchema 方法,實現根據Schema的分離

 1 public static IServiceCollection AddTenantDatabasePerSchema<TDbContext>(this IServiceCollection services,
 2         string connectionStringName, string key = "default")
 3     where TDbContext : DbContext, ITenantDbContext
 4 {
 5     var option = new ConnectionResolverOption()
 6     {
 7         Key = key,
 8         Type = ConnectionResolverType.BySchema,
 9         ConnectinStringName = connectionStringName,
10         DBType = DatabaseIntegration.SqlServer
11     };
12 
13 
14     return services.AddTenantDatabasePerSchema<TDbContext>(option);
15 }
16 
17 public static IServiceCollection AddTenantDatabasePerSchema<TDbContext>(this IServiceCollection services,
18         ConnectionResolverOption option)
19     where TDbContext : DbContext, ITenantDbContext
20 {
21     if (option == null)
22     {
23         option = new ConnectionResolverOption()
24         {
25             Key = "default",
26             Type = ConnectionResolverType.BySchema,
27             ConnectinStringName = "default",
28             DBType = DatabaseIntegration.SqlServer
29         };
30     }
31 
32     return services.AddTenantDatabasePerTable<TDbContext>(option);
33 }
新增 AddTenantDatabasePerSchema 方法

 

2. 新增 DatabaseIntegration 列舉,用於標記db的型別

1 public enum DatabaseIntegration
2 {
3     None = 0,
4     Mysql = 1,
5     SqlServer = 2
6 }

 

3. 修改 TenantSqlConnectionResolver 類立面的GetConection 方法,在switch中新增新增一個case。就是程式碼中高亮部分

 1 public string GetConnection()
 2 {
 3     string connectionString = null;
 4     switch (this.option.Type)
 5     {
 6         case ConnectionResolverType.ByDatabase:
 7             connectionString = configuration.GetConnectionString(this.tenantInfo.Name);
 8             break;
 9         case ConnectionResolverType.ByTabel:
10         case ConnectionResolverType.BySchema:
11             connectionString = configuration.GetConnectionString(this.option.ConnectinStringName);
12             break;
13     }
14         
15     if (string.IsNullOrEmpty(connectionString))
16     {
17         throw new NullReferenceException("can not find the connection");
18     }
19     return connectionString;
20 }

 

 4. 修改 StoreDbContext 裡的 OnModelCreating 方法,把之前按照Table分離資料的程式碼註釋,重新新增按Schema分離資料的程式碼。

這裡需要注意的是,在專案目前的結構,同一個DbContext,同時只能支援按Table或Schema其中的一種。

其實實際專案中,也的確沒有必要需要對一個DbContext同時支援Table或Schema的支援,因為本質上這2種方式都是同時儲存在一個數據庫。

不過這是能實現的,本文暫不做實現。

1 protected override void OnModelCreating(ModelBuilder modelBuilder)
2 {
3     // seperate by table
4     // modelBuilder.Entity<Product>().ToTable(this.tenantInfo.Name + "_Products");
5 
6     // seperate by Schema
7     modelBuilder.Entity<Product>().ToTable(nameof(this.Products), "dbo."+this.tenantInfo.Name);
8 }

 

5. 修改 Startup 類裡面的 ConfigureServices 方法,把之前按照Table分離資料的注入程式碼註釋,重新新增新的程式碼。

1 public void ConfigureServices(IServiceCollection services)
2 {
3     // services.AddConnectionByDatabase<StoreDbContext>();
4     // services.AddTenantDatabasePerTable<StoreDbContext>("default");
5     services.AddTenantDatabasePerSchema<StoreDbContext>("mssql");
6     services.AddControllers();
7 }

 

驗證效果

 啟動專案

本文沒有加入EF core的自動遷移程式碼,如果需要需要檢視如果實現自動遷移,請參考我的另一篇文章,是系列文章的附加內容,並不是專案中的主要內容。

自動遷移文章

EF core (code first) 通過自動遷移實現多租戶資料分離 :按Schema分離資料

 

呼叫介面

1. 我們還是跟本系列的其他文章一樣,分別在store1和store2中新增資料。

其中怎麼新增的就不再重複貼圖了,簡單來說就是呼叫controller的post方法在資料庫中新增資料

 

下面是store1的查詢結果

 

store2的查詢結果

 

 

檢視資料庫資料和結構

可以看到在multiple_tenancy_default3裡面,有4個Schema,其中 dbo.store1 和 dob.store2 是存放我們的表。

 

 

store1中的資料

 

store2中的資料

 

 

 

 

 總結

本文跟本系列一樣,都是非常簡單的實操性指引。完成本文之後,實際上已經實現了本專案的所有需求,分別是按資料庫,按表,按Schema分離資料。

但是如果把這種程式碼結構全搬進去實際商用專案中,還是操之過早,希望閣下可以等待我們把程式碼結構整理和抽象後再結合到專案。因為系列主線文章比較簡單,這種程式碼結構實際上不適合專案長期發展。

 

關於程式碼

本系列文章全部在github上。

請檢視part3分支的程式碼

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