diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/ITemplateEngineRepository.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/ITemplateEngineRepository.cs
index 08f3106b..25c1098a 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/ITemplateEngineRepository.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/ITemplateEngineRepository.cs
@@ -1,6 +1,7 @@
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Scripts;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
+using ZB.MOM.WW.ScadaBridge.Commons.Types.Templates;
namespace ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
@@ -55,6 +56,12 @@ public interface ITemplateEngineRepository
/// A task that resolves to a read-only list of all templates without script bodies.
Task> GetAllTemplatesForAnalysisAsync(CancellationToken cancellationToken = default);
///
+ /// Returns an untracked identity/edge projection of every template (no children), ordered by name.
+ ///
+ /// Cancellation token.
+ /// A task that resolves to a read-only list of template stubs.
+ Task> GetTemplateStubsAsync(CancellationToken cancellationToken = default);
+ ///
/// Database-side paged listing of templates as row summaries (child
/// collections reduced to counts, no script bodies). Backs the
/// ListTemplates management command, which previously materialised
diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Templates/TemplateStub.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Templates/TemplateStub.cs
new file mode 100644
index 00000000..318886b8
--- /dev/null
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Templates/TemplateStub.cs
@@ -0,0 +1,19 @@
+namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Templates;
+
+///
+/// Lightweight template identity/edge projection (no attributes, alarms, scripts or compositions)
+/// for callers that only need names and the inheritance/folder/ownership edges.
+///
+/// Template ID.
+/// Template name.
+/// Base template, if any.
+/// Containing folder, if any.
+/// True for auto-derived composition templates.
+/// Owning composition row for derived templates.
+public sealed record TemplateStub(
+ int Id,
+ string Name,
+ int? ParentTemplateId,
+ int? FolderId,
+ bool IsDerived,
+ int? OwnerCompositionId);
diff --git a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/TemplateEngineRepository.cs b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/TemplateEngineRepository.cs
index 573d139f..839153d4 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/TemplateEngineRepository.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/TemplateEngineRepository.cs
@@ -90,6 +90,16 @@ public class TemplateEngineRepository : ITemplateEngineRepository
.ToListAsync(cancellationToken);
}
+ ///
+ public async Task> GetTemplateStubsAsync(CancellationToken cancellationToken = default)
+ {
+ return await _context.Templates
+ .AsNoTracking()
+ .OrderBy(t => t.Name)
+ .Select(t => new TemplateStub(t.Id, t.Name, t.ParentTemplateId, t.FolderId, t.IsDerived, t.OwnerCompositionId))
+ .ToListAsync(cancellationToken);
+ }
+
///
// Two deliberate departures from :
//
diff --git a/tests/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests/TemplateEngineRepositoryTests.cs b/tests/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests/TemplateEngineRepositoryTests.cs
index ee82df07..91bee9b5 100644
--- a/tests/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests/TemplateEngineRepositoryTests.cs
+++ b/tests/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests/TemplateEngineRepositoryTests.cs
@@ -31,6 +31,27 @@ public class TemplateEngineRepositoryTests : IDisposable
_context.Dispose();
}
+ /// Verifies the stub projection carries identity and edges and leaves nothing tracked.
+ /// A task that represents the asynchronous operation.
+ [Fact]
+ public async Task GetTemplateStubs_ProjectsEdgesUntrackedOrderedByName()
+ {
+ var baseT = new Template("Base");
+ _context.Templates.Add(baseT);
+ await _context.SaveChangesAsync();
+ _context.Templates.Add(new Template("Alpha") { ParentTemplateId = baseT.Id });
+ await _context.SaveChangesAsync();
+ _context.ChangeTracker.Clear();
+
+ var stubs = await _repository.GetTemplateStubsAsync();
+
+ Assert.Equal(new[] { "Alpha", "Base" }, stubs.Select(s => s.Name));
+ Assert.Equal(baseT.Id, stubs[0].ParentTemplateId);
+ Assert.Null(stubs[1].ParentTemplateId);
+ Assert.False(stubs[0].IsDerived);
+ Assert.Empty(_context.ChangeTracker.Entries());
+ }
+
/// Verifies that the topology page is bounded, filtered and returned untracked.
/// A task that represents the asynchronous operation.
[Fact]