说到程序里面数据库管理,无非就是两件事情,一是数据库操作,对于数据库的操作,各种程序语言都有封装,也就是所谓的ORM框架,.net 方向一般用得比较多和就是.net framework和dapper,abp里还集成了NHibernate,另外就是连接字符串的管理,简单的应用直接用一个数据库连接字符串就可以了,但是对于大型的应用,比如有多租户概念的系统,比如有一些分库分表需求的设计系统,那么连接字符串的管理将是非常复杂和核心的内容。

  对于读写分离,大家应该比较熟悉,数据库层面,大型的关系型数据库都支持,这里的读写分离是指代码层面,针对DBA已经做好的数据库读写分离来管理数据库连接字符串。

  Abp基本框架提供了最基础的数据库连接字符串管理,zero项目实现了多租户的数据库连接管理,即把每个租户的连接字符串存储在租户里面,对于每一个Uow操作,都会找租户的连接字符串,如果找到,就使用,没有找到,向上层找默认的连接字符串。代码如下:

/// <summary>
/// Implements <see cref="IDbPerTenantConnectionStringResolver"/> to dynamically resolve
/// connection string for a multi tenant application.
/// </summary>
public class DbPerTenantConnectionStringResolver : DefaultConnectionStringResolver, IDbPerTenantConnectionStringResolver
{
/// <summary>
/// Reference to the session.
/// </summary>
public IAbpSession AbpSession { get; set; } private readonly ICurrentUnitOfWorkProvider _currentUnitOfWorkProvider;
private readonly ITenantCache _tenantCache; /// <summary>
/// Initializes a new instance of the <see cref="DbPerTenantConnectionStringResolver"/> class.
/// </summary>
public DbPerTenantConnectionStringResolver(
IAbpStartupConfiguration configuration,
ICurrentUnitOfWorkProvider currentUnitOfWorkProvider,
ITenantCache tenantCache)
: base(configuration)
{
_currentUnitOfWorkProvider = currentUnitOfWorkProvider;
_tenantCache = tenantCache; AbpSession = NullAbpSession.Instance;
} public override string GetNameOrConnectionString(ConnectionStringResolveArgs args)
{
if (args.MultiTenancySide == MultiTenancySides.Host)
{
return GetNameOrConnectionString(new DbPerTenantConnectionStringResolveArgs(null, args));
} return GetNameOrConnectionString(new DbPerTenantConnectionStringResolveArgs(GetCurrentTenantId(), args));
} public virtual string GetNameOrConnectionString(DbPerTenantConnectionStringResolveArgs args)
{
if (args.TenantId == null)
{
//Requested for host
return base.GetNameOrConnectionString(args);
} var tenantCacheItem = _tenantCache.Get(args.TenantId.Value);
if (tenantCacheItem.ConnectionString.IsNullOrEmpty())
{
//Tenant has not dedicated database
return base.GetNameOrConnectionString(args);
} return tenantCacheItem.ConnectionString;
} protected virtual int? GetCurrentTenantId()
{
return _currentUnitOfWorkProvider.Current != null
? _currentUnitOfWorkProvider.Current.GetTenantId()
: AbpSession.TenantId;
}
}

  那么我们要改造的地方其他就可以参照这个来管理连接字符串

  还有一个问题,就是我们怎么让框架知道我们使用的是读库还是写库呢?

  Abp里面,公开给用户控制Uow的,就是UnitOfWorkAttribute装饰器,增加一个读库还是写库的标识IsReadDb,在UnitOfWorkOptions类里面也要加对应的属性,那么我们在构造UnitOfWorkOptions类的时候,可以把属性装饰器里面的IsReadDb属性赋值给UnitOfWorkOptions,再获取DbContext方法的时候,把此参数传入Uow连接字符串管理,在连接字符串管理里面,判断此参数的值,确定数据库字符串选择。

  主要代码:

/// <summary>
/// Unit of work options.
/// </summary>
public class UnitOfWorkOptions
{
// ...... /// <summary>
/// 自定义:设置是否是读库
/// </summary>
public bool IsReadDb { get; set; }
} [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Interface)]
public class UnitOfWorkAttribute : Attribute
{
// ...... /// <summary>
/// 自定义:设置是否是读库
/// </summary>
public bool IsReadDb { get; set; }
public UnitOfWorkOptions CreateOptions()
{
return new UnitOfWorkOptions
{
IsTransactional = IsTransactional,
IsolationLevel = IsolationLevel,
Timeout = Timeout,
Scope = Scope,
IsReadDb = IsReadDb
};
}
}

  在获取DbContext方法的时候,传递数据库读写参数

