核心接口源码
// framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/ICurrentTenant.cs
public interface ICurrentTenant
{
bool IsAvailable { get; } // 当前租户 ID 是否为 null
Guid? Id { get; } // 当前租户 ID,null 表示 Host
string? Name { get; } // 当前租户名称
IDisposable Change(Guid? id, string? name = null);
// Change 返回 IDisposable,using 块结束后自动恢复上下文
}// framework/src/Volo.Abp.MultiTenancy.Abstractions/Volo/Abp/MultiTenancy/IMultiTenant.cs
public interface IMultiTenant
{
Guid? TenantId { get; }
}IMultiTenant 是实体标记接口。任何实现了它的实体,仓储操作会自动加上 WHERE TenantId = @currentId。
租户切换的实现原理
CurrentTenant.Change 的实现使用 AsyncLocal 存储当前租户 ID,确保同一次请求内共享、跨请求隔离:
// CurrentTenant.cs(简化)
public class CurrentTenant : ICurrentTenant, ITransientDependency
{
private readonly ICurrentTenantAccessor _accessor;
public Guid? Id => _accessor.Current?.TenantId;
public IDisposable Change(Guid? id, string? name = null)
{
var original = _accessor.Current;
_accessor.Current = new TenantInfo(id, name);
return new DisposeAction(() => _accessor.Current = original); // 恢复原租户
}
}DisposeAction 在 using 块结束时自动还原,这是"切换-用完即恢复"模式的关键。
自动过滤的底层
回顾 RepositoryBase.ApplyDataFilters:
if (typeof(IMultiTenant).IsAssignableFrom(typeof(TOtherEntity)))
{
var tenantId = CurrentTenant.Id;
query = (TQueryable)query.WhereIf(
DataFilter.IsEnabled<IMultiTenant>(), // 检查过滤是否启用
e => ((IMultiTenant)e!).TenantId == tenantId
);
}DataFilter.IsEnabled<IMultiTenant>() 默认返回 true。可以通过 DataFilter.Disable<IMultiTenant>() 临时禁用:
// 管理员查看所有租户数据
using (DataFilter.Disable<IMultiTenant>())
{
var allOrders = await _orderRepo.GetListAsync(); // 不加 TenantId 过滤
}连接字符串解析
// IConnectionStringResolver.cs — 连接串解析器
// 默认实现:所有租户使用同一个连接串(共享数据库模式)
// 扩展实现:按租户 ID 返回不同的连接串(独立数据库模式)在独立数据库模式中,每个租户的 Tenant 表里存储了各自的连接串,解析器根据当前 ICurrentTenant.Id 查找对应连接串。
解析器的扩展架构
ABP 的租户解析使用"贡献者"模式:
// 内置解析器(按优先级排列)
// 1. DomainTenantResolveContributor — 从域名解析(sub.yourapp.com)
// 2. HeaderTenantResolveContributor — 从请求头解析(X-Tenant-Id)
// 3. CookieTenantResolveContributor — 从 Cookie 解析
// 4. RouteTenantResolveContributor — 从路由参数解析
// 5. QueryStringTenantResolveContributor — 从 URL 参数解析自定义解析:
Configure<AbpTenantResolveOptions>(options =>
{
options.Contributors.Add(new MyCustomTenantResolveContributor());
});实战:三选一的数据隔离方案
// 方案 1:共享数据库(默认)——最简单
// 所有租户共用表,每行数据记录 TenantId
// 配置:不需要额外配置
// 适合:B2B 小规模 SaaS
// 方案 2:独立 Schema——中等隔离
// 同一数据库,每个租户一个 Schema,表结构相同
// 配置:重写 IConnectionStringResolver
// 方案 3:独立数据库——最高隔离
// 每个租户独立的数据库实例
// 配置:Tenant 实体存储连接串,IConnectionStringResolver 按 TenantId 返回不同串
夜雨聆风