.Net 8.0之SQL Server读写分离的配置
最先考虑的肯定是配置不同的数据库连接,查询数据的就走 查询用的数据库A,增删改数据的就走 修改用的数据库B。专业点描述就是先将数据库配置发布订阅模式,实现了1主多从的模式,主数据库一般负责更新数据,从数据库会同步主数据库的数据过来。解决思路是在DBContext中去修改数据库连接,在具体使用DBContext查询数据或者新增数据时,指定具体的数据库配置去查询数据。将BaseService文件,注入
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.结论
本文介绍了读写分离的设置。也是我自己理解的过程,我也上传了代码。请按需下载。至此,结束。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)