public static class UnitOfWorkExtensions
{
public static TDbContext GetDbContext<TDbContext>(this IActiveUnitOfWork unitOfWork, MultiTenancySides? multiTenancySide = null, string name = null)
where TDbContext : DbContext
{
if (unitOfWork == null)
{
throw new ArgumentNullException("unitOfWork");
} if (!(unitOfWork is EfCoreUnitOfWork))
{
throw new ArgumentException("unitOfWork is not type of " + typeof(EfCoreUnitOfWork).FullName, "unitOfWork");
} return (unitOfWork as EfCoreUnitOfWork).GetOrCreateDbContext<TDbContext>(multiTenancySide, name, unitOfWork.Options.IsReadDb);
}
} public virtual TDbContext GetOrCreateDbContext<TDbContext>(MultiTenancySides? multiTenancySide = null, string name = null,bool isReadDb = false)
where TDbContext : DbContext
{
var concreteDbContextType = _dbContextTypeMatcher.GetConcreteType(typeof(TDbContext)); var connectionStringResolveArgs = new ConnectionStringResolveArgs(multiTenancySide);
connectionStringResolveArgs["DbContextType"] = typeof(TDbContext);
connectionStringResolveArgs["DbContextConcreteType"] = concreteDbContextType;
connectionStringResolveArgs["IsReadDb"] = isReadDb;
var connectionString = ResolveConnectionString(connectionStringResolveArgs);
}

  最终,参照Abp连接字符串的管理,代码如下:

public class DbPerTenantConnectionStringResolver : DefaultConnectionStringResolver, IDbPerTenantConnectionStringResolver
{
/// <summary>
/// Reference to the session.
/// </summary>
public IAbpSession AbpSession { get; set; } private readonly ICurrentUnitOfWorkProvider _currentUnitOfWorkProvider; /// <summary>
/// Initializes a new instance of the <see cref="DbPerTenantConnectionStringResolver"/> class.
/// </summary>
public DbPerTenantConnectionStringResolver(
IAbpStartupConfiguration configuration,
ICurrentUnitOfWorkProvider currentUnitOfWorkProvider)
: base(
configuration)
{
_currentUnitOfWorkProvider = currentUnitOfWorkProvider;
AbpSession = NullAbpSession.Instance;
} public override string GetNameOrConnectionString(ConnectionStringResolveArgs args)
{
if (args.MultiTenancySide == MultiTenancySides.Host)
{
return GetNameOrConnectionString(new DbPerTenantConnectionStringResolveArgs(null, args));
} return GetNameOrConnectionString(new DbPerTenantConnectionStringResolveArgs(GetCurrentTenantId(), args));
} public virtual string GetNameOrConnectionString(DbPerTenantConnectionStringResolveArgs args)
{
if (args.TenantId == null)
{
//Requested for host
return base.GetNameOrConnectionString(args);
} var tenantCacheItem = Rpc.Call<Sys_TenantQueryDto>("CommonService.CommonServiceServiceAppService.GetTenantInfo", args.TenantId);
if(tenantCacheItem == null)
{
return base.GetNameOrConnectionString(args);
} if(Convert.ToBoolean(args["IsReadDb"]))
{
if(!string.IsNullOrEmpty(tenantCacheItem.ReadConnectionString))
{
return tenantCacheItem.ReadConnectionString;
}
else
{
return base.GetNameOrConnectionString(args);
}
}
else
{
if (!string.IsNullOrEmpty(tenantCacheItem.ConnectionString))
{
return tenantCacheItem.ConnectionString;
}
else
{
return base.GetNameOrConnectionString(args);
}
}
} protected virtual int? GetCurrentTenantId()
{
return _currentUnitOfWorkProvider.Current != null
? _currentUnitOfWorkProvider.Current.GetTenantId()
: AbpSession.TenantId;
}
}

  使用的时候,在类或者方法上,增加Uow属性装饰器上定义参数即可

  补充说明:可以参照这种方式,自定义的扩展,比如每一个DbContext自定义连接字符串,我们可以在自己的租户管理表中添加属性,自定义数据库连接字符串选择逻辑。

