fix(template-engine): resolve TemplateEngine-011,013,014 — remove dead converter, duplicate-id-safe cycle detection, unified deletion logic; TemplateEngine-012 deferred
This commit is contained in:
@@ -9,6 +9,21 @@ namespace ScadaLink.TemplateEngine;
|
||||
/// </summary>
|
||||
public static class CycleDetector
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds an Id-keyed lookup that tolerates duplicate Ids in the input list
|
||||
/// (e.g. multiple not-yet-saved templates all carrying Id 0). On a duplicate
|
||||
/// the first occurrence wins — graph walks only need one representative node
|
||||
/// per Id, and a real cycle through any duplicate would still be reachable.
|
||||
/// A plain <c>ToDictionary(t => t.Id)</c> would instead throw ArgumentException.
|
||||
/// </summary>
|
||||
internal static Dictionary<int, Template> BuildLookup(IReadOnlyList<Template> allTemplates)
|
||||
{
|
||||
var lookup = new Dictionary<int, Template>();
|
||||
foreach (var t in allTemplates)
|
||||
lookup.TryAdd(t.Id, t);
|
||||
return lookup;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether setting <paramref name="parentId"/> as the parent of template
|
||||
/// <paramref name="templateId"/> would introduce an inheritance cycle.
|
||||
@@ -27,28 +42,30 @@ public static class CycleDetector
|
||||
|
||||
// Walk the inheritance chain from the proposed parent upward.
|
||||
// If we arrive back at templateId, there is a cycle.
|
||||
var lookup = allTemplates.ToDictionary(t => t.Id);
|
||||
var lookup = BuildLookup(allTemplates);
|
||||
var visited = new HashSet<int> { templateId };
|
||||
var chain = new List<string>();
|
||||
|
||||
var templateName = lookup.TryGetValue(templateId, out var tmpl) ? tmpl.Name : templateId.ToString();
|
||||
chain.Add(templateName);
|
||||
|
||||
var currentId = parentId;
|
||||
while (currentId != 0)
|
||||
// ParentTemplateId is int? — a missing value (not 0) means "no parent",
|
||||
// so a template with a real Id of 0 is walked like any other node.
|
||||
int? currentId = parentId;
|
||||
while (currentId.HasValue)
|
||||
{
|
||||
if (!lookup.TryGetValue(currentId, out var current))
|
||||
if (!lookup.TryGetValue(currentId.Value, out var current))
|
||||
break;
|
||||
|
||||
chain.Add(current.Name);
|
||||
|
||||
if (visited.Contains(currentId))
|
||||
if (visited.Contains(currentId.Value))
|
||||
{
|
||||
return $"Inheritance cycle detected: {string.Join(" -> ", chain)}.";
|
||||
}
|
||||
|
||||
visited.Add(currentId);
|
||||
currentId = current.ParentTemplateId ?? 0;
|
||||
visited.Add(currentId.Value);
|
||||
currentId = current.ParentTemplateId;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -70,7 +87,7 @@ public static class CycleDetector
|
||||
return $"Template '{selfName}' cannot compose itself.";
|
||||
}
|
||||
|
||||
var lookup = allTemplates.ToDictionary(t => t.Id);
|
||||
var lookup = BuildLookup(allTemplates);
|
||||
|
||||
// BFS/DFS from composedTemplateId through all its compositions.
|
||||
// If we reach templateId, that's a cycle.
|
||||
@@ -115,7 +132,7 @@ public static class CycleDetector
|
||||
int? proposedComposedTemplateId,
|
||||
IReadOnlyList<Template> allTemplates)
|
||||
{
|
||||
var lookup = allTemplates.ToDictionary(t => t.Id);
|
||||
var lookup = BuildLookup(allTemplates);
|
||||
|
||||
// Build adjacency: for each template, collect all reachable templates
|
||||
// via inheritance (parent) and composition edges.
|
||||
@@ -124,11 +141,12 @@ public static class CycleDetector
|
||||
var visited = new HashSet<int>();
|
||||
var queue = new Queue<int>();
|
||||
|
||||
// Seed with proposed targets
|
||||
if (proposedParentId.HasValue && proposedParentId.Value != 0)
|
||||
// Seed with proposed targets. A null proposed id means "no edge"; a value
|
||||
// of 0 is a legitimate Id, so only HasValue gates enqueuing.
|
||||
if (proposedParentId.HasValue)
|
||||
queue.Enqueue(proposedParentId.Value);
|
||||
|
||||
if (proposedComposedTemplateId.HasValue && proposedComposedTemplateId.Value != 0)
|
||||
if (proposedComposedTemplateId.HasValue)
|
||||
queue.Enqueue(proposedComposedTemplateId.Value);
|
||||
|
||||
while (queue.Count > 0)
|
||||
@@ -146,8 +164,8 @@ public static class CycleDetector
|
||||
if (!lookup.TryGetValue(currentId, out var current))
|
||||
continue;
|
||||
|
||||
// Follow inheritance edge
|
||||
if (current.ParentTemplateId.HasValue && current.ParentTemplateId.Value != 0)
|
||||
// Follow inheritance edge (int? — missing value means no parent)
|
||||
if (current.ParentTemplateId.HasValue)
|
||||
queue.Enqueue(current.ParentTemplateId.Value);
|
||||
|
||||
// Follow composition edges
|
||||
|
||||
Reference in New Issue
Block a user