fix: 统一逐租户上下文执行

This commit is contained in:
root
2026-01-30 01:04:51 +00:00
parent 41cfd2e2e8
commit cf697b3889
29 changed files with 1037 additions and 490 deletions

View File

@@ -18,6 +18,26 @@ public abstract class AppDbContext(
private readonly ICurrentUserAccessor? _currentUserAccessor = currentUserAccessor;
private readonly IIdGenerator? _idGenerator = idGenerator;
/// <summary>
/// 是否禁用软删除过滤器。
/// </summary>
/// <remarks>
/// 仅允许在少数系统任务/恢复场景中临时关闭,默认应保持开启。
/// </remarks>
protected bool IsSoftDeleteFilterDisabled { get; private set; }
/// <summary>
/// 临时禁用软删除过滤器(仅关闭软删除过滤,不影响租户过滤)。
/// </summary>
/// <returns>作用域对象,释放后恢复之前的过滤状态。</returns>
public IDisposable DisableSoftDeleteFilter()
{
var previous = IsSoftDeleteFilterDisabled;
IsSoftDeleteFilterDisabled = true;
return new SoftDeleteFilterScope(this, previous);
}
/// <summary>
/// 构建模型时应用软删除过滤器。
/// </summary>
@@ -179,7 +199,15 @@ public abstract class AppDbContext(
private void SetSoftDeleteFilter<TEntity>(ModelBuilder modelBuilder)
where TEntity : class, ISoftDeleteEntity
{
modelBuilder.Entity<TEntity>().HasQueryFilter(entity => entity.DeletedAt == null);
QueryFilterCombiner.Combine<TEntity>(modelBuilder, "soft_delete", entity => IsSoftDeleteFilterDisabled || entity.DeletedAt == null);
}
private sealed class SoftDeleteFilterScope(AppDbContext context, bool previous) : IDisposable
{
public void Dispose()
{
context.IsSoftDeleteFilterDisabled = previous;
}
}
/// <summary>

View File

@@ -0,0 +1,23 @@
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
namespace TakeoutSaaS.Infrastructure.Common.Persistence;
/// <summary>
/// 查询过滤器合并器:用于追加具名 QueryFilter避免覆盖已有过滤器。
/// </summary>
internal static class QueryFilterCombiner
{
/// <summary>
/// 为指定实体追加具名查询过滤器。
/// </summary>
/// <typeparam name="TEntity">实体类型。</typeparam>
/// <param name="modelBuilder">模型构建器。</param>
/// <param name="filterKey">过滤器键。</param>
/// <param name="filter">新增过滤器表达式。</param>
internal static void Combine<TEntity>(ModelBuilder modelBuilder, string filterKey, Expression<Func<TEntity, bool>> filter)
where TEntity : class
{
modelBuilder.Entity<TEntity>().HasQueryFilter(filterKey, filter);
}
}

View File

@@ -62,7 +62,7 @@ public abstract class TenantAwareDbContext(
private void SetTenantFilter<TEntity>(ModelBuilder modelBuilder)
where TEntity : class, IMultiTenantEntity
{
modelBuilder.Entity<TEntity>().HasQueryFilter(entity => entity.TenantId == CurrentTenantId);
QueryFilterCombiner.Combine<TEntity>(modelBuilder, "tenant", entity => entity.TenantId == CurrentTenantId);
}
/// <summary>
@@ -74,9 +74,30 @@ public abstract class TenantAwareDbContext(
foreach (var entry in ChangeTracker.Entries<IMultiTenantEntity>())
{
if (entry.State == EntityState.Added && entry.Entity.TenantId == 0 && tenantId != 0)
if (entry.State is EntityState.Detached or EntityState.Unchanged)
{
continue;
}
if (tenantId == 0)
{
if (entry.Entity.TenantId != 0)
{
throw new InvalidOperationException("未进入租户上下文,禁止写入 TenantId 不为 0 的多租户数据。");
}
continue;
}
if (entry.State == EntityState.Added && entry.Entity.TenantId == 0)
{
entry.Entity.TenantId = tenantId;
continue;
}
if (entry.Entity.TenantId != tenantId)
{
throw new InvalidOperationException("检测到跨租户写入,已阻止保存。");
}
}
}