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的动态和一些问题, ...
随机推荐
- Bazinga
Bazinga Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total Sub ...
- uploadify上传插件参数的一些设置
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- telnet测试端口是否打开?
如何测试远程服务器的某个端口是否打开? 例如: telnet 192.168.1.1 5000 返回: Trying 127.0.0.1...Connected to 127.0.0.1.Escape ...
- 嵌入式Linux之虚拟内存地址空间布局(Virtual Memory Space)
虚拟内存地址空间 Linux内核属于微内核的范畴,内核控制计算机的硬件资源,运行在特权模式:用户态应用程序运行在普通用户模式,无法直接访问硬件资源,必须依托于内核提供的资源,如CPU资源.Memory ...
- Intellij IDEA中使用Debug调试详解
转载:https://www.linuxidc.com/Linux/2017-09/146772.htm Intellij IDEA中使用Debug调试详解 Debug用来追踪代码的运行流程,通常 ...
- CSAR——Channel-wise and Spatial Feature Modulation Network for Single Image Super-Resolution
1. 摘要 CNN 中的特征包含着不同类型的信息,它们对图像重建的贡献也不一样.然而,现在的大多数 CNN 模型却缺少对不同信息的辨别能力,因此也就限制了模型的表示容量. 另一方面,随着网络的加深,来 ...
- (转)websphere内存溢出,手动导出was的phd和javacore文件
原文:https://www.cnblogs.com/zhengbn/p/3499700.html 1:服务器-->服务器类型-->WebSphere Application Server ...
- Linux基础—saltstack运维工具学习
一.saltstack简介 1.saltstack是什么 系统管理员日常会进行大量的重复性操作,例如安装软件,修改配置文件,创建用户,批量执行命令等,如果主机数量庞大,单靠人工维护实在让人难以忍受. ...
- SpringBoot错误经验
1.在application.properties 添加 debug=true,可以看见项目的执行流程有助于调bug 2.如果错误显示端口号被占用 cmd 步骤1 查看端口号应用情况:netstat ...
- 【MM系列】SAP MM中物料帐下修改物料的价格
公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[MM系列]SAP 物料帐下修改物料的价格 ...