.NET SAAS 架构与设计 -SqlSugar ORM
阅读原文时间:2021年12月06日阅读:4

常用的Saas分库分为2种类型的库

1.1 基础信息库

主要存组织架构 、权限、字典、用户等 公共信息

性能优化:因为基础信息库是共享的,所以我们可以使用 读写分离,或者二级缓存来进行性能上的优化

2.2 业务库

我们要进行的分库都基于业务库进行分库,例如 A集团使用 A01库 ,B集团使用B01库 ,也可以多个小集团使用一个 数据库

如下:

业务库1   :集团A

业务库2  :    集团B, 集团F

业务库3 :     集团C, 集团D, 集团E

性能优先:因为合理的进行了分库,所以在性能上并没有什么瓶颈,并且数据库可以扔到不同的服务器上

下面的表设计的比较简单,主要是通过用户可以拿到当前用户的连接字符串,然进行数据库操作

2.1 数据库配置表

主键、数据库连接信息、集团ID  (基础信息库)

2.2 用户表

主键、用户名、密码、集团ID (基础信息库)

下面的代码很简单,我们通了多租户方式实现了动态根据用户获取不同的业务库

操作业务库我们使用   DbManger.BizDb

操作基础信息库我们使用 DbManger.MasterDb

如果我们要用到事务就用  DbManger.Db

///

/// 数据库管理 ///
public class DbManger
{

   /// <summary>  
   /// 获取业务库对象  
   /// </summary>  
    public static ISqlSugarClient BizDb  
    {  
        get  
        {  
            UserInfo user = GetUserInfo();//获取用户数据库连接字符串信息  
            var configId = user.OrgId.ToString();//集团ID(也可以叫租户ID)  
            if(!Db.IsAnyConnection(configId)){  
                  Db.AddConnection(new ConnectionConfig() {  
                    ConfigId = configId,  
                    ConnectionString = "DataSource=" + user.Connection,  
                    DbType = DbType.SqlServer,  
                     IsAutoCloseConnection = true });  
            }  
            var result=Db.GetConnection(configId);  
           //可以给业务库result设置AOP  
            return result;  
        }  
    }

    /// <summary>  
    /// 业取基础信息库  
    /// </summary>  
    public static ISqlSugarClient MasterDb  
    {  
        get  
        {  
            //如果是跨服务库分库,也需要动态配置的,因为库的IP会变  
            //参考业务库用法  
            return Db.GetConnection("default");  
        }  
    }

    /// <summary>  
    /// 利用SqlSugarScope单例+多租户来处理多库事务  
    /// </summary>  
    public static SqlSugarScope Db =new SqlSugarScope(new ConnectionConfig()  
    {  
        ConfigId="default",  
        DbType = SqlSugar.DbType.SqlServer,  
        ConnectionString =  @"基础信息库连接字符串",  
        IsAutoCloseConnection = true  
    },  
     db =>  
     {  
         //基础库AOP  
         db.Aop.OnLogExecuting = (s, p) =>  
         {

         };

     });

    /// <summary>  
    /// 获取用户数据库连接字符串信息  
    /// </summary>  
    /// <returns></returns>  
    private static UserInfo GetUserInfo()  
    {  
        throw new System.NotImplementedException();  
    }

}

使用用例,继承后直接使用

public class OrderManger: DbManger
{
public void Test()
{
try
{
Db.BeginTran();//用Db管理 MasterDb和BizDb事务

            MasterDb.Insertable(xxx).ExecuteCommand();//操作基础信息库  
            BizDb.Insertable(xxx).ExecuteCommand();//操作业务库

            Db.CommitTran();//统一事务

         }  
        catch (System.Exception ex)  
        {  
            Db.RollbackTran();  
            throw ex;  
        };  
    }  
}

4.1 跨库同服务器

跨库查询我们要用BizDb进行查询,因为BizDb是多变的,而MasterDb是固定的,查询用BizDb为主

var List = BizDb.Queryable<Order>()  
        .LeftJoin<Custom>((o, cus) => o.CustomId == cus.Id)  
        .AS<Custom>("SQLSUGAR4XTEST.dbo.Custom") //主表用AS("") 从表用AS<T>("")  
        .Where(o => o.Id == 1)  
        .Select((o, cus) => new ViewOrder { Id = o.Id, CustomName = cus.Name })  
        .ToList();

