我们公司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. scanf() 与 gets()--转载

    scanf( )函数和gets( )函数都可用于输入字符串,但在功能上有区别.若想从键盘上输入字符串"hi hello",则应该使用__gets__函数. gets可以接收空格:而 ...

  2. 添加环境变量(path)

    使用命令提示符((cmd)(批处理)(Batch)(.bat))添加环境变量 永久环境变量 命令提示符下修改 ==注意:要使用管理员身份运行cmd== set PATH=%PATH%;要添加的路径 r ...

  3. leetcode 142. 环形链表 II(c++)

    给定一个链表,返回链表开始入环的第一个节点. 如果链表无环,则返回 null. 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始). 如果 pos 是 - ...

  4. Understanding ECMAScript 6 阅读问题小记

    拖了一年说要看这本书,一直都没坚持下来,开个 bo 记录下觉得疑惑的问题,也算鞭策一下自己. 第一章 块级绑定 1. 第一章“块级绑定”下,说 const 变量如果绑定的是对象 Object,那么修改 ...

  5. java poi 导入导出Excel xsl xslx

    import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import  ...

  6. Spring学习01——HelloSpring

    这是一个spring入门demo: package com.su.test; public class HelloWorld { public void say(){ System.out.print ...

  7. 06 使用bbed修复update的数据--01

    场景1 表t3 SQL> select * from t3; ID NAME ---------- -------------------- aaa bbbb SQL> update t3 ...

  8. 【HANA系列】SAP HANA计算视图(calculation views)使用RANK报错

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[HANA系列]SAP HANA计算视图(cal ...

  9. 剑指offer--day03

    1.1题目:斐波那契数列:大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0).n<=39 1.2解题思路:斐波那契数列公式为: 这道题递归很好写 ...

  10. 解决django项目在ubuntu系统上无法安装mysqlclient

    首先我的项目是django2.0,python环境是3.5. 我们在本地开发完django项目了,在本地运行是成功的,然后我们把django项目放到服务器上,运行的时候就出错了. 如图: 我们都知道, ...