我们公司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共同讨论学习;