You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

171 lines
7.9 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// SqlSugar 实体仓储
/// </summary>
/// <typeparam name="T">实体类型,使用泛型让一个类可以操作所有表</typeparam>
public class SqlSugarRepository<T> : SimpleClient<T>, ISqlSugarRepository<T> where T : class, new()
{
/// <summary>
/// 构造函数 - 仓储初始化时自动执行租户库切换逻辑
/// 【对比 Ruoyi】Ruoyi 使用 MyBatis-Plus 的 @Mapper 注解 + @Autowired 注入,
/// 而 Admin.NET 使用泛型仓储 + 构造函数自动切库,开发者无需关心连接切换
/// </summary>
public SqlSugarRepository()
{
// 获取 SqlSugar 的多租户管理器(类似 Spring 的 DataSource 路由抽象)
// SqlSugarSetup.ITenant 是静态属性,存储了所有数据库连接的引用
var iTenant = SqlSugarSetup.ITenant; // App.GetRequiredService<ISqlSugarClient>().AsTenant();
// 默认连接主库(类似 Ruoyi 的 master 数据源)
base.Context = iTenant.GetConnectionScope(SqlSugarConst.MainConfigId);
// 【特性机制】C# 的 Attribute特性类似于 Java 的注解,用于标记类的元信息
// 若实体贴有多库特性,则返回指定库连接
// TenantAttribute 是 Admin.NET 自定义特性,标记该实体属于哪个租户库
if (typeof(T).IsDefined(typeof(TenantAttribute), false))
{
base.Context = iTenant.GetConnectionScopeWithAttr<T>();
return;
}
// 若实体贴有日志表特性,则返回日志库连接
// LogTableAttribute 标记日志表,查询时自动切换到日志库
if (typeof(T).IsDefined(typeof(LogTableAttribute), false))
{
if (iTenant.IsAnyConnection(SqlSugarConst.LogConfigId))
base.Context = iTenant.GetConnectionScope(SqlSugarConst.LogConfigId);
return;
}
// 若实体贴有系统表特性,则返回默认库连接
// SysTableAttribute 标记系统表(如用户、角色等),始终在主库查询
if (typeof(T).IsDefined(typeof(SysTableAttribute), false))
return;
// 【多租户实现】从 HTTP 请求头获取租户 ID类似 Ruoyi 的 tenant_id 参数)
// Ruoyi 多租户mybatis-plus: tenant: { mode: 1 } 自动注入 tenant_id
// Admin.NET 多租户:手动从 Header 获取,优先级:请求头 > 用户登录信息
var tenantId = App.HttpContext?.Request.Headers[ClaimConst.TenantId].FirstOrDefault();
if (tenantId == SqlSugarConst.MainConfigId) return;
else if (string.IsNullOrWhiteSpace(tenantId))
{
// 若未贴任何表特性或当前未登录或是默认租户Id则返回默认库连接
// App.User 是当前登录用户的信息,类似于 Spring Security 的 SecurityContextHolder
tenantId = App.User?.FindFirst(ClaimConst.TenantId)?.Value;
if (string.IsNullOrWhiteSpace(tenantId) || tenantId == SqlSugarConst.MainConfigId) return;
}
// 根据租户Id切换库连接 为空则返回默认库连接
// SysTenantService 管理所有租户的数据库连接映射
var sqlSugarScopeProviderTenant = App.GetRequiredService<SysTenantService>().GetTenantDbConnectionScope(long.Parse(tenantId));
if (sqlSugarScopeProviderTenant == null) return;
base.Context = sqlSugarScopeProviderTenant;
}
#region 分表操作
// 【分表机制】SqlSugar 内置分表支持,类似 ShardingSphere 但更轻量
// 适用场景:数据量过亿级别,按时间/地区分表存储
/// <summary>
/// 分表插入(单条)
/// 【对比 Ruoyi】Ruoyi 需手动配置 ShardingSphere 分片规则SqlSugar 只需调用 SplitTable() 方法
/// </summary>
public async Task<bool> SplitTableInsertAsync(T input)
{
return await base.AsInsertable(input).SplitTable().ExecuteCommandAsync() > 0;
}
/// <summary>
/// 分表插入(批量)
/// 【语法说明】async/await 是 C# 的异步编程模式,等同于 Java 的 CompletableFuture
/// Task<bool> 表示返回一个异步结果,类似 Future<Void> 但更强大
/// </summary>
public async Task<bool> SplitTableInsertAsync(List<T> input)
{
return await base.AsInsertable(input).SplitTable().ExecuteCommandAsync() > 0;
}
/// <summary>
/// 分表更新
/// 【ORM 差异】SqlSugar 的 AsUpdateable 类似 MyBatis-Plus 的 UpdateWrapper
/// 但语法更链式流畅AsUpdateable(entity).SplitTable().ExecuteCommandAsync()
/// </summary>
public async Task<bool> SplitTableUpdateAsync(T input)
{
return await base.AsUpdateable(input).SplitTable().ExecuteCommandAsync() > 0;
}
public async Task<bool> SplitTableUpdateAsync(List<T> input)
{
return await base.AsInsertable(input).SplitTable().ExecuteCommandAsync() > 0;
}
/// <summary>
/// 分表删除
/// 【语法说明】base.Context.Deleteable() 链式调用,类似于 MyBatis-Plus 的 lambdaQuery
/// 这种 Builder 模式让 SQL 构建更直观,避免 XML 配置文件
/// </summary>
public async Task<bool> SplitTableDeleteableAsync(T input)
{
return await base.Context.Deleteable(input).SplitTable().ExecuteCommandAsync() > 0;
}
public async Task<bool> SplitTableDeleteableAsync(List<T> input)
{
return await base.Context.Deleteable(input).SplitTable().ExecuteCommandAsync() > 0;
}
/// <summary>
/// 分表查询(单条)
/// 【语法说明】Expression<Func<T, bool>> 是 C# 的 Lambda 表达式类型
/// 等同于 Java 的 Function<T, Boolean>,用于构建查询条件
/// 例如p => p.Name == "张三" 会被翻译成 WHERE name = '张三'
/// </summary>
public Task<T> SplitTableGetFirstAsync(Expression<Func<T, bool>> whereExpression)
{
return base.AsQueryable().SplitTable().FirstAsync(whereExpression);
}
/// <summary>
/// 分表判断是否存在
/// 【对比 Ruoyi】MyBatis-Plus 用 count(*) > 0SqlSugar 用 AnyAsync() 更语义化
/// </summary>
public Task<bool> SplitTableIsAnyAsync(Expression<Func<T, bool>> whereExpression)
{
return base.Context.Queryable<T>().Where(whereExpression).SplitTable().AnyAsync();
}
/// <summary>
/// 分表查询(全部)
/// 【语法说明】Context.Queryable<T>() 是 SqlSugar 的查询入口
/// 链式调用Queryable -> Where -> SplitTable -> ToListAsync
/// 类似 MyBatis-Plus 的 query().lambdaQuery().list()
/// </summary>
public Task<List<T>> SplitTableGetListAsync()
{
return Context.Queryable<T>().SplitTable().ToListAsync();
}
public Task<List<T>> SplitTableGetListAsync(Expression<Func<T, bool>> whereExpression)
{
return Context.Queryable<T>().Where(whereExpression).SplitTable().ToListAsync();
}
/// <summary>
/// 分表查询(指定表名)
/// 【高级用法】可以显式指定查询哪些分表,类似 ShardingSphere 的只读表
/// </summary>
public Task<List<T>> SplitTableGetListAsync(Expression<Func<T, bool>> whereExpression, string[] tableNames)
{
return Context.Queryable<T>().Where(whereExpression).SplitTable(t => t.InTableNames(tableNames)).ToListAsync();
}
#endregion 分表操作
}