前言

随着互联网的的高速发展,大多数的公司由于一开始使用的传统的硬件/软件架构,导致在业务不断发展的同时,系统也逐渐地逼近传统结构的极限。

于是,系统也急需进行结构上的升级换代。

在服务端,系统的I/O是很大的瓶颈。其中数据库的I/O最容易成为限制系统效率的一环。在优化数据库I/O这一环中,可以从优化系统调用数据库效率、数据库自身效率等多方面入手。

一般情况下,通过升级数据库服务器的硬件是最容易达到的。但是服务器资源不可能无限扩大,于是从调用数据库的效率方面入手是目前主流的优化方向。

于是读写分离、分库分表成为了软件系统的重要一环。并且需要在传统的系统架构下,是需要做强入侵性的修改。

什么是多租户:

多租户的英文是Multiple Tenancy,很多人把多租户和Saas划上等号,其实这还是有区别的。我们今天不讨论Sass这种如此广泛的议题。

现在很多的系统都是to B的,它们面向的是组织、公司和商业机构等。每个机构会有独立的组织架构,独立的订单结构,独立的服务等级和收费。

这就造成了各个机构间的数据是天然独立的,特别是部分的公司对数据的独立和安全性会有较高要求,往往数据是需要独立存储的。

由于多租户数据的天然独立,造成了系统可以根据机构的不同进行分库分表。所以这里讨论的多租户,仅限于数据层面的!

写这篇文章原因

其实由于一个群的朋友问到了相关的问题,由于当时我并没有dotnet环境,所以简单地写了几句代码,我本身是不知道代码是否正确的。

在我有空的时候,试了一下原来是可实施的。我贴上当时随手写的核心代码,其中connenctionResolver是需要自己创建的。

这代码是能用的,如果对于asp.net core很熟悉的人,把这段代码放入到ConfigureServices方法内即可。

但是我还是强烈建议大家跟着我的介绍逐步实施。

 services.AddDbContext<MyContext>((serviceProvider, options)=>
{
var connenctionResolver = serviceProvider.GetService<IConnectionResolver>();
options.UseSqlServer(connenctionResolver.ConnectionString);
});

系列文章目录

主线文章

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

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

Asp.net core下利用EF core实现从数据实现多租户(3): 按Schema分离 附加:EF Migration 操作  

附加文章

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

EF core (code first) 通过自动迁移实现多租户数据分离 :按Schema分离数据

实施

项目介绍

这个Demo,主要通过根据http request header来获取不同的租户的标识,从而达到区分租户最终实现数据的隔离。

项目依赖:

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

2. Mysql. 使用 Pomelo.EntityFrameworkCore.MySql 包

3. EF core,Microsoft.EntityFrameworkCore, 版本3.1.1。这里必须要用3.1的,因为ef core3.0是面向.net standard 2.1.

项目中必须对象是什么:

1. DbContext和对应数据库对象

2. ConnenctionResolver, 用于获取连接字符串

3. TenantInfo, 用于表示租户信息

4. TenantInfoMiddleware,用于在asp.net core管道根据http的内容从而解析出TenantInfo

5. Controller, 用于实施对应的

实施步骤

1. 创建TenanInfo 和 TenantInfoMiddleware. TenanInfo 作为租户的信息,通过IOC创建,并且在TenantInfoMiddleware通过解析http request header,修改TenantInfo

 using System;

 namespace kiwiho.Course.MultipleTenancy.EFcore.Api.Infrastructure
{
public class TenantInfo
{
public string Name { get; set; }
}
}
 using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection; namespace kiwiho.Course.MultipleTenancy.EFcore.Api.Infrastructure
{
public class TenantInfoMiddleware
{
private readonly RequestDelegate _next; public TenantInfoMiddleware(RequestDelegate next)
{
_next = next;
} public async Task InvokeAsync(HttpContext context)
{
var tenantInfo = context.RequestServices.GetRequiredService<TenantInfo>();
var tenantName = context.Request.Headers["Tenant"]; if (string.IsNullOrEmpty(tenantName))
tenantName = "default"; tenantInfo.Name = tenantName; // Call the next delegate/middleware in the pipeline
await _next(context);
}
}
}

TenantInfoMiddleware

2. 创建HttpHeaderSqlConnectionResolver并且实现ISqlConnectionResolver接口。这里要做的事情很简单,直接同TenantInfo取值,并且在配置文件查找对应的connectionString。

其实这个实现类在正常的业务场景是需要包含逻辑的,但是在Demo里为了简明扼要,就使用最简单的方式实现了。

 using System;
