前言

前段时间写了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 操作的更多相关文章

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

    前言 随着互联网的的高速发展,大多数的公司由于一开始使用的传统的硬件/软件架构,导致在业务不断发展的同时,系统也逐渐地逼近传统结构的极限. 于是,系统也急需进行结构上的升级换代. 在服务端,系统的I/ ...

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

    前言 在上一篇文章中,我们介绍了如何根据不同的租户进行数据分离,分离的办法是一个租户一个数据库. 也提到了这种模式还是相对比较重,所以本文会介绍一种更加普遍使用的办法: 按表分离租户. 这样做的好处是 ...

  3. EF Core下利用Mysql进行数据存储在并发访问下的数据同步问题

    小故事 在开始讲这篇文章之前,我们来说一个小故事,纯素虚构(真实的存钱逻辑并非如此) 小刘发工资后,赶忙拿着现金去银行,准备把钱存起来,而与此同时,小刘的老婆刘嫂知道小刘的品性,知道他发工资的日子,也 ...

  4. .net core跨平台应用研究-ubuntu core下配置.net core运行时

    引言 年初研究了一阵子.net core跨平台应用,先后发表了几篇应用研究的文章.因工作原因,忙于项目上线,有一阵子没来博客园写文章了.最近项目基本收尾,抽空翻了下自己的博客,廖廖几篇文章,真让人汗颜 ...

  5. AspNet Core下利用 app-metrics+Grafana + InfluxDB实现高大上的性能监控界面

    在日常系统工作中,我们为了洞察系统的问题和运作情况通常会记录日志的方式来进行分析,但是在很多情况下都是被动的在出问题后才会去查日志.在很多时候,我们可能更需要相对实时的了解整个系统或者某一时段的运行的 ...

  6. AspNet Core 下利用普罗米修斯+Grafana构建Metrics和服务器性能的监控 (无心打造文字不喜勿喷谢谢!)

    概述 Prometheus的主要特点 组件 结构图 适用场景 不适用场景 安装node_exporter,系统性能指数收集(收集系统性能情况) 下载文件 解压并复制node_exporter应用程序到 ...

  7. .Net Core下使用HtmlAgilityPack解析采集互联网数据

    HtmlAgilityPack应该算是.Net下最好用的html解析库了. 因为最近帮朋友采集一些数据,在nuget里面搜索了好几个库,最后决定就用HtmlAgilityPack.并简单的记录下使用的 ...

  8. RedHat Linux下利用sersync进行实时同步数据

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://linux5588.blog.51cto.com/65280/772054 拓扑图 ...

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

    前言 写这篇文章的原因,其实由于我写EF core 实现多租户的时候,遇到的问题. 具体文章的链接: Asp.net core下利用EF core实现从数据实现多租户(1) Asp.net core下 ...

随机推荐

  1. 【一起学源码-微服务】Hystrix 源码一:Hystrix基础原理与Demo搭建

    说明 原创不易,如若转载 请标明来源! 欢迎关注本人微信公众号:壹枝花算不算浪漫 更多内容也可查看本人博客:一枝花算不算浪漫 前言 前情回顾 上一个系列文章讲解了Feign的源码,主要是Feign动态 ...

  2. nginx负载均衡动态自动更新(微博开源模块nginx-upsync-module使用)

    这几天项目有个需求:负载要求能根据节点健康状态动态的增减.nginx自带的upstram已经很强大,而且基于Nginx Upstream配置动态更新已经有很多开源方案,大多数都是基于生成配置文件后进行 ...

  3. Java Collection集合概述及其常用方法

    Collection集合概述 Java数组的长度是固定的,为了使程序能够方便地存储和操作数目不固定的一组数据,JDK类库提供了Java集合 与数组不同的是,集合中不能存放基本类型数据,而只能存放对象的 ...

  4. 头条面试竟然问我maven

    maven package和maven install 有什么区别? 你常用的maven命令有哪些? <dependencyManagement> 是干什么的? 还有用过其它构建工具吗? ...

  5. 【Linux】---Linux系统下各种常用命令总结

    在Linux系统下,“万物皆文件”,之所以强调在强调这个概念,是因为很多人已经习惯了win系统下找找点点得那种方式和思维,因此总是会觉得linux系统下很多指令既复杂又难记.其实都是一样得东西,只是w ...

  6. hdu - 4990

    Read the program below carefully then answer the question.    #pragma comment(linker, "/STACK:1 ...

  7. MyEclipse导出war包丢失文件问题解决

    这两天忙于一项目的上线,现总结一下遇到的一个奇怪问题的解决方案. 公司用的是Windows系统的服务器,所以省去了很多linux的繁琐命令.部署工作简单了很多.一切准备结束,放上War包启动服务器后, ...

  8. crawler 听课笔记 碎碎念 2 一些爬虫须知的基本常识和流程

    html的宗旨:      <标签 属性=”属性的值“></标签>        只是对于文本的一种解释划分吧 dom的宗旨:      就是一个大数组,处理方便,效率低 xm ...

  9. python3装饰器-进阶

    一.wraps 作用:优化装饰器 from functools import wraps # 导入wraps def wrapper(f): @wraps(f) # wraps的语法糖 def inn ...

  10. 8.for循环及练习

    For循环:   虽然所有循环结构都可以用 while 或者 do...while 表示,但Java提供了另一种语句— —for循环,使一些循环结构变的更加简单. for 循环语句是支持迭代的一种通用 ...