在.NET项目里使用Entity Framework Core实现读写分离,核心思路是为读操作和写操作分别配置不同的数据库上下文,读操作指向从数据库实例,写操作指向主数据库实例,以此分担单库压力,提升系统整体性能。

读写分离的基本实现思路
读写分离的核心是将数据库的读请求和写请求路由到不同的数据库实例,通常主库负责处理写操作、事务操作,从库负责处理查询操作。在Entity Framework Core中,我们可以通过定义两个不同的数据库上下文类,分别配置主库和从库的连接字符串,再根据操作类型选择对应的上下文实例。
1. 准备主从数据库连接字符串
首先在项目的配置文件appsettings.json中添加主库和从库的连接字符串,示例如下:
{
"ConnectionStrings": {
"MasterDb": "Server=192.168.0.1;Database=TestDb;User Id=sa;Password=123456;",
"SlaveDb": "Server=192.168.0.2;Database=TestDb;User Id=sa;Password=123456;"
}
}
2. 定义读写分离的数据库上下文
分别创建读上下文和写上下文,继承DbContext,在构造函数中接收对应的连接字符串配置:
using Microsoft.EntityFrameworkCore;
// 写操作上下文,指向主库
public class WriteDbContext : DbContext
{
public WriteDbContext(DbContextOptions<WriteDbContext> options) : base(options)
{
}
// 定义数据实体集合
public DbSet<User> Users { get; set; }
}
// 读操作上下文,指向从库
public class ReadDbContext : DbContext
{
public ReadDbContext(DbContextOptions<ReadDbContext> options) : base(options)
{
}
public DbSet<User> Users { get; set; }
}
// 用户实体类
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
3. 注册上下文到依赖注入容器
在Program.cs中配置服务,将读写上下文分别注册到依赖注入容器,读取配置文件中的连接字符串:
var builder = WebApplication.CreateBuilder(args);
// 注册写上下文,使用主库连接字符串
builder.Services.AddDbContext<WriteDbContext>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("MasterDb"));
});
// 注册读上下文,使用从库连接字符串
builder.Services.AddDbContext<ReadDbContext>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("SlaveDb"));
});
var app = builder.Build();
4. 业务层使用不同的上下文
在具体的业务代码中,根据操作类型注入对应的上下文,写操作使用WriteDbContext,读操作使用ReadDbContext:
public class UserService
{
private readonly WriteDbContext _writeDbContext;
private readonly ReadDbContext _readDbContext;
// 构造函数注入两个上下文
public UserService(WriteDbContext writeDbContext, ReadDbContext readDbContext)
{
_writeDbContext = writeDbContext;
_readDbContext = readDbContext;
}
// 写操作:新增用户
public async Task AddUserAsync(User user)
{
_writeDbContext.Users.Add(user);
await _writeDbContext.SaveChangesAsync();
}
// 读操作:查询所有用户
public async Task<List<User>> GetAllUsersAsync()
{
return await _readDbContext.Users.ToListAsync();
}
}
进阶优化方案
1. 多从库负载均衡
如果项目有多个从库实例,可以将从库连接字符串配置为数组,每次读操作时随机选择一个从库连接,实现简单的负载均衡:
// 从库连接字符串数组
var slaveConnections = builder.Configuration.GetSection("SlaveConnections").Get<string[]>();
// 随机选择一个从库连接
var randomSlaveConnection = slaveConnections[new Random().Next(slaveConnections.Length)];
builder.Services.AddDbContext<ReadDbContext>(options =>
{
options.UseSqlServer(randomSlaveConnection);
});
2. 基于特性的读写路由
可以通过自定义特性标记业务方法,实现自动路由读写上下文,减少业务代码的重复逻辑。首先定义读写标记特性:
[AttributeUsage(AttributeTargets.Method)]
public class ReadOperationAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Method)]
public class WriteOperationAttribute : Attribute { }
然后通过AOP或者中间件识别方法上的特性,自动选择对应的上下文实例,避免手动注入两个上下文。
注意事项
- 主从数据库之间会存在数据同步延迟,如果写操作后立刻要求读取最新数据,建议直接使用写上下文查询,避免读取到旧数据。
- 事务操作必须全部使用写上下文,因为事务只能在主库上执行,从库不支持写事务。
- 从库的查询结果默认是只读的,不要尝试在从库上下文上执行修改操作,否则会抛出异常。
- 如果使用的是云数据库服务,部分云厂商的从库可能不支持某些复杂的查询语法,需要提前验证兼容性。
读写分离方案需要根据项目的实际业务场景调整,数据量较小、读写压力低的场景不需要强行使用读写分离,避免增加系统复杂度。
Entity_Framework_Core.NET读写分离数据库上下文修改时间:2026-06-15 06:36:34