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进行判断
internal static IServiceCollection AddDatabase<TDbContext>(this IServiceCollection services,
ConnectionResolverOption option)
where TDbContext : DbContext, ITenantDbContext
{
services.AddSingleton(option); services.AddScoped<TenantInfo>();
services.AddScoped<ISqlConnectionResolver, TenantSqlConnectionResolver>(); services.AddDbContext<TDbContext>((serviceProvider, options) =>
{
var dbContextManager = serviceProvider.GetService<IDbContextManager>();
var resolver = serviceProvider.GetRequiredService<ISqlConnectionResolver>(); DbContextOptionsBuilder dbOptionBuilder = null;
switch (option.DBType)
{
case DatabaseIntegration.SqlServer:
dbOptionBuilder = options.UseSqlServer(resolver.GetConnection());
break;
case DatabaseIntegration.Mysql:
dbOptionBuilder = options.UseMySql(resolver.GetConnection());
break;
default:
throw new System.NotSupportedException("db type not supported");
}
if (option.Type == ConnectionResolverType.ByTabel || option.Type == ConnectionResolverType.BySchema)
{
dbOptionBuilder.ReplaceService<IModelCacheKeyFactory, TenantModelCacheKeyFactory<TDbContext>>();
}
}); return services;
}
修改 AddDatabase 方法
添加2个 AddTenantDatabasePerSchema 方法,实现根据Schema的分离
public static IServiceCollection AddTenantDatabasePerSchema<TDbContext>(this IServiceCollection services,
string connectionStringName, string key = "default")
where TDbContext : DbContext, ITenantDbContext
{
var option = new ConnectionResolverOption()
{
Key = key,
Type = ConnectionResolverType.BySchema,
ConnectinStringName = connectionStringName,
DBType = DatabaseIntegration.SqlServer
}; return services.AddTenantDatabasePerSchema<TDbContext>(option);
} public static IServiceCollection AddTenantDatabasePerSchema<TDbContext>(this IServiceCollection services,
ConnectionResolverOption option)
where TDbContext : DbContext, ITenantDbContext
{
if (option == null)
{
option = new ConnectionResolverOption()
{
Key = "default",
Type = ConnectionResolverType.BySchema,
ConnectinStringName = "default",
DBType = DatabaseIntegration.SqlServer
};
} return services.AddTenantDatabasePerTable<TDbContext>(option);
}
添加 AddTenantDatabasePerSchema 方法
2. 添加 DatabaseIntegration 枚举,用于标记db的类型
public enum DatabaseIntegration
{
None = ,
Mysql = ,
SqlServer =
}
3. 修改 TenantSqlConnectionResolver 类立面的GetConection 方法,在switch中添加添加一个case。就是代码中高亮部分
public string GetConnection()
{
string connectionString = null;
switch (this.option.Type)
{
case ConnectionResolverType.ByDatabase:
connectionString = configuration.GetConnectionString(this.tenantInfo.Name);
break;
case ConnectionResolverType.ByTabel:
case ConnectionResolverType.BySchema:
connectionString = configuration.GetConnectionString(this.option.ConnectinStringName);
break;
} if (string.IsNullOrEmpty(connectionString))
{
throw new NullReferenceException("can not find the connection");
}
return connectionString;
}
4. 修改 StoreDbContext 里的 OnModelCreating 方法,把之前按照Table分离数据的代码注释,重新添加按Schema分离数据的代码。
这里需要注意的是,在项目目前的结构,同一个DbContext,同时只能支持按Table或Schema其中的一种。
其实实际项目中,也的确没有必要需要对一个DbContext同时支持Table或Schema的支持,因为本质上这2种方式都是同时保存在一个数据库。
不过这是能实现的,本文暂不做实现。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// seperate by table
// modelBuilder.Entity<Product>().ToTable(this.tenantInfo.Name + "_Products"); // seperate by Schema
modelBuilder.Entity<Product>().ToTable(nameof(this.Products), "dbo."+this.tenantInfo.Name);
}
5. 修改 Startup 类里面的 ConfigureServices 方法,把之前按照Table分离数据的注入代码注释,重新添加新的代码。
public void ConfigureServices(IServiceCollection services)
{
// services.AddConnectionByDatabase<StoreDbContext>();
// services.AddTenantDatabasePerTable<StoreDbContext>("default");
services.AddTenantDatabasePerSchema<StoreDbContext>("mssql");
services.AddControllers();
}
验证效果
启动项目
本文没有加入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
Asp.net core下利用EF core实现从数据实现多租户(3): 按Schema分离 附加:EF Migration 操作的更多相关文章
- Asp.net core下利用EF core实现从数据实现多租户(1)
前言 随着互联网的的高速发展,大多数的公司由于一开始使用的传统的硬件/软件架构,导致在业务不断发展的同时,系统也逐渐地逼近传统结构的极限. 于是,系统也急需进行结构上的升级换代. 在服务端,系统的I/ ...
- Asp.net core下利用EF core实现从数据实现多租户(2) : 按表分离
前言 在上一篇文章中,我们介绍了如何根据不同的租户进行数据分离,分离的办法是一个租户一个数据库. 也提到了这种模式还是相对比较重,所以本文会介绍一种更加普遍使用的办法: 按表分离租户. 这样做的好处是 ...
- EF Core下利用Mysql进行数据存储在并发访问下的数据同步问题
小故事 在开始讲这篇文章之前,我们来说一个小故事,纯素虚构(真实的存钱逻辑并非如此) 小刘发工资后,赶忙拿着现金去银行,准备把钱存起来,而与此同时,小刘的老婆刘嫂知道小刘的品性,知道他发工资的日子,也 ...
- .net core跨平台应用研究-ubuntu core下配置.net core运行时
引言 年初研究了一阵子.net core跨平台应用,先后发表了几篇应用研究的文章.因工作原因,忙于项目上线,有一阵子没来博客园写文章了.最近项目基本收尾,抽空翻了下自己的博客,廖廖几篇文章,真让人汗颜 ...
- AspNet Core下利用 app-metrics+Grafana + InfluxDB实现高大上的性能监控界面
在日常系统工作中,我们为了洞察系统的问题和运作情况通常会记录日志的方式来进行分析,但是在很多情况下都是被动的在出问题后才会去查日志.在很多时候,我们可能更需要相对实时的了解整个系统或者某一时段的运行的 ...
- AspNet Core 下利用普罗米修斯+Grafana构建Metrics和服务器性能的监控 (无心打造文字不喜勿喷谢谢!)
概述 Prometheus的主要特点 组件 结构图 适用场景 不适用场景 安装node_exporter,系统性能指数收集(收集系统性能情况) 下载文件 解压并复制node_exporter应用程序到 ...
- .Net Core下使用HtmlAgilityPack解析采集互联网数据
HtmlAgilityPack应该算是.Net下最好用的html解析库了. 因为最近帮朋友采集一些数据,在nuget里面搜索了好几个库,最后决定就用HtmlAgilityPack.并简单的记录下使用的 ...
- RedHat Linux下利用sersync进行实时同步数据
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://linux5588.blog.51cto.com/65280/772054 拓扑图 ...
- EF core (code first) 通过自定义 Migration History 实现多租户使用同一数据库时更新数据库结构
前言 写这篇文章的原因,其实由于我写EF core 实现多租户的时候,遇到的问题. 具体文章的链接: Asp.net core下利用EF core实现从数据实现多租户(1) Asp.net core下 ...
随机推荐
- JS获取时间戳的几种方法与区别
var today = new Date() Date.now()) Date.parse(today) today.valueOf()) today.getTime() 可以看出,第二种是精确到秒的 ...
- goland编辑器永久激活
1 下载goland破解文件补丁 链接: https://pan.baidu.com/s/1i3dFAwscXPzKV-1imvgkdA 提取码: furt 2 打开goland的安装文件,将下载好的 ...
- nginx服务无法停止(Windows)
本人一般停止nginx服务都是通过Windows自带的任务管理器来强制结束nginx进程实现的,如图 2.但是 这次我通过同样的方法来结束nginx服务,发现nginx的进程无法结束 3.首先我要 ...
- day6 相对定位:position:relative
相对定位:position:relative 特点:a.相对于自己原来位置的定位,以自己的左上角为基准. b.相对定位原来的位置仍然算位置,不会出现浮动现象. 以下为初始位置:(可以看出设置margi ...
- DOCKER学习_017:Docker-Compose介绍
dockers三驾马车 Docker Machine Docker Swarm Docker Compose 一 Docker Compose介绍 Docker Compose是一个定义和运行多容器应 ...
- 公司项目redis 项目报错 记事
异常内容: Timeout performing GET Key_CacheHSCode, inst: 1, mgr: ExecuteSelect, err: never, queue: 2, qu ...
- Redis系列-存储hash主要操作命令
Redis系列-存储篇hash主要操作函数小结 hash是一些列key value(field value)的映射表.常常用其存储一些对象实例.相对于把一个对象的各个字段存储为string,存储为ha ...
- NIO&AIO编程模型
NIO线程模型 什么是NIO线程模型? 上图是NIO的线程模型, 基于select实现, 这种线程模型的特点: 多条channel通过一个选择器和单挑线程绑定, 并且在这种编程模型中, Cha ...
- 使用Async方法 Using Async Methods 精通ASP-NET-MVC-5-弗瑞曼 Listing 4-32.
- AVL练习题——宠物收养所
题目描述 最近,阿Q开了一间宠物收养所.收养所提供两种服务:收养被主人遗弃的宠物和让新的主人领养这些宠物.每个领养者都希望领养到自己满意的宠物,阿Q根据领养者的要求通过他自己发明的一个特殊的公式,得出 ...