using Microsoft.Extensions.Configuration; namespace kiwiho.Course.MultipleTenancy.EFcore.Api.Infrastructure
{
public interface ISqlConnectionResolver
{
string GetConnection(); } public class HttpHeaderSqlConnectionResolver : ISqlConnectionResolver
{
private readonly TenantInfo tenantInfo;
private readonly IConfiguration configuration; public HttpHeaderSqlConnectionResolver(TenantInfo tenantInfo, IConfiguration configuration)
{
this.tenantInfo = tenantInfo;
this.configuration = configuration;
}
public string GetConnection()
{
var connectionString = configuration.GetConnectionString(this.tenantInfo.Name);
if(string.IsNullOrEmpty(connectionString)){
throw new NullReferenceException("can not find the connection");
}
return connectionString;
}
}
}

ConnectionResolver

3. 创建类MultipleTenancyExtension,里面包含最重要的配置数据库连接字符串的方法。其中里面的DbContext并没有使用泛型,是为了更加简明点

 using kiwiho.Course.MultipleTenancy.EFcore.Api.DAL;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore; namespace kiwiho.Course.MultipleTenancy.EFcore.Api.Infrastructure
{
public static class MultipleTenancyExtension
{
public static IServiceCollection AddConnectionByDatabase(this IServiceCollection services)
{
services.AddDbContext<StoreDbContext>((serviceProvider, options)=>
{
var resolver = serviceProvider.GetRequiredService<ISqlConnectionResolver>(); options.UseMySql(resolver.GetConnection());
}); return services;
}
}
}

MultipleTenancyExtension

4. 在Startup类中配置依赖注入和把TenantInfoMiddleware加入到管道中。

 public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<TenantInfo>();
services.AddScoped<ISqlConnectionResolver, HttpHeaderSqlConnectionResolver>();
services.AddConnectionByDatabase();
services.AddControllers();
}

ConfigureServices

在Configure内,在UseRouting前把TenantInfoMiddleware加入到管道

 app.UseMiddleware<TenantInfoMiddleware>();

5. 配置好DbContext和对应的数据库对象

 using Microsoft.EntityFrameworkCore;

 namespace kiwiho.Course.MultipleTenancy.EFcore.Api.DAL
{
public class StoreDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
public StoreDbContext(DbContextOptions options) : base(options)
{
}
} }

StoreDbContext

 using System.ComponentModel.DataAnnotations;

 namespace kiwiho.Course.MultipleTenancy.EFcore.Api.DAL
{
public class Product
{
[Key]
public int Id { get; set; } [StringLength(), Required]
public string Name { get; set; } [StringLength()]
public string Category { get; set; } public double? Price { get; set; }
}
}

Product

6. 创建ProductController, 并且在里面添加3个方法,分别是创建,查询所有,根据id查询。在构造函数通过EnsureCreated以达到在数据库不存在是自动创建数据库。

 using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using kiwiho.Course.MultipleTenancy.EFcore.Api.DAL;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; namespace kiwiho.Course.MultipleTenancy.EFcore.Api.Controllers
{
[ApiController]
[Route("api/Products")]
public class ProductController : ControllerBase
{
private readonly StoreDbContext storeDbContext; public ProductController(StoreDbContext storeDbContext){
this.storeDbContext = storeDbContext;
this.storeDbContext.Database.EnsureCreated();
} [HttpPost("")]
public async Task<ActionResult<Product>> Create(Product product){
var rct = await this.storeDbContext.Products.AddAsync(product); await this.storeDbContext.SaveChangesAsync(); return rct?.Entity; } [HttpGet("{id}")]
public async Task<ActionResult<Product>> Get([FromRoute] int id){ var rct = await this.storeDbContext.Products.FindAsync(id); return rct; } [HttpGet("")]
public async Task<ActionResult<List<Product>>> Search(){
var rct = await this.storeDbContext.Products.ToListAsync();
return rct;
}
}
}

ProductController

验证效果

1. 启动项目

2. 通过postman在store1中创建一个Orange,在store2中创建一个cola。要注意的是Headers仲的Tenant:store1是必须的。

图片就只截了store1的例子

3. 分别在store1,store2中查询所有product

store1:只查到了Orange

store2: 只查到了cola

4. 通过查询数据库验证数据是否已经隔离。可能有人会觉得为什么2个id都是1。是因为Product的Id使用 [Key] ,数据库的id是自增长的。

其实这是故意为之的,为的是更好的展示这2个对象是在不同的数据库

store1:

store2:

总结

这是一个很简单的例子,似乎把前言读完就已经能实现,那么为什么还要花费那么长去介绍呢。

这其实是一个系列文章,这里只做了最简单的介绍。具体来说,它真的是一个Demo。

接下来要做什么:

在很多实际场景中,其实一个机构一个数据库,这种模式似乎太重了,而且每个机构都需要部署数据库服务器和实例好像很难自动化。

并且,大多数的机构,其实完全没有必要独立一个数据库的。可以通过分表,分Schema实现数据隔离。

所以接下来我会介绍怎么利用EFCore的现有接口实施。并且最终把核心代码做成类库,并且结合MySql,SqlServer做成扩展

关于代码

文章中的代码并非全部代码,如果仅仅拷贝文章的代码可能还不足以实施。但是关键代码已经全部贴出

