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下 ...
随机推荐
- SpringBoot入门(一)
1 简介 Spring Boot是快速搭建Spring工程的脚手架,简化配置与依赖关系(约定大于配置),让我们把精力集中在业务部分 2 简单入门事例 创建一个Hello World的Web工程 2.1 ...
- Android学习进度二
在最新的Android开发中,Google已经使用了新的开发技术,即使用Jectpack来开发App.所以今天我主要学习了这方面的知识. Jetpack 是一套库.工具和指南,可帮助开发者更轻松地编写 ...
- 书写markdown的利器
最近在用markdown记录一些东西,发现vscode本身对markdown的支持有点单薄,像一些数学公式是没办法及时预览的,而且也没有把markdown文件转换为html和pdf的功能,于是我从 ...
- 记一次docker镜像导出导入流程
目标:导出测试环境的镜像到本地机器 过程: 测试机: docker save -o /Dockerfile/crontabService/php72.tar lnmp72:v1.4 压缩,要不文件太大 ...
- Java Linked集合的简单介绍和常用方法的使用
LinkedList的简单介绍 java.util.LinkedList 集合数据存储的结构是链表结构.LinkedList是一个双向链表在实际开发中,对一个集合元素的添加和删除,经常涉及到首尾操作, ...
- React16源码解读:开篇带你搞懂几个面试考点
引言 如今,主流的前端框架React,Vue和Angular在前端领域已成三足鼎立之势,基于前端技术栈的发展现状,大大小小的公司或多或少也会使用其中某一项或者多项技术栈,那么掌握并熟练使用其中至少一种 ...
- 19秦皇岛现场赛F题 dfs
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6736 如果环的边长为k,那么环的删边方案数是2k-1.如果链的边长为k,那么链的删边方案数是2k.环的 ...
- 使用使用django-cors-headers解决跨域问题
安装 pip3 install -i https://pypi.douban.com/simple django-cors-headers 注册App INSTALLED_APPS = [ ... ' ...
- Django2 外键遇到的坑
# 出版社 class Publisher(models.Model): # 自增.主键 id id = models.AutoField(primary_key=True) # varchar(32 ...
- JS 点击验证码刷新
<img src="/get_valid_img" id="valid-img" title="点击再换一张" class=" ...