前言
公司之前使用Ado.net和Dapper进行数据访问层的操作, 进行读写分离也比较简单, 只要使用对应的数据库连接字符串即可. 而最近要迁移到新系统中,新系统使用.net core和EF Core进行数据访问. 所以趁着国庆假期拿出一两天时间研究了一下如何EF Core进行读写分离.
思路
根据园子里的Jeffcky大神的博客, 参考
EntityFramework Core进行读写分离最佳实践方式,了解一下(一)?
EntityFramework Core进行读写分离最佳实践方式,了解一下(二)?
最简单的思路就是使用手动切换EF Core上下文的连接, 即context.Database.GetDbConnection().ConnectionString = "xxx", 但必须要先创建上下文, 再关闭之前的连接, 才能进行切换
另一种方式是通过监听Diagnostic来将进行查询的sql切换到从库执行, 这种方式虽然可以实现无感知的切换操作, 但不能满足公司的业务需求. 在后台管理或其他对数据实时性要求比较高的项目里,查询操作也都应该走主库,而这种方式却会切换到从库去. 另一方面就是假若公司的库比较多,每种业务都对应了一个库, 每个库都对应了一种DbContext, 这种情况下, 要实现自动切换就变得很复杂了.
上面的两种方式都是从切换数据库连接入手,但是频繁的切换数据库连接势必会对性能造成影响. 我认为最理想的方式是要避免数据库连接的切换, 且能够适应多DbContext的情况, 在创建上下文实例时,就指定好是访问主库还是从库, 而不是在后期再进行数据库切换. 因此, 在上下文实例化时,就传入相应的数据库连接字符串, 这样一来DbContext的创建就需要交由我们自己来进行, 就不是由DI容器进行创建了. 同时仓储应该区分为只读和可读可写两种,以防止其他人对从库进行写操作.
实现
public interface IReadOnlyRepository<TEntity, TKey>
where TEntity : class, IEntity<TKey>
where TKey : IEquatable<TKey>
{}
public interface IRepository<TEntity, TKey> : IReadOnlyRepository<TEntity, TKey>
where TEntity : class, IEntity<TKey>
where TKey : IEquatable<TKey>
{}
IReadOnlyRepository接口是只读仓储接口,提供查询相关方法,IRepository接口是可读可写仓储接口,提供增删查改等方法, 接口的实现就那些东西这里就省略了.
public interface IRepositoryFactory
{
IRepository<TEntity, TKey> GetRepository<TEntity, TKey>(IUnitOfWork unitOfWork)
where TEntity : class, IEntity<TKey>
where TKey : IEquatable<TKey>;
IReadOnlyRepository<TEntity, TKey> GetReadOnlyRepository<TEntity, TKey>(IUnitOfWork unitOfWork)
where TEntity : class, IEntity<TKey>
where TKey : IEquatable<TKey>;
}
public class RepositoryFactory : IRepositoryFactory
{
public RepositoryFactory()
{
}
public IRepository<TEntity, TKey> GetRepository<TEntity, TKey>(IUnitOfWork unitOfWork)
where TEntity : class, IEntity<TKey>
where TKey : IEquatable<TKey>
{
return new Repository<TEntity, TKey>(unitOfWork);
}
public IReadOnlyRepository<TEntity, TKey> GetReadOnlyRepository<TEntity, TKey>(IUnitOfWork unitOfWork)
where TEntity : class, IEntity<TKey>
where TKey : IEquatable<TKey>
{
return new ReadOnlyRepository<TEntity, TKey>(unitOfWork);
}
}
RepositoryFactory提供仓储对象的实例化
public interface IUnitOfWork : IDisposable
{
public DbContext DbContext { get; }
/// <summary>
/// 获取只读仓储对象
/// </summary>
IReadOnlyRepository<TEntity, TKey> GetReadOnlyRepository<TEntity, TKey>()
where TEntity : class, IEntity<TKey>
where TKey : IEquatable<TKey>;
/// <summary>
/// 获取仓储对象
/// </summary>
IRepository<TEntity, TKey> GetRepository<TEntity, TKey>()
where TEntity : class, IEntity<TKey>
where TKey : IEquatable<TKey>;
int SaveChanges();
Task<int> SaveChangesAsync(CancellationToken cancelToken = default);
}
public class UnitOfWork : IUnitOfWork
{
private readonly IServiceProvider _serviceProvider;
private readonly DbContext _dbContext;
private readonly IRepositoryFactory _repositoryFactory;
private bool _disposed;
public UnitOfWork(IServiceProvider serviceProvider, DbContext context)
{
Check.NotNull(serviceProvider, nameof(serviceP