代码全部放到github上了。这是part1,请checkout分支part1. 或者在master分支上的part1文件夹内。

可以查看master上commit tag是part1 的部分

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

Asp.net core下利用EF core实现从数据实现多租户(1)的更多相关文章

  1. Asp.net core下利用EF core实现从数据实现多租户(3): 按Schema分离 附加:EF Migration 操作

    前言 前段时间写了EF core实现多租户的文章,实现了根据数据库,数据表进行多租户数据隔离. 今天开始写按照Schema分离的文章. 其实还有一种,是通过在数据表内添加一个字段做多租户的,但是这种模 ...

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

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

  3. 【asp.net core 系列】8 实战之 利用 EF Core 完成数据操作层的实现

    0. 前言 通过前两篇,我们创建了一个项目,并规定了一个基本的数据层访问接口.这一篇,我们将以EF Core为例演示一下数据层访问接口如何实现,以及实现中需要注意的地方. 1. 添加EF Core 先 ...

  4. EF Core 快速上手——EF Core 入门

    EF Core 快速上手--EF Core 介绍 本章导航 从本书你能学到什么 对EF6.x 程序员的一些话 EF Core 概述 1.3.1 ORM框架的缺点 第一个EF Core应用   本文是对 ...

  5. EF Core 快速上手——EF Core的三种主要关系类型

    系列文章 EF Core 快速上手--EF Core 入门 本节导航 三种数据库关系类型建模 Migration方式创建和习修改数据库 定义和创建应用DbContext 将复杂查询拆分为子查询   本 ...

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

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

  7. ASP.NET Core 中使用EF Core 将实体映射到数据库表的方法(SQL Server)

    前段时间听过一个关于使用ASP.NET Core建立项目的视频.其中使用EF Core映射到数据库的部分是按部就班地学习.今天自己建立项目时,有些步骤已经有一些遗忘.所以写下这篇文章,顺便理清思路. ...

  8. 在ASP.NET Core中通过EF Core实现一个简单的全局过滤查询

    前言 不知道大家是否和我有同样的问题: 一般在数据库的设计阶段,会制定一些默认的规则,其中有一条硬性规定就是一定不要对任何表中的数据执行delete硬删除操作,因为每条数据对我们来说都是有用的,并且是 ...

  9. Asp.net core 学习笔记 ( ef core )

    更新 : 2018-11-26 这里记入一下关于 foreignKey cascade action 默认情况下如果我们使用 data annotation required + foreginkey ...

随机推荐

  1. 常见的sql注入环境搭建

    常见的sql注入环境搭建 By : Mirror王宇阳 Time:2020-01-06 PHP+MySQL摘要 $conn = new mysqli('数据库服务器','username','pass ...

  2. React实现座位排布组件

    React实现座位排布组件 最近在开发一个影院系统的后台管理系统,该后台可以设置一个影厅的布局. 后台使用的是react框架,一位大神学长在几天之内就把这个控件研究出来了,并进行了较为严密的封装,佩服 ...

  3. 一个简单的spring boot程序

    搭建一个spring boot项目十分的方便,网上也有许多,可以参考 https://www.cnblogs.com/ityouknow/p/5662753.html 进行项目的搭建.在此我就不详细介 ...

  4. Java面向对象之异常详解

    目录 Java面向对象之异常[一] Java面向对象之异常[二] 捕获异常的规则 访问异常信息 异常对方法重写的影响 finally详解 Java面向对象之异常[一] Java面向对象之异常[二] 往 ...

  5. leetcode腾讯精选练习之两数相加

    两数相加 题目: 给出两个非空的链表用来表示两个非负的整数.其中,它们各自的位数是按照逆序的方式存储的,并且它们的每个节点只能存储一位数字.如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们 ...

  6. 小白学 Python 爬虫(40):爬虫框架 Scrapy 入门基础(七)对接 Selenium 实战

    人生苦短,我用 Python 前文传送门: 小白学 Python 爬虫(1):开篇 小白学 Python 爬虫(2):前置准备(一)基本类库的安装 小白学 Python 爬虫(3):前置准备(二)Li ...

  7. 1z0-062 题库解析2

    Examine the parameters for a database instance: NAME TYPE VALUE-------------------------------- ---- ...

  8. CentOS 7.2 搭建Jenkins

    1,准备阶段 jenkins下载 https://jenkins.io/download/ 这里我们选择稳定版本 (本文介绍的是通过tomcat来部署的,由于jenkins内部也集成了容器,也可直接通 ...

  9. Centos7 搭建Grafana+Jmeter+Influxdb 性能实时监控平台

    未完,待更新 背景 日常工作中,经常会用到Jmeter去压测,毕竟LR还要钱(@¥&*...),而最常用的接口压力测试,我们都是通过聚合报告去查看压测结果的,然鹅聚合报告的真的是丑到家了,作为 ...

  10. [洛谷P2962] [USACO09NOV] 灯Lights

    Description Bessie and the cows were playing games in the barn, but the power was reset and the ligh ...