EF core 实现读写分离解决方案
我们公司2019年web开发已迁移至.NET core,目前有部分平台随着用户量增加,单一数据库部署已经无法满足我们的业务需求,一直在寻找EF CORE读写分离解决方案,目前在各大技术论坛上还没找到很好的方案,根据之前找到的读写分离方案,综合目前EF core 的能力,自己编写了一套EF core实现mysql读写分离的解决方案,目前以应用到正式生产环境(Linux)中,日活跃用户20W,木有发现明显BUG,推荐个大家使用,部分代码参考文章(https://www.cnblogs.com/qtqq/p/6942312.html),废话不多说直接上代码:
一、读写分离,采用的是一主多从,主库进行数据写操作,从库进行数据读操作;对DbContext基类进行改造,构造函数传入读或写枚举;新建一个类SyDbContext继承DbContext基类;构造函数传入WriteAndRead枚举,用来区别是读库还是写库
using Microsoft.EntityFrameworkCore;
namespace Sykj.Repository
{
/// <summary>
/// 数据库上下文类
/// </summary>
public partial class SyDbContext : DbContext
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="options"></param>
public SyDbContext(WriteAndRead writeRead) : base(DbContextFactory.GetOptions(writeRead))
{
}
/// <summary>
/// 映射配置调用
/// </summary>
/// <param name="modelBuilder"></param>
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//应用映射配置
base.OnModelCreating(modelBuilder);
}
}
}
二、编写DbContextFactory工厂类,用于创建DbContext读/写实列(注意:DbContext在一个请求周期必须保证实例是唯一,所以编写一个CallContext类,先判断当前http请求线程是否有实例,没有则new一个,保证DbContext线程安全);masterConnectionString是主库连接实列,用于数据的写操作,slaveConnectionString是从库连接实列,用于数据的读操作,从库可以有多个,我们这里采用一主多从机制,随机分配从库策略(参数在配置文件进行设置,放在文章最后贴出代码)具体实现代码如下:
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Concurrent;
using System.Threading;
using Sykj.Infrastructure;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console; namespace Sykj.Repository
{
/// <summary>
/// DbContext工厂
/// </summary>
public class DbContextFactory
{
static Random r = new Random();
static int dbcount = ConfigurationManager.Configuration["DbCount"].ToInt(); /// <summary>
/// EF日志输出到Console
/// </summary>
static readonly LoggerFactory LoggerFactory = new LoggerFactory(new[] { new ConsoleLoggerProvider((_, __) => true, true) }); /// <summary>
/// 获取DbContext的Options
/// </summary>
/// <param name="writeRead"></param>
/// <returns></returns>
public static DbContextOptions<SyDbContext> GetOptions(WriteAndRead writeRead)
{
string masterConnectionString = ConfigurationManager.Configuration["ConnectionStrings:0:ConnectionString"]; //随机选择读数据库节点
var optionsBuilder = new DbContextOptionsBuilder<SyDbContext>();
if (writeRead == WriteAndRead.Read)
{
int i = r.Next(, dbcount);
string slaveConnectionString = ConfigurationManager.Configuration[string.Format("ConnectionStrings:{0}:ConnectionString_{0}", i)];
optionsBuilder.UseMySql(slaveConnectionString).UseLoggerFactory(LoggerFactory);
}
else
{
optionsBuilder.UseMySql(masterConnectionString).UseLoggerFactory(LoggerFactory);
}
return optionsBuilder.Options;
} /// <summary>
/// 创建ReadDbContext实例
/// </summary>
/// <returns></returns>
public static SyDbContext CreateReadDbContext()
{
//先从线程获取实例,保证线程安全
SyDbContext dbContext = (SyDbContext)CallContext.GetData("ReadDbContext");
if (dbContext == null)
{
if (dbcount==)//如果数据库数量为1,则不启用读写分离
{
dbContext = new SyDbContext(WriteAndRead.Write);
}
else
{
dbContext = new SyDbContext(WriteAndRead.Read);
}
CallContext.SetData("ReadDbContext", dbContext);
}
return dbContext;
} /// <summary>
/// 创建WriteDbContext实例
/// </summary>
/// <returns></returns>
public static SyDbContext CreateWriteDbContext()
{
//先从线程获取实例,保证线程安全
SyDbContext dbContext = (SyDbContext)CallContext.GetData("WriteDbContext");
if (dbContext == null)
{
dbContext = new SyDbContext(WriteAndRead.Write);
CallContext.SetData("WriteDbContext", dbContext);
}
return dbContext;
}
} /// <summary>
/// 读库/写库
/// </summary>
public enum WriteAndRead
{
Write,
Read
} /// <summary>
/// 从线程获取实例
/// </summary>
public class CallContext
{
static ConcurrentDictionary<string, AsyncLocal<object>> state = new ConcurrentDictionary<string, AsyncLocal<object>>(); public static void SetData(string name, object data) =>
state.GetOrAdd(name, _ => new AsyncLocal<object>()).Value = data; public static object GetData(string name) =>
state.TryGetValue(name, out AsyncLocal<object> data) ? data.Value : null;
}
}
using Microsoft.EntityFrameworkCore;
namespace Sykj.Repository
{
/// <summary>
/// 数据库上下文类
/// </summary>
public partial class SyDbContext : DbContext
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="options"></param>
public SyDbContext(WriteAndRead writeRead) : base(DbContextFactory.GetOptions(writeRead))
{
}
/// <summary>
/// 映射配置调用
/// </summary>
/// <param name="modelBuilder"></param>
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//应用映射配置
base.OnModelCreating(modelBuilder);
}
}
}
三、改造RepositoryBase仓储基类,具体代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Linq.Dynamic.Core;
namespace Sykj.Repository
{
/// <summary>
/// 仓储基类
/// </summary>
/// <typeparam name="T">实体类型</typeparam>
public abstract class RepositoryBase<T> : IRepository<T> where T : class
{
//定义数据访问上下文对象
private readonly Lazy<SyDbContext> _dbMaster = new Lazy<SyDbContext>(() => DbContextFactory.CreateWriteDbContext());
private readonly Lazy<SyDbContext> _dbSlave = new Lazy<SyDbContext>(() => DbContextFactory.CreateReadDbContext());
/// <summary>
/// 主库,写操作
/// </summary>
protected SyDbContext DbMaster => _dbMaster.Value;
/// <summary>
/// 从库,读操作
/// </summary>
protected SyDbContext DbSlave => _dbSlave.Value;
#region 同步
/// <summary>
/// 判断记录是否存在
/// </summary>
/// <param name="predicate">lambda表达式条件</param>
/// <returns></returns>
public bool IsExist(Expression<Func<T, bool>> predicate)
{
return DbSlave.Set<T>().Any(predicate);
}
/// <summary>
/// 新增实体
/// </summary>
/// <param name="entity">实体</param>
/// <param name="autoSave">是否立即执行保存</param>
/// <returns></returns>
public bool Add(T entity, bool autoSave = true)
{
int row = ;
DbMaster.Set<T>().Add(entity);
if (autoSave)
row = Save();
return (row > );
}
/// <summary>
/// 批量添加
/// </summary>
/// <param name="entities">实体列表</param>
/// <param name="autoSave">是否立即执行保存</param>
/// <returns></returns>
public bool AddRange(IEnumerable<T> entities, bool autoSave = true)
{
int row = ;
DbMaster.Set<T>().AddRange(entities);
if (autoSave)
row = Save();
return (row > );
}
/// <summary>
/// 更新实体
/// </summary>
/// <param name="entity">实体</param>
/// <param name="autoSave">是否立即执行保存</param>
public bool Update(T entity, bool autoSave = true)
{
int row = ;
DbMaster.Update(entity);
if (autoSave)
row = Save();
return (row > );
}
/// <summary>
/// 更新实体部分属性
/// </summary>
/// <param name="entity">实体</param>
/// <param name="autoSave">是否立即执行保存</param>
/// <param name="updatedProperties">要更新的字段</param>
/// <returns></returns>
public bool Update(T entity, bool autoSave = true, params Expression<Func<T, object>>[] updatedProperties)
{
int row = ;
//告诉EF Core开始跟踪实体的更改,
//因为调用DbContext.Attach方法后,EF Core会将实体的State值
//更改回EntityState.Unchanged,
DbMaster.Attach(entity);
if (updatedProperties.Any())
{
foreach (var property in updatedProperties)
{
//告诉EF Core实体的属性已经更改。将属性的IsModified设置为true后,
//也会将实体的State值更改为EntityState.Modified,
//这样就保证了下面SaveChanges的时候会将实体的属性值Update到数据库中。
DbMaster.Entry(entity).Property(property).IsModified = true;
}
}
if (autoSave)
row = Save();
return (row > );
}
/// <summary>
/// 更新实体部分属性,泛型方法
/// </summary>
/// <param name="entity">实体</param>
/// <param name="autoSave">是否立即执行保存</param>
/// <param name="updatedProperties">要更新的字段</param>
/// <returns></returns>
public bool Update<Entity>(Entity entity, bool autoSave = true, params Expression<Func<Entity, object>>[] updatedProperties) where Entity : class
{
int row = ;
//告诉EF Core开始跟踪实体的更改,
//因为调用DbContext.Attach方法后,EF Core会将实体的State值
//更改回EntityState.Unchanged,
DbMaster.Attach(entity);
if (updatedProperties.Any())
{
foreach (var property in updatedProperties)
{
//告诉EF Core实体的属性已经更改。将属性的IsModified设置为true后,
//也会将实体的State值更改为EntityState.Modified,
//这样就保证了下面SaveChanges的时候会将实体的属性值Update到数据库中。
DbMaster.Entry(entity).Property(property).IsModified = true;
}
}
if (autoSave)
row = Save();
return (row > );
}
/// <summary>
/// 批量更新实体
/// </summary>
/// <param name="entities">实体列表</param>
/// <param name="autoSave">是否立即执行保存</param>
public bool UpdateRange(IEnumerable<T> entities, bool autoSave = true)
{
int row = ;
DbMaster.UpdateRange(entities);
if (autoSave)
row = Save();
return (row > );
}
/// <summary>
/// 根据lambda表达式条件获取单个实体
/// </summary>
/// <param name="predicate">lambda表达式条件</param>
/// <returns></returns>
public T GetModel(Expression<Func<T, bool>> predicate)
{
return DbSlave.Set<T>().FirstOrDefault(predicate);
}
/// <summary>
/// 删除实体
/// </summary>
/// <param name="entity">要删除的实体</param>
/// <param name="autoSave">是否立即执行保存</param>
public bool Delete(T entity, bool autoSave = true)
{
int row = ;
DbMaster.Set<T>().Remove(entity);
if (autoSave)
row = Save();
return (row > );
}
/// <summary>
/// 批量删除
/// </summary>
/// <param name="T">对象集合</param>
/// <returns></returns>
public bool Delete(IEnumerable<T> entities)
{
DbMaster.Set<T>().RemoveRange(entities);
int row = DbMaster.SaveChanges();
return (row > );
}
/// <summary>
/// 批量删除
/// </summary>
/// <param name="T">对象集合</param>
/// <param name="autoSave">是否立即执行保存</param>
/// <returns></returns>
public bool Delete(IEnumerable<T> entities, bool autoSave = true)
{
int row = ;
DbMaster.Set<T>().RemoveRange(entities);
if (autoSave)
row = Save();
return (row > );
}
/// <summary>
/// 获取实体集合
/// </summary>
/// <returns></returns>
public virtual IQueryable<T> GetList()
{
return DbSlave.Set<T>().AsQueryable();
}
/// <summary>
/// 根据lambda表达式条件获取单个实体
/// </summary>
/// <param name="predicate">lambda表达式条件</param>
/// <returns></returns>
public virtual IQueryable<T> GetList(Expression<Func<T, bool>> predicate)
{
return DbSlave.Set<T>().Where(predicate);
}
/// <summary>
/// 根据lambda表达式条件获取实体集合
/// </summary>
/// <param name="top">前几条</param>
/// <param name="predicate">查询条件</param>
/// <param name="ordering">排序</param>
/// <param name="args">条件参数</param>
/// <returns></returns>
public virtual IQueryable<T> GetList(int top, string predicate, string ordering, params object[] args)
{
var result = DbSlave.Set<T>().AsQueryable();
if (!string.IsNullOrWhiteSpace(predicate))
result = result.Where(predicate, args);
if (!string.IsNullOrWhiteSpace(ordering))
result = result.OrderBy(ordering);
if (top > )
{
result = result.Take(top);
}
return result;
}
/// <summary>
/// 分页查询,返回实体对象
/// </summary>
/// <param name="pageIndex">当前页</param>
/// <param name="pageSize">页大小</param>
/// <param name="predicate">条件</param>
/// <param name="ordering">排序</param>
/// <param name="args">条件参数</param>
/// <returns></returns>
public virtual IQueryable<T> GetPagedList(int pageIndex, int pageSize, string predicate, string ordering, params object[] args)
{
var result = (from p in DbSlave.Set<T>()
select p).AsQueryable();
if (!string.IsNullOrWhiteSpace(predicate))
result = result.Where(predicate, args);
if (!string.IsNullOrWhiteSpace(ordering))
result = result.OrderBy(ordering);
return result.Skip((pageIndex - ) * pageSize).Take(pageSize);
}
/// <summary>
/// 获取记录总数
/// </summary>
/// <param name="predicate">查询条件</param>
/// <param name="args">条件参数</param>
/// <returns></returns>
public virtual int GetRecordCount(string predicate, params object[] args)
{
if (string.IsNullOrWhiteSpace(predicate))
{
return DbSlave.Set<T>().Count();
}
else
{
return DbSlave.Set<T>().Where(predicate, args).Count();
}
}
/// <summary>
/// 事务性保存 读库
/// </summary>
public int Save()
{
int result = DbMaster.SaveChanges();
return result;
}
#endregion
}
}
四、配置文件参数配置:
appsetting.json
{
"urls": "http://*:5009",
"ConnectionStrings": [
//主库,用于写操作
{
"ConnectionString": "Server=.;UserId=xxx;PassWord=xxx;Database=xx;Charset=utf8;"
},
//从库1,用于读操作可以有n个
{
"ConnectionString_1":"Server=.;UserId=xxx;PassWord=xxx;Database=xx;Charset=utf8;"
},
//从库2,用于读操作可以有n个
{
"ConnectionString_2":"Server=.;UserId=xxx;PassWord=xxx;Database=xxx;Charset=utf8;"
}
],
"DbCount": 2,//从库数量
"RedisConnectionString": "ip:端口,defaultdatabase=1",//Redis缓存服务器
"IsRedis": true,//是否启用Redis缓存
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
}
}
五、以上就是全部内容,如有疑问或发现bug请移步QQ群:855531299共同讨论学习;
源码地址:https://gitee.com/shangyakejiwenhua/sykj
EF core 实现读写分离解决方案的更多相关文章
- EF Core 实现读写分离的最佳方案
前言 公司之前使用Ado.net和Dapper进行数据访问层的操作, 进行读写分离也比较简单, 只要使用对应的数据库连接字符串即可. 而最近要迁移到新系统中,新系统使用.net core和EF Cor ...
- EntityFramework Core进行读写分离最佳实践方式,了解一下(二)?
前言 写过上一篇关于EF Core中读写分离最佳实践方式后,虽然在一定程度上改善了问题,但是在评论中有的指出更换到从数据库,那么接下来要进行插入此时又要切换到主数据库,同时有的指出是否可以进行底层无感 ...
- efcore在Saas系统下多租户零脚本分表分库读写分离解决方案
efcore在Saas系统下多租户零脚本分表分库读写分离解决方案 ## 介绍 本文ShardinfCore版本x.6.0.20+ 本期主角: - [`ShardingCore`](https://gi ...
- EntityFramework Core进行读写分离最佳实践方式,了解一下(一)?
前言 本来打算写ASP.NET Core MVC基础系列内容,看到有园友提出如何实现读写分离,这个问题提的好,大多数情况下,对于园友在评论中提出的问题,如果是值得深究或者大多数同行比较关注的问题我都会 ...
- Entity Framework Core 实现读写分离
在之前的版本中我们可用构造函数实现,其实现在的版本也一样,之前来构造连接字符串,现在相似,构造DbContextOptions<T> 代码如下: public SContext(Maste ...
- SQL Server、MySQL主从搭建,EF Core读写分离代码实现
一.SQL Server的主从复制搭建 1.1.SQL Server主从复制结构图 SQL Server的主从通过发布订阅来实现 1.2.基于SQL Server2016实现主从 新建一个主库&quo ...
- 【爬坑笔记】c# 如何通过EF Core读写sql server的类似double型字段
=============================================== 2019/8/31_第1次修改 ccb_warlock == ...
- 基于 EntityFramework 的数据库主从读写分离服务插件
基于 EntityFramework 的数据库主从读写分离服务插件 1. 版本信息和源码 1.1 版本信息 v1.01 beta(2015-04-07),基于 EF 6.1 开发,支持 EF 6.1 ...
- net Core 使用MyCat分布式数据库,实现读写分离
net Core 使用MyCat分布式数据库,实现读写分离 目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 MyCat2.0版本很快就发布了,关于MyCat的动态和一些问题, ...
随机推荐
- 关联规则挖掘--Apriori算法
- vim 复制
要复制到别的地方,用 "+y 来复制,注意是三个字符.gg"+yG 1.复制 1)单行复制 在命令模式下,将光标移动到将要复制的行处,按“yy”进行复制: 2)多行复制 在命令模式 ...
- 微信小程序自定义底部导航栏组件+跳转
微信小程序本来封装有底部导航栏,但对于想自定义样式和方法的开发者来说,这并不是很好. 参考链接:https://github.com/ljybill/miniprogram-utils/tree/ma ...
- android中使用Application
在android开发过程中,我们可能存储一些全局的变量,最好在正在app的任何一个activity或者service中都可以访问到,这时我们可以使用application. 我们的一个应用就叫appl ...
- RabbitMQ消息如何100%投递成功(六)
消息如何保障100%的投递成功? 什么是生产端的可靠性投递? 保障消息的成功发出 保障MQ节点的成功接收 发送端收到MQ节点(Broker)确认应答 完善的消息进行补偿机制(如网络问题没有返回确认应答 ...
- [CSP-S模拟测试]:礼物(数学)
题目传送门(内部题80) 输入格式 第一行输入一个正整数$n$. 第二行到第$n+1$行每行两个正整数$a_i$和$b_i$表示第$i$个礼物中包含$a_i$个红宝石和$b_i$个绿宝石. 输出格式 ...
- [洛谷P3938]:斐波那契(fibonacci)(数学)
题目传送门 题目描述 小$C$养了一些很可爱的兔子.有一天,小$C$突然发现兔子们都是严格按照伟大的数学家斐波那契提出的模型来进行繁衍:一对兔子从出生后第二个月起,每个月刚开始的时候都会产下一对小兔子 ...
- 疑难杂症——bash: /dev/null: Permission denied
描述 在使用 Devstack 的时候需要时常切换用户su stack,此时会触发错误: root@mickeyfan-dev:~# su stack bash: /dev/null: Permiss ...
- HAWQ技术总结
HAWQ技术总结: 1. 官网: http://hawq.incubator.apache.org/ 2. 特性 2.1 sql支持完善 ANSI SQL标准,OLAP扩展,标准JDBC/ODBC支持 ...
- python接口自动化:对外接口sign签名
签名参数sign生成的方法: 在接口开发过程中,一般通过时间戳+sign作为密匙加密传输 实现代码如下: #python实现sign签名 import hashlib,time class sign: ...