Skip to content

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,确认文件可写路径与权限。
  • 大对象加载:避免一次性拉取大字段(如文本/二进制);必要时分页或延迟加载。

版权所有 © 2021-2026 ApeVolo-Team