using Microsoft.EntityFrameworkCore; using ZB.MOM.WW.OtOpcUa.Configuration.Entities; namespace ZB.MOM.WW.OtOpcUa.Configuration.Queries; /// /// Shared query for the cluster-scoped audit view. Audit rows reach ConfigAuditLog by two /// paths that stamp different columns: /// /// the bespoke stored-procedure path stamps ClusterId directly; /// the structured AuditWriterActor path stamps NodeId (leaving /// ClusterId null). /// /// A cluster-scoped view must surface both, so this query matches rows whose ClusterId /// equals the cluster or whose NodeId belongs to a node in the cluster /// (membership from : NodeId → ClusterId). /// public static class ClusterAuditQuery { /// /// Returns the newest audit rows visible for /// , newest first. Executes one query to resolve the cluster's /// node IDs, then one filtered query against ConfigAuditLog. /// /// The config database context. /// The cluster whose audit rows to fetch. /// Maximum number of rows to return. /// Cancellation token. /// The matching audit rows, newest first. public static async Task> ForClusterAsync( OtOpcUaConfigDbContext db, string clusterId, int pageSize, CancellationToken ct = default) { var nodeIds = await db.ClusterNodes.AsNoTracking() .Where(n => n.ClusterId == clusterId) .Select(n => n.NodeId) .ToListAsync(ct); return await db.ConfigAuditLogs.AsNoTracking() .Where(a => a.ClusterId == clusterId || (a.ClusterId == null && a.NodeId != null && nodeIds.Contains(a.NodeId))) .OrderByDescending(a => a.Timestamp) .Take(pageSize) .ToListAsync(ct); } }