1.背景
在实际工作中,会有读写分离的场景。对于该类型场景,我们应该怎么处理了。最先考虑的肯定是配置不同的数据库连接,查询数据的就走 查询用的数据库A,增删改数据的就走 修改用的数据库B。专业点描述就是先将数据库配置发布订阅模式,实现了1主多从的模式,主数据库一般负责更新数据,从数据库会同步主数据库的数据过来。上面的A就是从数据库服务器,B就是主数据库服务器。
本文介绍在.Net 8.0下,结合EFCore在项目中如何配置Sql Server读写分离。解决思路是在DBContext中去修改数据库连接,在具体使用DBContext查询数据或者新增数据时,指定具体的数据库配置去查询数据。
2.操作
2.1 准备一个web api项目
这是我之前做的demo,我会以它为例子去讲解。
2.2 新增配置
打开配置文件appsettings.json,加入下列配置
"ConnectionStrings": { "WriteConnection": "Data Source=127.0.0.1;Initial Catalog=AdvancedCustomerDB_Init;Persist Security Info=True;User ID=sa;Password=*;Encrypt=False;TrustServerCertificate=true;", "ReadConnectionList": [ "Data Source=127.0.0.1;Initial Catalog=AdvancedCustomerDB_Init_1;Persist Security Info=True;User ID=sa;Password=*;Encrypt=False;TrustServerCertificate=true;", "Data Source=127.0.0.1;Initial Catalog=AdvancedCustomerDB_Init_2;Persist Security Info=True;User ID=sa;Password=*;Encrypt=False;TrustServerCertificate=true;" ] }
2.3 编写代码
关键类DbContextFactory,专门生成DBContext
using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using SimpleWebApi.Migration; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SimpleWebApi.Business.Service.Interface { public class DbContextFactory : IDbContextFactory { private DBConnectionOption _DBConnection = null; private DbContext _DbContext = null; public DbContextFactory(IOptionsMonitor<DBConnectionOption> optionsMonitor, DbContext dbContext) { _DBConnection = optionsMonitor.CurrentValue; _DbContext = dbContext; } public DbContext GetDbContext(WriteAndReadEnum writeAndRead) { switch (writeAndRead) { case WriteAndReadEnum.Write: ToWrite(); break; case WriteAndReadEnum.Read: ToRead(); break; default: break; } return _DbContext; } private void ToWrite() { string conn = _DBConnection.WriteConnection; //主库连接 this._DbContext = _DbContext.SetConnectionString(conn); } private void ToRead() { string conn = string.Empty; int Count = _DBConnection.ReadConnectionList.Count; int index=new Random().Next(0, Count); conn = _DBConnection.ReadConnectionList[index]; this._DbContext=_DbContext.SetConnectionString(conn); } } }
using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SimpleWebApi.Business.Service.Interface { public interface IDbContextFactory { public DbContext GetDbContext(WriteAndReadEnum writeAndRead); } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SimpleWebApi.Business.Service.Interface { public enum WriteAndReadEnum { Write, Read } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SimpleWebApi.Business.Service.Interface { /// <summary> /// 读写数据库的数据库连接 /// </summary> public class DBConnectionOption { public string WriteConnection { get; set; } public List<string> ReadConnectionList { get; set; } } }
using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SimpleWebApi.Migration { public static class DbContextExtension { public static DbContext SetConnectionString(this DbContext dbContext,string conn) { if (dbContext is AdvancedCustomerDbContext) { AdvancedCustomerDbContext context = (AdvancedCustomerDbContext)dbContext; return context.SetConnectionString(conn); } else { throw new Exception(); } } } }
2.4 改造原有代码
将BaseService文件,注入IDbContextFactory对象,并在每个方法中,加入数据操作的类型,比如是查询还是更新数据。
对于子类CommodityService等,里面的每个方法中,加入数据操作的类型,比如是查询还是更新数据。相关接口变动了参数的,也需要修改。此处不做具体介绍。
找到AdvancedCustomerDbContext。按照下图修改代码
打开Program.cs,加入下图代码
#region EFCore支持读写分离 builder.Services.AddTransient<IDbContextFactory, DbContextFactory>(); builder.Services.Configure<DBConnectionOption>(builder.Configuration.GetSection("ConnectionStrings")); #endregion
ApiController代码如下:
using AutoMapper; using Microsoft.AspNetCore.Mvc; using SimpleWebApi.Business.Service.Interface; using SimpleWebApi.Migration.Models; using System.Linq.Expressions; namespace SimpleWebApi.Controllers { [ApiController] [Route("api/[controller]/[action]")] public class ApiController : ControllerBase { private readonly ILogger<ApiController> _logger; private ICommodityService _comService; private ICompanyInfoService _companyService; private IMapper _mapper; public ApiController(ILogger<ApiController> logger, ICommodityService comService, ICompanyInfoService companyService, IMapper mapper) { _logger = logger; _comService = comService; _companyService = companyService; _mapper = mapper; } [HttpGet] public IEnumerable<CommodityDTO> GetCommodity(int Id,int type=1) { WriteAndReadEnum typeDB = (WriteAndReadEnum)type; Expression<Func<Commodity, bool>> funcWhere = null; funcWhere = a => a.Id == Id; var commodityList = _comService.Query(funcWhere,typeDB); List<CommodityDTO> list = new List<CommodityDTO>(); _mapper.Map<IQueryable<Commodity>, List<CommodityDTO>>(commodityList, list); return list; } [HttpGet] public CompanyInfoDTO GetCompanyInfo(int companyId) { var company = _companyService.GetCompany(companyId); CompanyInfoDTO dto = new CompanyInfoDTO(); _mapper.Map<CompanyInfo, CompanyInfoDTO>(company, dto); return dto; } [HttpPost] public bool AddCommodity(CommodityDTO companyDto) { Commodity company = new Commodity(); _mapper.Map<CommodityDTO, Commodity>(companyDto,company); var flag = _comService.AddCommodity(company); return flag; } } }
2.5 代码执行
A.测试写数据
使用接口/api/Api/AddCommodity 新增数据。这接口应该是写主数据库AdvancedCustomerDB_Init,从数据库AdvancedCustomerDB_Init_1和AdvancedCustomerDB_Init_2不会马上有数据库(我做了限制)
查询数据库。发现数据已经成功插入到主数据库AdvancedCustomerDB_Init,且从数据库暂时没这条数据。
A.测试读数据
使用读接口 /api/Api/GetCommodity 查询刚刚新增的那条Id=6的数据。(做了限制。此时从数据没这个数据)
可以明显看到,接口 没有返回数据吗,因为此时从数据库就是没这个数据的
修改参数type为0(1表示 从数据库查询,1表示 主数据库查询)
这么设置,是考虑到有时候数据查询要有即时性。所以直接指定从 主数据库查询数据。具体看实际情况的。
发现接口成功返回了数据。符合预期
3.结论
本文介绍了读写分离的设置。也是我自己理解的过程,我也上传了代码。请按需下载。至此,结束。