生成的SQL如下:

SELECT [o].[Id] AS [Id] ,
[cus].[Name] AS [CustomName]
FROM [Order] o
Left JOIN [SQLSUGAR4XTEST].[dbo].[Custom] cus --生成的Sql就多了库名
ON ( [o].[CustomId] = [cus].[Id] ) WHERE ( [o].[Id] = @Id0 )

注意:上面的例子是SqlServer跨库查询的用法,不同的数据库跨库查询用法不一样

更多用法: https://www.donet5.com/Home/Doc?typeId=2244

4.2跨库不同服务器

因为我们基础信息库是固定的,所以我们可以把基础信息库,同步到不同的服务器上,通过读分离的方式 ,那么每个业务服务器都会有一个

基础信息库了,然后在通过4.1的方式实现

我们可以通过CodeFirst创建业务库和表

https://www.donet5.com/Home/Doc?typeId=1207

一个业务库中的表对应多个集团,那我们设计表的时候肯定有个 OrgId或者租户ID进行数据表区别

我们需要将DbManger.bizDb进行修改,添加相应的表过滤器如果表中有OrgId的就可以添加过滤器

///

/// 获取业务库对象 ///
public static ISqlSugarClient BizDb
{
get
{
UserInfo user = GetUserInfo();//获取用户数据库连接字符串信息
var configId = user.OrgId.ToString();//集团ID(也可以叫租户ID)
if(!Db.IsAnyConnection(configId)){
Db.AddConnection(new ConnectionConfig() {
ConfigId = configId,
ConnectionString = "DataSource=" + user.Connection,
DbType = DbType.SqlServer,
IsAutoCloseConnection = true });
}

      var result=Db.GetConnection(configId);  
      result.QueryFilter.Add(new TableFilterItem<Order>(it => it.OrgId==configId);//添加表过滤器  
     //可以多个表

     return result;  
}  

}

https://www.donet5.com/Home/Doc?typeId=1205

我们可以通过差异日志拿到数据更变记录,记录到日志,SAAS操作一些核心数据差异日志肯定少不了,安全系数高

db.Aop.OnDiffLogEvent = it =>
{
//操作前记录 包含: 字段描述 列名 值 表名 表描述
var editBeforeData = it.BeforeData;
//操作后记录 包含: 字段描述 列名 值 表名 表描述
var editAfterData = it.AfterData;
var sql = it.Sql;
var parameter = it.Parameters;
var data = it.BusinessData;//这边会显示你传进来的对象
var time = it.Time;
var diffType=it.DiffType;//enum insert 、update and delete

            //Write logic  

};

db.Insertable(new Student() { Name = "beforeName" })
.EnableDiffLogEvent() //注意需要加上启用日志,可以传参数
.ExecuteReturnIdentity(); 

具体用法:https://www.donet5.com/Home/Doc?typeId=1204

SAAS架构用到技术基本上都是现有功能,我这一篇文章相当于一个汇总,本人也做了6年SAAS架构,至少目前的方案是成熟方案 ,并且有产品验证并且上线运营,如果有更好的建议可能发贴,或者直接找我沟通

常用的Saas分库分为2种类型的库

1.1 基础信息库

主要存组织架构 、权限、字典、用户等 公共信息

性能优化:因为基础信息库是共享的,所以我们可以使用 读写分离,或者二级缓存来进行性能上的优化

2.2 业务库

我们要进行的分库都基于业务库进行分库,例如 A集团使用 A01库 ,B集团使用B01库 ,也可以多个小集团使用一个 数据库

如下:

业务库1   :集团A

业务库2  :    集团B, 集团F

业务库3 :     集团C, 集团D, 集团E

性能优先:因为合理的进行了分库,所以在性能上并没有什么瓶颈,并且数据库可以扔到不同的服务器上

下面的表设计的比较简单,主要是通过用户可以拿到当前用户的连接字符串,然进行数据库操作

2.1 数据库配置表

主键、数据库连接信息、集团ID  (基础信息库)

2.2 用户表

主键、用户名、密码、集团ID (基础信息库)

下面的代码很简单,我们通了多租户方式实现了动态根据用户获取不同的业务库

操作业务库我们使用   DbManger.BizDb

操作基础信息库我们使用 DbManger.MasterDb

如果我们要用到事务就用  DbManger.Db

///&nbsp;<summary>

///&nbsp;数据库管理

///&nbsp;</summary>

public class DbManger

{

///&nbsp;<summary>

///&nbsp;获取业务库对象

///&nbsp;</summary>

public static ISqlSugarClient&nbsp;BizDb

{

get 

{

UserInfo&nbsp;user&nbsp;=&nbsp;GetUserInfo();``//获取用户数据库连接字符串信息

var configId&nbsp;=&nbsp;user.OrgId.ToString();``//集团ID(也可以叫租户ID)

if``(!Db.IsAnyConnection(configId)){

Db.AddConnection(``new ConnectionConfig()&nbsp;{

ConfigId&nbsp;=&nbsp;configId,

ConnectionString&nbsp;= "DataSource=" +&nbsp;user.Connection,

DbType&nbsp;=&nbsp;DbType.SqlServer,

IsAutoCloseConnection&nbsp;= true });

}

var result=Db.GetConnection(configId);

//可以给业务库result设置AOP

return result;

}

}

///&nbsp;<summary>

///&nbsp;业取基础信息库

///&nbsp;</summary>

public static ISqlSugarClient&nbsp;MasterDb

{

get

{

//如果是跨服务库分库,也需要动态配置的,因为库的IP会变

//参考业务库用法

return Db.GetConnection(``"default"``);

}

}

///&nbsp;<summary>

///&nbsp;利用SqlSugarScope单例+多租户来处理多库事务

///&nbsp;</summary>

public static SqlSugarScope&nbsp;Db&nbsp;=``new SqlSugarScope(``new ConnectionConfig()

{

ConfigId=``"default"``,

DbType&nbsp;=&nbsp;SqlSugar.DbType.SqlServer,

ConnectionString&nbsp;= @"基础信息库连接字符串"``,

IsAutoCloseConnection&nbsp;= true

},

db&nbsp;=>

{

//基础库AOP

db.Aop.OnLogExecuting&nbsp;=&nbsp;(s,&nbsp;p)&nbsp;=>

{

};

});

///&nbsp;<summary>

///&nbsp;获取用户数据库连接字符串信息

///&nbsp;</summary>

///&nbsp;<returns></returns>

private static UserInfo&nbsp;GetUserInfo()

{

throw new System.NotImplementedException();

}

}

使用用例,继承后直接使用

public class OrderManger:&nbsp;DbManger

{

public void Test()

{

try

{

Db.BeginTran();``//用Db管理&nbsp;MasterDb和BizDb事务

MasterDb.Insertable(xxx).ExecuteCommand();``//操作基础信息库

BizDb.Insertable(xxx).ExecuteCommand();``//操作业务库

Db.CommitTran();``//统一事务

}

catch (System.Exception&nbsp;ex)

{

Db.RollbackTran();

throw ex;

};

}

}

4.1 跨库同服务器

跨库查询我们要用BizDb进行查询,因为BizDb是多变的,而MasterDb是固定的,查询用BizDb为主

var List&nbsp;=&nbsp;BizDb.Queryable<Order>()

.LeftJoin<Custom>((o,&nbsp;cus)&nbsp;=>&nbsp;o.CustomId&nbsp;==&nbsp;cus.Id)

.AS<Custom>(``"SQLSUGAR4XTEST.dbo.Custom"``) //主表用AS("")&nbsp;从表用AS<T>("")

.Where(o&nbsp;=>&nbsp;o.Id&nbsp;==&nbsp;1)

.Select((o,&nbsp;cus)&nbsp;=> new ViewOrder&nbsp;{&nbsp;Id&nbsp;=&nbsp;o.Id,&nbsp;CustomName&nbsp;=&nbsp;cus.Name&nbsp;})

.ToList();

生成的Sql如下

SELECT  [o].[Id] AS [Id]&nbsp;,

[cus].[``Name``] AS [CustomName]

FROM [``Order``]&nbsp;o

Left JOIN [SQLSUGAR4XTEST].[dbo].[Custom]&nbsp;cus --生成的Sql就多了库名

ON (&nbsp;[o].[CustomId]&nbsp;=&nbsp;[cus].[Id]&nbsp;) WHERE (&nbsp;[o].[Id]&nbsp;=&nbsp;@Id0&nbsp;)

注意:上面的例子是SqlServer跨库查询的用法,不同的数据库跨库查询用法不一样,

更多用法: https://www.donet5.com/Home/Doc?typeId=2244

4.2跨库不同服务器

因为我们基础信息库是固定的,所以我们可以把基础信息库,同步到不同的服务器上,通过读分离的方式 ,那么每个业务服务器都会有一个

基础信息库了,然后在通过4.1的方式实现

我们可以通过CodeFirst创建业务库和表

https://www.donet5.com/Home/Doc?typeId=1207

一个业务库中的表对应多个集团,那我们设计表的时候肯定有个 OrgId或者租户ID进行数据表区别

我们需要将DbManger.bizDb进行修改,添加相应的表过滤器如果表中有OrgId的就可以添加过滤器

///&nbsp;<summary>

///&nbsp;获取业务库对象

///&nbsp;</summary>

public static ISqlSugarClient&nbsp;BizDb

{

get 

{

UserInfo&nbsp;user&nbsp;=&nbsp;GetUserInfo();``//获取用户数据库连接字符串信息

var configId&nbsp;=&nbsp;user.OrgId.ToString();``//集团ID(也可以叫租户ID)

if``(!Db.IsAnyConnection(configId)){

Db.AddConnection(``new ConnectionConfig()&nbsp;{

ConfigId&nbsp;=&nbsp;configId,

ConnectionString&nbsp;= "DataSource=" +&nbsp;user.Connection,

DbType&nbsp;=&nbsp;DbType.SqlServer,

IsAutoCloseConnection&nbsp;= true });

}

var result=Db.GetConnection(configId);

result.QueryFilter.Add(``new TableFilterItem<Order>(it&nbsp;=>&nbsp;it.OrgId==configId);``//添加表过滤器

//可以多个表

return result;

}

}

https://www.donet5.com/Home/Doc?typeId=1205

我们可以通过差异日志拿到数据更变记录,记录到日志,SAAS操作一些核心数据差异日志肯定少不了,安全系数高

db.Aop.OnDiffLogEvent&nbsp;=&nbsp;it&nbsp;=>

{

//操作前记录&nbsp;&nbsp;包含:&nbsp;字段描述&nbsp;列名&nbsp;值&nbsp;表名&nbsp;表描述

var editBeforeData&nbsp;=&nbsp;it.BeforeData;

//操作后记录&nbsp;&nbsp;&nbsp;包含:&nbsp;字段描述&nbsp;列名&nbsp;值&nbsp;&nbsp;表名&nbsp;表描述

var editAfterData&nbsp;=&nbsp;it.AfterData;

var sql&nbsp;=&nbsp;it.Sql;

var parameter&nbsp;=&nbsp;it.Parameters;

var data&nbsp;=&nbsp;it.BusinessData;``//这边会显示你传进来的对象

var time&nbsp;=&nbsp;it.Time;

var  diffType=it.DiffType;``//enum&nbsp;insert&nbsp;、update&nbsp;and&nbsp;delete

//Write&nbsp;logic

};

db.Insertable(``new Student()&nbsp;{&nbsp;Name&nbsp;= "beforeName" })

.EnableDiffLogEvent() //注意需要加上启用日志,可以传参数

.ExecuteReturnIdentity();

具体用法:https://www.donet5.com/Home/Doc?typeId=1204

SAAS架构用到技术基本上都是现有功能,我这一篇文章相当于一个汇总,本人也做了6年SAAS架构,至少目前的方案是成熟方案 ,并且有产品验证并且上线运营,如果有更好的建议可能发贴,或者直接找我沟通

源码下载: https://github.com/donet5/sqlsugar

Nuget: 安装SqlSugarCore