我们公司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 实现读写分离解决方案的更多相关文章

  1. EF Core 实现读写分离的最佳方案

    前言 公司之前使用Ado.net和Dapper进行数据访问层的操作, 进行读写分离也比较简单, 只要使用对应的数据库连接字符串即可. 而最近要迁移到新系统中,新系统使用.net core和EF Cor ...

  2. EntityFramework Core进行读写分离最佳实践方式,了解一下(二)?

    前言 写过上一篇关于EF Core中读写分离最佳实践方式后,虽然在一定程度上改善了问题,但是在评论中有的指出更换到从数据库,那么接下来要进行插入此时又要切换到主数据库,同时有的指出是否可以进行底层无感 ...

  3. efcore在Saas系统下多租户零脚本分表分库读写分离解决方案

    efcore在Saas系统下多租户零脚本分表分库读写分离解决方案 ## 介绍 本文ShardinfCore版本x.6.0.20+ 本期主角: - [`ShardingCore`](https://gi ...

  4. EntityFramework Core进行读写分离最佳实践方式,了解一下(一)?

    前言 本来打算写ASP.NET Core MVC基础系列内容,看到有园友提出如何实现读写分离,这个问题提的好,大多数情况下,对于园友在评论中提出的问题,如果是值得深究或者大多数同行比较关注的问题我都会 ...

  5. Entity Framework Core 实现读写分离

    在之前的版本中我们可用构造函数实现,其实现在的版本也一样,之前来构造连接字符串,现在相似,构造DbContextOptions<T> 代码如下: public SContext(Maste ...

  6. SQL Server、MySQL主从搭建,EF Core读写分离代码实现

    一.SQL Server的主从复制搭建 1.1.SQL Server主从复制结构图 SQL Server的主从通过发布订阅来实现 1.2.基于SQL Server2016实现主从 新建一个主库&quo ...

  7. 【爬坑笔记】c# 如何通过EF Core读写sql server的类似double型字段

    =============================================== 2019/8/31_第1次修改                       ccb_warlock == ...

  8. 基于 EntityFramework 的数据库主从读写分离服务插件

    基于 EntityFramework 的数据库主从读写分离服务插件 1. 版本信息和源码 1.1 版本信息 v1.01 beta(2015-04-07),基于 EF 6.1 开发,支持 EF 6.1 ...

  9. net Core 使用MyCat分布式数据库,实现读写分离

    net Core 使用MyCat分布式数据库,实现读写分离 目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 MyCat2.0版本很快就发布了,关于MyCat的动态和一些问题, ...

随机推荐

  1. (18)C++项目练习一(功能会不断扩展)--------【聊天工具】

    1.准备使用Qt和C++做一个远程(基于互联网的)聊天工具,需要实现以下功能 (1)多对多聊天功能 (2)文件传输功能 (3)注册.登录功能 (4)加好友.同意好友功能 (5)好友列表.黑名单功能(分 ...

  2. AC自动机再加强版

    AC自动机 拓扑排序优化,注意拓扑排序前要把所有入度为零的点都加进去 #include<bits/stdc++.h> using namespace std; #define maxn 1 ...

  3. 【洛谷P1310 表达式的值】

    题目链接 题目描述 对于1 位二进制变量定义两种运算: 运算的优先级是: 先计算括号内的,再计算括号外的. “× ”运算优先于“⊕”运算,即计算表达式时,先计算× 运算,再计算⊕运算.例如:计算表达式 ...

  4. qbzt day6 下午 模拟赛

    我太菜了 T2 给定一张有向图,每个点有点权.试找到一条路径,使得该路径上的点权最 大值减去点权最小值最大,问这个差最大是多少.   话说这个题第一个想到的思路是tarjan缩点+拓扑排序来着... ...

  5. firefox的group群组功能很好!

    ================================ /usr/share/themes中有一些主题,包括: adwaita: 阿德维塔, 不二论 anaconda等等. 计算机环境: f ...

  6. LeetCode 46——全排列

    1. 题目 2. 解答 给定一个序列,序列中的任意一个数字都可以作为全排列的最后一位.然后,其余位置元素的确定便是剩余元素的一个全排列,也就是一个子问题. 例子中 [1, 2, 3] 的全排列,最后一 ...

  7. Retrofit RestAdapter 配置说明

    RestAdapter.Builder builder = new RestAdapter.Builder();                    builder.setEndpoint(ip地址 ...

  8. day48—JavaScript键盘事件

    转行学开发,代码100天——2018-05-03 今天继续学习JavaScript事件基础之键盘事件. 键盘代号获取 keyCode 键盘事件:onkeydown onkeyup 如通过键盘上下左右按 ...

  9. 阶段1 语言基础+高级_1-3-Java语言高级_04-集合_04 数据结构_4_数据结构_链表

    查询慢,增删快. 绿色代表一条链 红色是另外一条链 .查询是从头开始查所以慢. 在300和55之间添加一个元素

  10. selenium验证码处理之cookie登录

    在实际测试中会经常见到登录操作需要验证码验证登录 常见验证有以下几种: 验证码登录 图片识别   图片滑块识别验证   4.简单验证码计算 针对上面的登录验证解决办法有以下几种: 1.让开发去掉验证码 ...