From 2e761771c4412160a9aedf5a9e053c509da5c0db Mon Sep 17 00:00:00 2001 From: ahmet-cetinkaya Date: Fri, 31 Jul 2026 12:31:18 +0300 Subject: [PATCH] fix(multi-tenancy): stamp tenant id on bulk AddRange writes MultiTenancyInterceptor only handled single Add operations. An AddRangeOperation reports OperationType.AddRange with a null CurrentDocument, so bulk writes were skipped and tenant entities reached the database with a default (empty) tenant id. Unwrap AddRangeOperation.CurrentDocuments and stamp every document with the same null/default rule used for single Adds. The single-Add path and its OperationType.Add guard are unchanged, so Update/Delete/Replace are still skipped and an explicitly set tenant id is never overwritten. --- .../MultiTenancy/MultiTenancyInterceptor.cs | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/Core/MultiTenancy/MultiTenancyInterceptor.cs b/src/Core/MultiTenancy/MultiTenancyInterceptor.cs index 60d5c34..1055d1c 100644 --- a/src/Core/MultiTenancy/MultiTenancyInterceptor.cs +++ b/src/Core/MultiTenancy/MultiTenancyInterceptor.cs @@ -22,16 +22,36 @@ public override ValueTask SavingChangesAsync(VaultInterceptorContext context, Ca foreach (var operation in context.Operations) { - if (operation.OperationType is OperationType.Add && operation.CurrentDocument is TInterface entity) + // An AddRange operation carries its batch in CurrentDocuments (its CurrentDocument is null and its + // OperationType is AddRange), so without unwrapping it the whole batch reaches the database without a + // tenant id. Single Adds are stamped the same way through their CurrentDocument. + if (operation is AddRangeOperation addRangeOperation) { - var documentTenantId = tenantIdGetter(entity); - if (documentTenantId is null || documentTenantId.Equals(default(TTenantId))) + foreach (var document in addRangeOperation.CurrentDocuments) { - tenantIdSetter(entity, tenantId.Value); + StampIfUnset(document); } } + else if (operation.OperationType is OperationType.Add && operation.CurrentDocument is TInterface entity) + { + StampIfUnset(entity); + } } return ValueTask.CompletedTask; + + void StampIfUnset(object? document) + { + if (document is not TInterface tenantEntity) + { + return; + } + + var documentTenantId = tenantIdGetter(tenantEntity); + if (documentTenantId is null || documentTenantId.Equals(default(TTenantId))) + { + tenantIdSetter(tenantEntity, tenantId.Value); + } + } } }