fix: 统一逐租户上下文执行
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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("检测到跨租户写入,已阻止保存。");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user