perf(templateengine): alarm-override, folder-delete and template-delete checks read lean template projections (review 2026-10-21 04/P6, 08/P1)

InstanceService.SetAlarmOverrideAsync and TemplateDeletionService move to the
no-tracking, body-free GetAllTemplatesForAnalysisAsync; TemplateFolderService
counts folder contents from GetTemplateStubsAsync.
This commit is contained in:
Joseph Doherty
2026-10-22 06:25:16 -04:00
parent 7d11a1db8d
commit c7774b7ec2
7 changed files with 54 additions and 29 deletions
@@ -243,7 +243,9 @@ public class InstanceService
// against the template's direct alarms alone would miss them, silently
// accepting an override for a non-existent name or bypassing the lock
// rule for a composed alarm. Mirrors SetAttributeOverrideAsync.
var allTemplates = await _repository.GetAllTemplatesAsync(cancellationToken);
// The resolver reads member names and lock flags only, so the no-tracking,
// script-body-free analysis projection suffices (review 2026-10-21 04/P6, 08/P1).
var allTemplates = await _repository.GetAllTemplatesForAnalysisAsync(cancellationToken);
var resolvedAlarm = TemplateResolver
.ResolveAllMembers(instance.TemplateId, allTemplates)
.FirstOrDefault(m => m.MemberType == "Alarm" && m.CanonicalName == alarmCanonicalName);
@@ -53,7 +53,10 @@ public class TemplateDeletionService
}
// Check 2: Child templates reference it as parent. Split derived vs. regular.
var allTemplates = await _repository.GetAllTemplatesAsync(cancellationToken);
// Reads ids, names, parent/derived/owner edges and composition rows only — the
// no-tracking, script-body-free analysis projection carries all of them
// (review 2026-10-21 04/P6, 08/P1).
var allTemplates = await _repository.GetAllTemplatesForAnalysisAsync(cancellationToken);
var inheritors = allTemplates.Where(t => t.ParentTemplateId == templateId).ToList();
var regularChildren = inheritors.Where(t => !t.IsDerived).ToList();
var derivatives = inheritors.Where(t => t.IsDerived).ToList();
@@ -79,7 +82,7 @@ public class TemplateDeletionService
}
// Check 3: Other templates compose it directly.
// Read the Compositions navigation already loaded by GetAllTemplatesAsync
// Read the Compositions navigation already loaded by GetAllTemplatesForAnalysisAsync
// rather than issuing one GetCompositionsByTemplateIdAsync round-trip per
// template — this is the same source TemplateService
// .DeleteTemplateAsync uses for the equivalent check.
@@ -232,7 +232,9 @@ public class TemplateFolderService
return Result<bool>.Failure($"Folder with ID {folderId} not found.");
var allFolders = await _repository.GetAllFoldersAsync(cancellationToken);
var allTemplates = await _repository.GetAllTemplatesAsync(cancellationToken);
// Only FolderId is read — the untracked identity projection, not the full
// template graph with every script body (review 2026-10-21 04/P6, 08/P1).
var allTemplates = await _repository.GetTemplateStubsAsync(cancellationToken);
var childFolderCount = allFolders.Count(f => f.ParentFolderId == folderId);
var childTemplateCount = allTemplates.Count(t => t.FolderId == folderId);