企业级工作流解决方案(十三)--集成Abp和ng-alain--数据库读写分离的更多相关文章

  1. SpringCloud微服务实战——搭建企业级开发框架(二十七):集成多数据源+Seata分布式事务+读写分离+分库分表

    读写分离:为了确保数据库产品的稳定性,很多数据库拥有双机热备功能.也就是,第一台数据库服务器,是对外提供增删改业务的生产服务器:第二台数据库服务器,主要进行读的操作. 目前有多种方式实现读写分离,一种 ...

  2. 企业级工作流解决方案(十五)--集成Abp和ng-alain--Abp其他改造

    配置功能增强 Abp定义了各种配置接口,但是没有定义这些配置数据从哪里来,但是管理配置数据对于一个应用程序来说,是必不可少的一件事情. .net的配置数据管理,一般放在Web.config文件或者Ap ...

  3. 企业级工作流解决方案(十四)--集成Abp和ng-alain--自动化脚本

    对于.net方向,做过自动化的,应该没有人不熟悉msbuild吧,非常强大的代码编译工具,.net平台的编译工作都是交给他来完成的,包括.net core的命令,本质上都是调用msbuild来执行的 ...

  4. 企业级工作流解决方案(十)--集成Abp和ng-alain--权限系统

    权限系统 应用系统离不开权限控制,权限中心不一定能抽象出所有的业务场景,这里定义的权限系统不一定能够满足所有的场景,但应该可以满足多数的业务需求. Abp的zero项目也定义了权限相关的表,但里面很多 ...

  5. 企业级工作流解决方案(十二)--集成Abp和ng-alain--用户身份认证与权限验证

    多租户 如果系统需要支持多租户,那么最好事先定义好多租户的存储部署方式,Abp提供了几种方式,根据需要选择,每一个用户身份认证与权限验证都需要完全的隔离 这里设计的权限数据全部存储在缓存中,每个租户单 ...

  6. 企业级工作流解决方案(十一)--集成Abp和ng-alain--权限系统服务

    权限系统主要定义为管理员增删改查权限数据,直接读取数据库,权限系统服务主要定义为供其他系统调用的权限验证接口,定义为两个不同的微服务. 权限系统有一个特点,数据变动比较小,数据量本身并不是很大,访问量 ...

  7. 企业级工作流解决方案(六)--微服务消息处理模型之与Abp集成

    身份认证传递 对于Abp比较熟悉的朋友应该对他里面的用户身份认证比较熟悉,他是通过实现微软提供的权限认证方式实现的,用户登录身份信息存储在System.Security.Claims.ClaimsPr ...

  8. 企业级工作流解决方案(八)--微服务Tcp消息传输模型之服务端处理

    服务端启动 服务端启动主要做几件事情,1. 从配置文件读取服务配置(主要是服务监听端口和编解码配置),2. 注册编解码器工厂,3. 启动dotnetty监听端口,4. 读取配置文件,解析全局消息处理模 ...

  9. 企业级工作流解决方案(九)--微服务Tcp消息传输模型之客户端处理

    客户端启动 客户端启动主要做三件事情,1. 从配置文件读取服务调用配置,存储到全局对象中.2. 指定客户端编解码器工厂.3. 预连接,即预先建立与服务端的通信Chanel. [DependsOn(ty ...

随机推荐

  1. 【微信小程序】:小程序,新场景

    前言: 我们频繁进入的地方,是场景. 手机.是场景:浏览器.是场景.事实上,微信,也是场景-- 微信要做的是占领很多其它用户时间.占领很多其它应用场景.占领很多其它服务入口.这是商业本质想去垄断要做的 ...

  2. Windows XP时代终结:假设你还在用它怎么办

    2014 年 4 月 8 日.是Windows XP退休的日子. 4 月 8 日过后,Windows XP 的安全性更新将停止了. watermark/2/text/aHR0cDovL2Jsb2cuY ...

  3. 【cl】oracle之Sequence

    sequence:序列 首先要有create sequence或者create any sequence权限: 1.语法如下: create sequence  cltest---序列名 increm ...

  4. Java 异步转同步 ListenableFuture in Guava

    ListenableFuture的说明 并发编程是一个难题,但是一个强大而简单的抽象可以显著的简化并发的编写.出于这样的考虑,Guava 定义了 ListenableFuture接口并继承了JDK c ...

  5. php执行运算符

    php执行运算符 简介 php 支持一个执行运算符:反引号(``).反引号(``)位于键盘Tab键左上方.php 将尝试将反引号中的内容作为外壳命令来执行,并将其输出信息返回(例如,可以赋给一个变量而 ...

  6. nyoj--44--子串和(动态规划)

    子串和 时间限制:5000 ms  |  内存限制:65535 KB 难度:3 描述 给定一整型数列{a1,a2...,an},找出连续非空子串{ax,ax+1,...,ay},使得该子序列的和最大, ...

  7. AJAX复习笔记

    AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术.通过在后台与服务器进行少量数据交换,AJAX 可况下更新以使网页实现异步更新. 工作原理: AJAX是基于现有的Internet ...

  8. MAVEN学习笔记之Maven生命周期和插件简介(3)

    MAVEN学习笔记之Maven生命周期和插件简介(3) clean compile site三套生命周期相互独立. clean pre-clean 执行清理前的工作 clean 清理上一次构建生成的所 ...

  9. Oracle下rman备份和还原到数据库任意一个时间点

    Rman备份为物理备份,启用rman备份必须开启数据库归档,开启归档后相当于给数据库加了一层双保险.Rman备份主要备份数据库的数据文件,控制文件,归档日志. RMAN 备份 一. 检查数据库是否启用 ...

  10. Python内置数据结构之元组tuple

    1. Python序列之元组:不可修改的序列 元组其实跟列表差不多,也是存一组数,只不是它一旦创建,便不能像列表式的增删改,只能查,切片,所以元组又叫只读列表. 元组用圆括号括起(这是通常采用的做法) ...