feat(uns): IUnsTreeService structural load + DI registration
This commit is contained in:
@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ZB.MOM.WW.OtOpcUa.AdminUI.Hubs;
|
||||
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns;
|
||||
using ZB.MOM.WW.OtOpcUa.Commons.Browsing;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browser;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser;
|
||||
@@ -42,6 +43,7 @@ public static class EndpointRouteBuilderExtensions
|
||||
services.AddSingleton<Browsing.BrowseSessionRegistry>();
|
||||
services.AddHostedService<Browsing.BrowseSessionReaper>();
|
||||
services.AddScoped<Browsing.IBrowserSessionService, Browsing.BrowserSessionService>();
|
||||
services.AddScoped<IUnsTreeService, UnsTreeService>();
|
||||
services.AddSingleton<IDriverBrowser, OpcUaClientDriverBrowser>();
|
||||
services.AddSingleton<IDriverBrowser, GalaxyDriverBrowser>();
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns;
|
||||
|
||||
/// <summary>
|
||||
/// Loads the structural portion of the unified-namespace (UNS) browse tree —
|
||||
/// Enterprise → Cluster → Area → Line → Equipment — from the config database.
|
||||
/// Equipment children (tags/virtual tags) are summarised by count only and loaded
|
||||
/// lazily by the renderer; this service never returns Tag/VirtualTag leaf nodes.
|
||||
/// </summary>
|
||||
public interface IUnsTreeService
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads the full structural tree. Empty clusters are retained so they remain
|
||||
/// visible and editable. The returned nodes are detached view-models, safe to
|
||||
/// hold and mutate UI state on after the underlying context is disposed.
|
||||
/// </summary>
|
||||
/// <param name="ct">A token to cancel the load.</param>
|
||||
/// <returns>The enterprise root nodes, each populated down to equipment.</returns>
|
||||
Task<IReadOnlyList<UnsNode>> LoadStructureAsync(CancellationToken ct = default);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user