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下 ...
随机推荐
- 重拾c++第三天(6):分支语句与逻辑运算符
1.逻辑运算符 && || ! 2.关系运算符优先级高于逻辑运算符 3.cctype库中好用的判断 4. ?:符号用法: 状态1?结果1:结果2 5.switch用法: switch ...
- 为什么说ArrayList是线程不安全的?
一.概述 对于ArrayList,相信大家并不陌生.这个类是我们平时接触得最多的一个列表集合类. 面试时相信面试官首先就会问到关于它的知识.一个经常被问到的问题就是:ArrayList是否是线程安全的 ...
- Qt Installer Framework翻译(2)
开始 Qt IFW作为Qt项目的一部分进行开发.该框架自身也使用Qt.然而,它能被用于安装所有类型的应用程序,包括(但不限于)使用Qt编译的. 支持的平台 已在下列平台中进行了测试: > Mic ...
- 为什么你应该使用 Kubernetes(k8s)
Kubernetes (Kube 或 K8s)越来越流行,他是市场上最好的容器编排工具之一. 1. 什么是容器? 容器就是一个包,其中包含了应用及其所有依赖. 容器中的应用与主机系统是隔离的,不关注环 ...
- GStreamer基础教程13 - 调试Pipeline
摘要 在很多情况下,我们需要对GStreamer创建的Pipeline进行调试,来了解其运行机制以解决所遇到的问题.为此,GStreamer提供了相应的调试机制,方便我们快速定位问题. 查看调试日志 ...
- EFCore-脚手架Scaffold发生Build Failed问题的终极解决
大家在使用EntityFrameworkCore的DBFirst的脚手架(Scaffolding)时应该遇到过Build Failed的错误,而没有任何提示,我也遇到过不少次,目前已经完美解决并将排查 ...
- NOI3.1 6377:生日相同 2.0
描述 在一个有180人的大班级中,存在两个人生日相同的概率非常大,现给出每个学生的名字,出生月日.试找出所有生日相同的学生. 输入 第一行为整数n,表示有n个学生,n ≤ 180.此后每行包含一个字符 ...
- NOI2.5 8465:马走日
描述 马在中国象棋以日字形规则移动. 请编写一段程序,给定n*m大小的棋盘,以及马的初始位置(x,y),要求不能重复经过棋盘上的同一个点,计算马可以有多少途径遍历棋盘上的所有点. 输入 第一行为整数T ...
- python,finally的应用
脚本执行过程中可能因为被测试的环境有改变导致中间某一部分无法继续执行下去 可以在最后一行加上finally来执行最后一句脚本 比如 最后执行退出 表示 无论中间过程失败还是成功,最终都会执行退出操作 ...
- Python3基础之内置模块
模块和包 一.定义: 模块:用来从逻辑上组织Python代码(变量,函数,类,逻辑:实现一个功能),本质就是.py结尾的Python文件包:用来从逻辑上组织模块,本质就是一个目录(必须带有一个__in ...