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(1, 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)//如果数据库数量为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 = 0;

            DbMaster.Set<T>().Add(entity);

            if (autoSave)

                row = Save();

            return (row > 0);

        }



        /// <summary>

        /// 批量添加

        /// </summary>

        /// <param name="entities">实体列表</param>

        /// <param name="autoSave">是否立即执行保存</param>

        /// <returns></returns>

        public bool AddRange(IEnumerable<T> entities, bool autoSave = true)

        {

            int row = 0;

            DbMaster.Set<T>().AddRange(entities);

            if (autoSave)

                row = Save();

            return (row > 0);

        }



        /// <summary>

        /// 更新实体

        /// </summary>

        /// <param name="entity">实体</param>

        /// <param name="autoSave">是否立即执行保存</param>

        public bool Update(T entity, bool autoSave = true)

        {

            int row = 0;

            DbMaster.Update(entity);

            if (autoSave)

                row = Save();

            return (row > 0);

        }



        /// <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 = 0;

            //告诉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 > 0);

        }



        /// <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 = 0;

            //告诉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 > 0);

        }



        /// <summary>

        /// 批量更新实体

        /// </summary>

        /// <param name="entities">实体列表</param>

        /// <param name="autoSave">是否立即执行保存</param>

        public bool UpdateRange(IEnumerable<T> entities, bool autoSave = true)

        {

            int row = 0;

            DbMaster.UpdateRange(entities);

            if (autoSave)

                row = Save();

            return (row > 0);

        }



        /// <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 = 0;

            DbMaster.Set<T>().Remove(entity);

            if (autoSave)

                row = Save();

            return (row > 0);

        }



        /// <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 > 0);

        }



        /// <summary>

        /// 批量删除

        /// </summary>

        /// <param name="T">对象集合</param>

        /// <param name="autoSave">是否立即执行保存</param>

        /// <returns></returns>

        public bool Delete(IEnumerable<T> entities, bool autoSave = true)

        {

            int row = 0;

            DbMaster.Set<T>().RemoveRange(entities);

            if (autoSave)

                row = Save();

            return (row > 0);

        }



        /// <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 > 0)

            {

                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 - 1) * 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   

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值