feat(uns): IUnsTreeService structural load + DI registration

This commit is contained in:
Joseph Doherty
2026-06-08 12:23:00 -04:00
parent 0f286a70b8
commit cec670f0c8
6 changed files with 343 additions and 0 deletions
@@ -0,0 +1,78 @@
using Microsoft.EntityFrameworkCore;
using ZB.MOM.WW.OtOpcUa.Configuration;
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns;
/// <summary>
/// Default <see cref="IUnsTreeService"/>. Reads the structural rows with a handful of
/// untracked queries, computes per-equipment tag/virtual-tag counts, and hands the flat
/// rows to the pure <see cref="UnsTreeAssembly.Build"/> to nest into the browse tree.
/// </summary>
/// <remarks>
/// A new context is created per call via the pooled factory — the same pattern every
/// AdminUI page uses — so the service is safe to register as a scoped singleton and call
/// concurrently from independent Blazor circuits.
/// </remarks>
public sealed class UnsTreeService(IDbContextFactory<OtOpcUaConfigDbContext> dbFactory) : IUnsTreeService
{
/// <inheritdoc />
public async Task<IReadOnlyList<UnsNode>> LoadStructureAsync(CancellationToken ct = default)
{
await using var db = await dbFactory.CreateDbContextAsync(ct);
var clusters = await db.ServerClusters
.AsNoTracking()
.Select(c => new ClusterRow(c.ClusterId, c.Enterprise, c.Site, c.Name))
.ToListAsync(ct);
var areas = await db.UnsAreas
.AsNoTracking()
.Select(a => new AreaRow(a.UnsAreaId, a.ClusterId, a.Name))
.ToListAsync(ct);
var lines = await db.UnsLines
.AsNoTracking()
.Select(l => new LineRow(l.UnsLineId, l.UnsAreaId, l.Name))
.ToListAsync(ct);
var equipmentRows = await db.Equipment
.AsNoTracking()
.Select(e => new
{
e.EquipmentId,
e.UnsLineId,
e.MachineCode,
e.Name,
})
.ToListAsync(ct);
// Per-equipment driver-tag counts (tags with no equipment are excluded).
var tagCounts = (await db.Tags
.AsNoTracking()
.Where(t => t.EquipmentId != null)
.GroupBy(t => t.EquipmentId)
.Select(g => new { EquipmentId = g.Key!, Count = g.Count() })
.ToListAsync(ct))
.ToDictionary(x => x.EquipmentId, x => x.Count, StringComparer.Ordinal);
// Per-equipment virtual-tag counts (EquipmentId is always set on virtual tags).
var vtagCounts = (await db.VirtualTags
.AsNoTracking()
.GroupBy(v => v.EquipmentId)
.Select(g => new { EquipmentId = g.Key, Count = g.Count() })
.ToListAsync(ct))
.ToDictionary(x => x.EquipmentId, x => x.Count, StringComparer.Ordinal);
var equipment = equipmentRows
.Select(e => new EquipmentRow(
e.EquipmentId,
e.UnsLineId,
e.MachineCode,
e.Name,
tagCounts.GetValueOrDefault(e.EquipmentId),
vtagCounts.GetValueOrDefault(e.EquipmentId)))
.ToList();
return UnsTreeAssembly.Build(clusters, areas, lines, equipment);
}
}