9.4 仓储模式
9.4.1 什么是仓储
在领域层和数据映射层的中介,使用类似集合的接口来存取领域对象,实际上,仓储被用于领域对象在数据库上的操作(实体 Entity 和值对象 Value types)。一般来说,我们针对不同的实体(或聚合根 Aggregate Root)会创建相对应的仓储。
简单来说,仓储就是数据存取操作的载体,但不限定于数据库。
9.4.2 内置仓储
Furion
框架内置了一个数据库操作的仓储,方便大家拓展和集成:
关于依赖注入说明
目前能够被依赖注入解析服务的仓储有:
IRepository
IRepository<TEntity>
IRepository<TEntity, TDbContextLocator>
ISqlRepository
ISqlRepository<TDbContextLocator>
IMSRepository
IMSRepository<TMasterDbContextLocator>
IMSRepository<TMasterDbContextLocator,...TSlaveDbContextLocator8>
IDbRepository<TDbContextLocator>
还有两个私有仓储,也是所有仓储的基类(用于高级自定义开发)
IPrivateRepository<TEntity>
:所有实体仓储的基类IPrivateSqlRepository
:所有数据库操作的基类
除此之后的所有仓储只能通过 rep.Constraint<TRepository>()
进行约束创建,如,只读仓储:
var readRepository = rep.Constraint<IReadableRepository<TEntity>>();
9.4.2.1 非泛型超级仓储
IRepository
:默认非泛型仓储接口,支持切换到任何仓储EFCoreRepository
:默认非泛型仓储实现
9.4.2.2 泛型实体仓储
IRepository<TEntity>
:默认数据库实体仓储接口EFCoreRepository<TEntity>
:默认数据库实体仓储实现
9.4.2.3 泛型多数据库实体仓储
IRepository<TEntity, TDbContextLocator>
:任意数据库的实体仓储接口EFCoreRepository<TEntity, TDbContextLocator>
:任意数据库的实体仓储实现
9.4.2.4 Sql
操作仓储
ISqlRepository
:默认数据库Sql
操作仓储接口SqlRepository
:默认数据库Sql
操作仓储实现
9.4.2.5 多数据库 Sql
操作仓储
ISqlRepository<TDbContextLocator>
:任意数据库的Sql
操作仓储接口SqlRepository<TDbContextLocator>
:任意数据库的Sql
操作仓储实现
9.4.2.6 只读实体仓储(支持多库)
IReadableRepository<TEntity>
:默认数据库只读实体仓储接口IReadableRepository<TEntity, TDbContextLocator>
:多数据库只读实体仓储实现
9.4.2.7 只写实体仓储(支持多库)
IWritableRepository<TEntity>
:默认数据库只写实体仓储接口IWritableRepository<TEntity, TDbContextLocator>
:多数据库只写实体仓储实现
9.4.2.8 只允许新增实体仓储(支持多库)
IInsertableRepository<TEntity>
:默认数据库只允许新增的实体仓储接口IInsertableRepository<TEntity, TDbContextLocator>
:多数据库只允许新增的实体仓储实现
9.4.2.9 只允许更新实体仓储(支持多库)
IUpdateableRepository<TEntity>
:默认数据库只允许更新的实体仓储接口IUpdateableRepository<TEntity, TDbContextLocator>
:多数据库只允许更新的实体仓储实现
9.4.2.10 只允许删除实体仓储(支持多库)
IDeletableRepository<TEntity>
:默认数据库只允许删除的实体仓储接口IDeletableRepository<TEntity, TDbContextLocator>
:多数据库只允许删除的实体仓储实现
9.4.2.11 只允许拓展操作实体仓储(支持多库)
功能移除声明
该功能在 Furion 2.5.1 +
版本中已移除。此操作让很多不了解 EFCore
的开发者产生了很大的误解,不知何时新增或何时更新,故移除此功能。
IOperableRepository<TEntity>
:默认数据库只允许拓展操作实体仓储接口IOperableRepository<TEntity, TDbContextLocator>
:多数据库只允许拓展操作实体仓储实现
9.4.2.12 只允许 Sql
查询仓储(支持多库)
ISqlReaderRepository
:默认数据库只允许Sql
查询仓储接口ISqlReaderRepository<TDbContextLocator>
:多数据库只允许Sql
查询仓储实现
9.4.2.13 只允许 Sql
非查询仓储(支持多库)
ISqlExecutableRepository
:默认数据库只允许Sql
非查询仓储接口ISqlExecutableRepository<TDbContextLocator>
:多数据库只允许Sql
非查询仓储实现
9.4.2.14 读写分离仓储
IMSRepository
:最多支持 一主 7 从 仓储
9.4.2.15 定位器仓储
IDbRepository<TDbContextLocator>
:初始化特定数据库仓储
9.4.3 仓储使用
Furion
提供了非常多的方式创建仓储,目的是为了让大家可以在不同的场景中使用。
9.4.3.1 构造函数注入
private readonly IRepository<Person> _personRepository;
public FurionService(IRepository<Person> personRepository)
{
_personRepository = personRepository;
}
9.4.3.2 方法参数注入
public async Task<List<PersonDto>> GetAll([FromServices] IRepository<Person> repository, string keyword)
{
var persons = await repository.AsQueryable().ToListAsync();
return persons.Adapt<List<PersonDto>>();
}
9.4.3.3 Db.GetRepository
获取
// 非泛型仓储
var repository = Db.GetRepository();
// 泛型仓储
var repository = Db.GetRepository<Person>();
// Sql 仓储
var sqlRepository = Db.GetSqlRepository();
特别说明
不管采用哪种方式,Furion
都保证了仓储一次请求唯一性。同时 Db.GetRepository<TEntity>()
方式支持任何静态类中使用。
9.4.4 仓储高级用法
9.4.4.1 动态切换实体仓储
var userRepository = personRepository.Change<User>();
9.4.4.2 动态切换仓储类型
比如,读写分离/主从库仓储:
// 只读仓储
var readRepository = personRepository.Constraint<IReadableRepository<User>>();
// 只写仓储
var writeRepository = personRepository.Constraint<IWritableRepository<User>>();
小知识
.Constraint
支持切换任何仓储类型。
9.4.4.3 获取 Sql
操作仓储
var sqlRepository = repository.Sql();
9.4.5 多数据库操作
Furion
通过 DbContextLocator
数据库上下文定位器实现多种数据库操作,可以随意切换数据库
9.4.5.1 动态切换多个数据库
动态切换数据库
// 切换到 MSSQL 操作 Person表
var mssqlRepository = repository.Change<Person, MsSqlDbContextLocator>();
// 切换到 MySql 操作 Person表
var mysqlRepository = repository.Change<Person, MySqlDbContextLocator>();
// 切换到 Sqlite 操作 Person表
var sqliteRepository = repository.Change<Person, SqliteDbContextLocator>();
// 其他更多数据库一样的操作
另外任何仓储或实体配置都支持多个数据库同时操作
仓储方式
IRepository<Person, MsSqlDbContextLocator> mssqlRepository
ISqlRepository<MsSqlDbContextLocator> mssqlRepository;
动态 sql
方式
"select * from person".Change<MsSqlDbContextLocator>().SqlQuery();
实体配置方式
public class User:Entity<int, MsSqlDbContextLocator, MySqlDbContextLocator>
{
}
Sql
代理方式
[SqlFunction("funcName", DbContextLocator = typeof(MySqlDbContextLocator))]
int GetAge(int id);
Linq
中方式
[QueryableFunction("funcName","dbo", DbContextLocator = typeof(MySqlDbContextLocator))]
string GetName()=> throw Oops.Oh("不支持该数据库操作");
9.4.6 在后台任务中使用
由于 DbContext
默认注册为 Scoped
生存周期,所以在后台任务中使用 IServiceScopeFactory
获取所有服务,如:
public class JobService : BackgroundService
{
// 日志对象
private readonly ILogger<JobService> _logger;
// 服务工厂
private readonly IServiceScopeFactory _scopeFactory;
public JobService(ILogger<JobService> logger
, IServiceScopeFactory scopeFactory)
{
_logger = logger;
_scopeFactory = scopeFactory;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("写日志~~");
using (var scope = _scopeFactory.CreateScope())
{
var services = scope.ServiceProvider;
// 获取数据库上下文
var dbContext = Db.GetDbContext(services);
// 获取仓储
var respository = Db.GetRepository<Person>(services);
// 解析其他服务
var otherService = services.GetService<XXX>();
}
return Task.CompletedTask;
}
}
数据库操作注意
如果作用域中对数据库有任何变更操作,需手动调用 SaveChanges
或带 Now
结尾的方法。也可以使用 Scoped.CreateUow(handler)
代替。
9.4.7 自定义仓储
有些时候我们需要自定义仓储,拓展现有的仓储功能,可参考以下代码(含定位器仓储和默认仓储实现)
/// <summary>
/// 自定义仓储接口
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TDbContextLocator"></typeparam>
public interface IMyRepository<TEntity, TDbContextLocator> : IPrivateRepository<TEntity>
where TEntity : class, IPrivateEntity, new()
where TDbContextLocator : class, IDbContextLocator
{
/// <summary>
/// 自定义方法
/// </summary>
void MyMethod();
}
/// <summary>
/// 自定义仓储实现类
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TDbContextLocator"></typeparam>
public class MyRepository<TEntity, TDbContextLocator> : PrivateRepository<TEntity>, IMyRepository<TEntity, TDbContextLocator>, IScoped
where TEntity : class, IPrivateEntity, new()
where TDbContextLocator : class, IDbContextLocator
{
/// <summary>
/// 实现基类构造函数
/// </summary>
/// <param name="serviceProvider"></param>
public MyRepository(IServiceProvider serviceProvider)
: base(typeof(TDbContextLocator), serviceProvider)
{
}
/// <summary>
/// 自定义方法
/// </summary>
public void MyMethod()
{
throw new System.NotImplementedException();
}
}
/// <summary>
/// 默认数据库自定义仓储接口
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public interface IMyRepository<TEntity> : IMyRepository<TEntity, MasterDbContextLocator>
where TEntity : class, IPrivateEntity, new()
{
}
/// <summary>
/// 默认数据库自定义仓储实现
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public class MyRepository<TEntity> : MyRepository<TEntity, MasterDbContextLocator>, IMyRepository<TEntity>, IScoped
where TEntity : class, IPrivateEntity, new()
{
public MyRepository(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
}
9.4.8 反馈与建议
与我们交流
给 Furion 提 Issue。