diff --git a/src/Infrastructure.EntityFramework/Auth/Repositories/EmergencyAccessRepository.cs b/src/Infrastructure.EntityFramework/Auth/Repositories/EmergencyAccessRepository.cs
index ac6ebbe04c..3beb682721 100644
--- a/src/Infrastructure.EntityFramework/Auth/Repositories/EmergencyAccessRepository.cs
+++ b/src/Infrastructure.EntityFramework/Auth/Repositories/EmergencyAccessRepository.cs
@@ -147,16 +147,23 @@ public class EmergencyAccessRepository : Repository
public async Task DeleteManyAsync(ICollection emergencyAccessIds)
{
- using (var scope = ServiceScopeFactory.CreateScope())
- {
- var dbContext = GetDatabaseContext(scope);
- var rangeToRemove = from ea in dbContext.EmergencyAccesses
- where emergencyAccessIds.Contains(ea.Id)
- select ea;
- dbContext.EmergencyAccesses.RemoveRange(rangeToRemove);
+ using var scope = ServiceScopeFactory.CreateScope();
+ var dbContext = GetDatabaseContext(scope);
+ var rangeToRemove = from ea in dbContext.EmergencyAccesses
+ where emergencyAccessIds.Contains(ea.Id)
+ select ea;
+ dbContext.EmergencyAccesses.RemoveRange(rangeToRemove);
- await dbContext.SaveChangesAsync();
- }
+ var granteeIds = rangeToRemove
+ .Where(ea => ea.Status == EmergencyAccessStatusType.Confirmed)
+ .Where(ea => ea.GranteeId.HasValue)
+ .Select(ea => ea.GranteeId!.Value) // .Value is safe here due to the Where above
+ .Distinct();
+
+ await dbContext.UserBumpManyAccountRevisionDatesAsync(
+ [.. granteeIds]
+ );
+
+ await dbContext.SaveChangesAsync();
}
-
}
diff --git a/src/Sql/dbo/Auth/Stored Procedures/EmergencyAccess_DeleteManyById.sql b/src/Sql/dbo/Auth/Stored Procedures/EmergencyAccess_DeleteManyById.sql
index b4c97ba6ea..ca536b30c1 100644
--- a/src/Sql/dbo/Auth/Stored Procedures/EmergencyAccess_DeleteManyById.sql
+++ b/src/Sql/dbo/Auth/Stored Procedures/EmergencyAccess_DeleteManyById.sql
@@ -1,9 +1,22 @@
CREATE PROCEDURE [dbo].[EmergencyAccess_DeleteManyById]
- @EmergencyAccessIds [dbo].[GuidIdArray] READONLY
+ @EmergencyAccessIds [dbo].[GuidIdArray] READONLY
AS
BEGIN
SET NOCOUNT ON
+ -- track GranteeIds for bumping revision date prior to deletion
+ DECLARE @GranteeIds AS TABLE (UserId UNIQUEIDENTIFIER)
+
+ -- this matches the logic in User_BumpAccountRevisionDateByEmergencyAccessGranteeId
+ INSERT INTO @GranteeIds
+ (UserId)
+ SELECT DISTINCT GranteeId
+ FROM
+ [dbo].[EmergencyAccess] EA
+ WHERE EA.Id IN (SELECT Id
+ FROM @EmergencyAccessIds
+ WHERE EA.[Status] = 2 )
+
DECLARE @BatchSize INT = 100
-- Delete EmergencyAccess Records
@@ -13,11 +26,17 @@ BEGIN
DELETE TOP(@BatchSize) EA
FROM
[dbo].[EmergencyAccess] EA
- INNER JOIN
+ INNER JOIN
@EmergencyAccessIds EAI ON EAI.Id = EA.Id
SET @BatchSize = @@ROWCOUNT
-
END
+
+ -- Bump AccountRevisionDate for affected users after deletions
+ Exec [dbo].[User_BumpManyAccountRevisionDates]
+ (
+ SELECT [UserId]
+ FROM @GranteeIds
+ )
END
GO
\ No newline at end of file
diff --git a/test/Infrastructure.IntegrationTest/Auth/Repositories/EmergencyAccessRepositoryTests.cs b/test/Infrastructure.IntegrationTest/Auth/Repositories/EmergencyAccessRepositoryTests.cs
index a91a983aa9..9877e8f84c 100644
--- a/test/Infrastructure.IntegrationTest/Auth/Repositories/EmergencyAccessRepositoryTests.cs
+++ b/test/Infrastructure.IntegrationTest/Auth/Repositories/EmergencyAccessRepositoryTests.cs
@@ -48,7 +48,7 @@ public class EmergencyAccessRepositoriesTests
/// All 3 records are then deleted in a single call to DeleteManyAsync.
///
[DatabaseTheory, DatabaseData]
- public async Task DeleteManyAsync_DeletesMultipleGranteeRecords(
+ public async Task DeleteManyAsync_DeletesMultipleGranteeRecords_UpdatesUserRevisionDates(
IUserRepository userRepository,
IEmergencyAccessRepository emergencyAccessRepository)
{
@@ -61,7 +61,7 @@ public class EmergencyAccessRepositoriesTests
SecurityStamp = "stamp",
});
- var granteeUser1 = await userRepository.CreateAsync(new User
+ var confirmedGranteeUser1 = await userRepository.CreateAsync(new User
{
Name = "Test Grantee User 1",
Email = $"test+grantee{Guid.NewGuid()}@email.com",
@@ -88,7 +88,7 @@ public class EmergencyAccessRepositoriesTests
var confirmedEmergencyAccess = await emergencyAccessRepository.CreateAsync(new EmergencyAccess
{
GrantorId = grantorUser.Id,
- GranteeId = granteeUser1.Id,
+ GranteeId = confirmedGranteeUser1.Id,
Status = EmergencyAccessStatusType.Confirmed,
});
@@ -110,7 +110,20 @@ public class EmergencyAccessRepositoriesTests
await emergencyAccessRepository.DeleteManyAsync([confirmedEmergencyAccess.Id, invitedEmergencyAccess.Id, acceptedEmergencyAccess.Id]);
// Assert
- var emergencyAccess = await emergencyAccessRepository.GetManyDetailsByGrantorIdAsync(grantorUser.Id);
- Assert.Empty(emergencyAccess);
+ // ensure Grantor records deleted
+ var grantorEmergencyAccess = await emergencyAccessRepository.GetManyDetailsByGrantorIdAsync(grantorUser.Id);
+ Assert.Empty(grantorEmergencyAccess);
+
+ // ensure Grantee records deleted
+ foreach (User grantee in (List)[confirmedGranteeUser1, granteeUser2, granteeUser3])
+ {
+ var granteeEmergencyAccess = await emergencyAccessRepository.GetManyDetailsByGranteeIdAsync(grantee.Id);
+ Assert.Empty(granteeEmergencyAccess);
+ }
+
+ // Only the Status.Confirmed grantee's AccountRevisionDate should be updated
+ var updatedGrantee = await userRepository.GetByIdAsync(confirmedGranteeUser1.Id);
+ Assert.NotNull(updatedGrantee);
+ Assert.NotEqual(updatedGrantee.AccountRevisionDate, confirmedGranteeUser1.AccountRevisionDate);
}
}
diff --git a/util/Migrator/DbScripts/2026-01-23_00_AddDeleteManyEmergencyAccess.sql b/util/Migrator/DbScripts/2026-01-23_00_AddDeleteManyEmergencyAccess.sql
index 40083f63a9..1c4a9c92b6 100644
--- a/util/Migrator/DbScripts/2026-01-23_00_AddDeleteManyEmergencyAccess.sql
+++ b/util/Migrator/DbScripts/2026-01-23_00_AddDeleteManyEmergencyAccess.sql
@@ -1,9 +1,22 @@
CREATE OR ALTER PROCEDURE [dbo].[EmergencyAccess_DeleteManyById]
- @EmergencyAccessIds [dbo].[GuidIdArray] READONLY
+ @EmergencyAccessIds [dbo].[GuidIdArray] READONLY
AS
BEGIN
SET NOCOUNT ON
+ -- track GranteeIds for bumping revision date prior to deletion
+ DECLARE @GranteeIds AS TABLE (UserId UNIQUEIDENTIFIER)
+
+ -- this matches the logic in User_BumpAccountRevisionDateByEmergencyAccessGranteeId
+ INSERT INTO @GranteeIds
+ (UserId)
+ SELECT DISTINCT GranteeId
+ FROM
+ [dbo].[EmergencyAccess] EA
+ WHERE EA.Id IN (SELECT Id
+ FROM @EmergencyAccessIds
+ WHERE EA.[Status] = 2 )
+
DECLARE @BatchSize INT = 100
-- Delete EmergencyAccess Records
@@ -13,11 +26,17 @@ BEGIN
DELETE TOP(@BatchSize) EA
FROM
[dbo].[EmergencyAccess] EA
- INNER JOIN
+ INNER JOIN
@EmergencyAccessIds EAI ON EAI.Id = EA.Id
SET @BatchSize = @@ROWCOUNT
-
END
+
+ -- Bump AccountRevisionDate for affected users after deletions
+ Exec [dbo].[User_BumpManyAccountRevisionDates]
+ (
+ SELECT [UserId]
+ FROM @GranteeIds
+ )
END
GO
\ No newline at end of file