ShardingCore实现Saas多租户

ShardingCore是一款efcore下高性能、轻量级针对分表分库读写分离的框架。正好多租户需要用到分库分表,因此选择了这个框架,其官方文档提供了详细的多租户教程,我实现的过程中绝大部分照搬了这个教程。最后架子搭起来,写个文章记录一下,也让自己理解的深入一些。

1. 多租户

多租户主要有三种实现的方式,多数据库,单数据库多架构(Schema),单数据库单架构。简单来说就是不同的租户的数据是存在不同的数据库,或者一个数据库的不同架构,或者仅仅以表中的某个字段来区分,由此可以很容易的理清三种方案在成本、编写逻辑、维护、数据备份与恢复等方面的优劣势。

因为ShardingCore并没有直接提供分架构的用法,我又想采取折中方案,即不同的租户的数据存储在相同数据库的不同的Schema中,所以我的设想是使用ShardingCore分库的方法来实现分架构,然后我失败了。这里需要简单介绍一下我的数据库,项目选用的是PostgreSQL,因为要存储地理数据,因此数据库加装了postgis扩展,项目中引用了Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite库。这时候问题来了,PostgreSQL数据库中postgis扩展只能属于一个架构,没法所有架构都用,而我每个租户有各自的地理数据,因此没法采用这种方案,最终选择了成本最高,隔离性和安全性最好的多数据库的方案。

2. 租户与用户

这部分是我与官方教程比较大的区别。我的想法是租户是租户,用户是用户,一个租户可以有很多个用户,租户的管理员只是租户用户中的一个,因此,租户存在主数据库中,用户存在租户数据库中。

主数据库的DbContext

public class DefaultDbContext: DbContext
{
   
   
    public DefaultDbContext(DbContextOptions<DefaultDbContext> options) : base(options)
    {
   
   
    }

    protected override void OnConfiguring(DbContextOptionsBuilder builder)
    {
   
   
        base.OnConfiguring(builder);
    }

    /// <summary>
    /// 租户表
    /// </summary>
    public DbSet<Tenant> Tenants {
   
    get; set; }

    protected override void OnModelCreating(ModelBuilder builder)
    {
   
   
        base.OnModelCreating(builder);
        string assemblyName = AssemblyName.GetAssemblyName(Assembly.GetExecutingAssembly().Location).FullName;
        Assembly assembly = Assembly.Load(assemblyName);
        foreach (var type in assembly.GetTypes())
        {
   
   
            var baseclass = type.BaseType;
            if (baseclass == typeof(EntityBase))
            {
   
   
                //种子数据
                MethodInfo method = type.GetMethod("HasData");
                if (method != null)
                {
   
   
                    var data = (IEnumerable<object>)method.Invoke(null, null);
                    builder.Entity(type.FullName).HasData(data);
                }
                else
                {
   
   
                    builder.Entity(type.FullName);
                }
            }
        }
    }
}

租户数据库的DbContext

public class TenantDbContext: AbstractShardingDbContext
{
   
   
    public TenantDbContext(DbContextOptions<TenantDbContext> options) : base(options)
    {
   
   
    }
    
    /// <summary>
    /// 用户表
    /// </summary>
    public DbSet<User> Users {
   
    get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
   
   
        base.OnModelCreating(modelBuilder);
        string assemblyName = AssemblyName.GetAssemblyName(Assembly.GetExecutingAssembly().Location).FullName;
        Assembly assembly = Assembly.Load(assemblyName);
        foreach (var type in assembly.GetTypes())
        {
   
   
            var baseclass = type.BaseType;
            if (baseclass == typeof(TenantEntityBase))
            {
   
   
                //种子数据
                MethodInfo method = type.GetMethod("HasData");
                if (method != null)
                {
   
   
                    var data = (IEnumerable<object>)method.Invoke(null, null);
                    modelBuilder.Entity(type.FullName).HasData(data);
                }
                else
                {
   
   
                    modelBuilder.Entity(type.FullName);
                }
            }
        }
    }

这里TenantDbContext继承自AbstractShardingDbContext,是ShardingCore分库的配置。

我的想法是系统的超级管理员也只是系统中一个特别的租户而已,也就是这个租户可以管理其他租户,类似于二房东,也住这栋楼里。所以超级管理员登录,跟一个普通租户的普通用户登录应该没有什么区别,因此我额外加了一个常量,来记录这个二房东的ID。

/// <summary>
/// 租户相关的常量
/// </summary>
public static class TenantConstant
{
   
   
    /// <summary>
    /// 系统租户ID
    /// </summary>
    public const long SYSTEM_TENANT_ID = 1;
}

3. 租户与DbContext

每个租户是不同的数据库,有不同的连接字符串,因此不能如下直接注入,得对每个租户分别注入TenantDbContext。

builder.Services.AddDbContext<DefaultDbContext>(options => {
   
   
    options.UseNpgsql(builder.Configuration.GetConnectionString("NpgContext")); 
});

因为使用了ShardingCore,他允许通过IShardingRuntimeContext来进行注入。首先构造一个租户与注入参数的中间类:

/// <summary>
/// 租户分库配置
/// </summary>
public class ShardingTenantOptions
{
   
   
    /// <summary>
    /// 默认数据源名称
    /// </summary>
    public string DefaultDataSourceName {
   
    get; set; }
    /// <summary>
    /// 默认数据库地址
    /// </summary>
    public string DefaultConnectionString {
   
    get; set; }
    /// <summary>
    /// 分片迁移的命名空间关键字
    /// </summary>
    public string MigrationNamespace {
   
    get; set; }
}

创建一个IShardingRuntimeContext构建器

public class ShardingBuilder : IShardingBuilder
{
   
   
    public static readonly ILoggerFactory efLogger = LoggerFactory.Create(builder 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值