DB 仓储使用
本页介绍 Ape‑Volo‑Admin 中 SqlSugar 的仓储封装、连接路由、多租户与日志库切换、常用查询与分页、与事务/缓存的协作,以及性能与常见问题建议。
仓储构造与连接选择
位置:Ape.Volo.Repository/SugarHandler/SugarRepository.cs
csharp
/// <summary>
/// SqlSugar仓储
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public class SugarRepository<TEntity> : ISugarRepository<TEntity> where TEntity : class, new()
{
public SugarRepository(IUnitOfWork unitOfWork, IHttpUser httpUser)
{
var sqlSugarScope = unitOfWork.GetDbClient();
var logDbAttribute = typeof(TEntity).GetCustomAttribute<LogDataBaseAttribute>();
if (logDbAttribute != null)
{
SugarClient = sqlSugarScope.GetConnectionScope(AppSettings.GetValue<string>("System", "LogDataBase"));
return;
}
// 使用多租户
var useMultiTenant = AppSettings.GetValue<bool>("Tenant", "Enabled");
if (useMultiTenant)
{
var multiDbTenantAttribute = typeof(TEntity).GetCustomAttribute<MultiDbTenantAttribute>();
if (multiDbTenantAttribute != null)
{
if (httpUser.IsNotNull() && httpUser.TenantId > 0)
{
var tenants = sqlSugarScope.Queryable<Tenant>().WithCache(86400).ToList();
var tenant = tenants.FirstOrDefault(x => x.TenantId == httpUser.TenantId);
if (tenant != null)
{
var iTenant = sqlSugarScope.AsTenant();
if (!iTenant.IsAnyConnection(tenant.ConfigId))
{
var connectionString = tenant.ConnectionString;
if (tenant.DbType == DbType.Sqlite)
{
connectionString = "DataSource=" + Path.Combine(AppSettings.ContentRootPath, tenant.ConnectionString);
}
iTenant.AddConnection(TenantHelper.GetConnectionConfig(tenant.ConfigId, tenant.DbType.Value, connectionString));
}
SugarClient = iTenant.GetConnectionScope(tenant.ConfigId);
return;
}
}
}
}
SugarClient = sqlSugarScope;
}
public ISqlSugarClient SugarClient { get; }
}要点:
- 通过实体特性
LogDataBaseAttribute可将日志类实体路由至独立日志库连接。 - 开启多租户后,标注
MultiDbTenantAttribute的实体将按HttpUser.TenantId切换连接;未建立连接会按租户配置动态创建并加入。 - SQLite 连接串在多租户场景会拼接 ContentRoot 生成 DataSource 路径。
实体与映射(简要)
建议为表/列使用 SqlSugar 特性,确保结构与约束清晰:
csharp
[SugarTable("sys_user")]
public class User
{
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
public long Id { get; set; }
[SugarColumn(Length = 50, IsNullable = false, UniqueGroupNameList = new[] {"uk_username"})]
public string UserName { get; set; }
[SugarColumn(Length = 50, IsNullable = true)]
public string NickName { get; set; }
}如需代码优先建表/更新,请参考项目初始化流程;生产库建议显式迁移并做好备份与变更评审。
常用查询与 CRUD
仓储通常暴露 Table/Queryable 入口进行查询:
csharp
// 查询单条
var user = await Table.Where(x => x.UserName == userName).FirstAsync();
// 新增
await Table.InsertAsync(new User { UserName = "admin" });
// 更新
await Table.Where(x => x.Id == id).UpdateAsync(x => new User { NickName = nick });
// 删除(建议优先软删)
await Table.Where(x => x.Id == id).DeleteAsync();分页与联表示例:
csharp
// 分页
var page = await Table.WhereIF(!string.IsNullOrWhiteSpace(keyword), x => x.UserName.Contains(keyword))
.OrderBy(x => x.Id, OrderByType.Desc)
.ToPageListAsync(pageIndex, pageSize);
// 关联加载(避免 N+1)
var dict = await Table.Where(x => x.Name == name)
.Includes(x => x.DictDetails.OrderBy(y => y.DictSort).ToList())
.FirstAsync();提示:在高并发热点查询上,结合缓存 AOP 能显著降低数据库压力,详见“缓存”章节。
与事务/工作单元配合
- 简单场景:在方法上加
[UseTran]即可由 AOP 自动包裹事务。 - 复杂编排:使用
IUnitOfWork.BeginTran/CommitTran/RollbackTran显式控制边界,并尽量缩小事务范围。
参考文档:见《事务》。
多租户与日志库切换说明
- 多租户开关:
Tenant.Enabled = true时启用;标注MultiDbTenantAttribute的实体会按租户连接路由。 - 日志库:标注
LogDataBaseAttribute的实体通过System:LogDataBase指定的连接名访问。 - 动态连接:首次访问某租户时若无连接,会按租户配置
ConfigId/DbType/ConnectionString动态创建。
性能建议与注意事项
- 避免 N+1:使用
Includes或显式联表查询,一次性取回所需数据。 - 精准列投影:只选取需要的字段,降低行大小与网络传输。
- 合理分页顺序:建立对应索引,避免在大表无序分页;尽量走覆盖索引。
- 谨慎 in/Contains:大集合 in 查询可考虑临时表或分批查询。
- 事务内避免 IO:事务中不要发起 HTTP/消息队列/长耗时计算,缩短持锁时间。
- 连接与池化:合理配置最大连接数,监控慢查询与阻塞等待。
常见问题(FAQ)
- DateTime 时区/精度:明确存储为 UTC 或本地时间,统一序列化与显示;必要时设置列精度。
- Decimal 精度:为金额/比例等字段设定合适精度与小数位,避免精度丢失。
- SQLite 路径:在多租户场景下会拼接 ContentRoot,确认文件可写路径与权限。
- 大对象加载:避免一次性拉取大字段(如文本/二进制);必要时分页或延迟加载。

