perf(adminui): raw tree renders tag rows from a projection without TagConfig
This commit is contained in:
@@ -173,6 +173,7 @@ public sealed class RawTreeService(IDbContextFactory<OtOpcUaConfigDbContext> dbF
|
||||
var tags = await db.Tags.AsNoTracking()
|
||||
.Where(t => t.DeviceId == deviceId && t.TagGroupId == null)
|
||||
.OrderBy(t => t.Name)
|
||||
.Select(t => new TagRow(t.TagId, t.Name, t.RowVersion))
|
||||
.ToListAsync(ct);
|
||||
|
||||
var nodes = new List<RawNode>(groups.Count + tags.Count);
|
||||
@@ -194,6 +195,7 @@ public sealed class RawTreeService(IDbContextFactory<OtOpcUaConfigDbContext> dbF
|
||||
var tags = await db.Tags.AsNoTracking()
|
||||
.Where(t => t.TagGroupId == groupId)
|
||||
.OrderBy(t => t.Name)
|
||||
.Select(t => new TagRow(t.TagId, t.Name, t.RowVersion))
|
||||
.ToListAsync(ct);
|
||||
|
||||
var nodes = new List<RawNode>(subGroups.Count + tags.Count);
|
||||
@@ -299,7 +301,13 @@ public sealed class RawTreeService(IDbContextFactory<OtOpcUaConfigDbContext> dbF
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
private static RawNode BuildTagNode(RawNode parent, Tag t) => new()
|
||||
/// <summary>
|
||||
/// The only columns a tree row needs from a <see cref="Tag" />. Projecting to this keeps the
|
||||
/// <c>TagConfig</c> nvarchar(max) JSON blob out of the tree's SELECT — it is never rendered.
|
||||
/// </summary>
|
||||
private sealed record TagRow(string TagId, string Name, byte[] RowVersion);
|
||||
|
||||
private static RawNode BuildTagNode(RawNode parent, TagRow t) => new()
|
||||
{
|
||||
Kind = RawNodeKind.Tag,
|
||||
Key = $"tag:{t.TagId}",
|
||||
|
||||
@@ -102,6 +102,41 @@ public sealed class RawTreeServiceTests
|
||||
(await service.LoadChildrenAsync(tags[0])).ShouldBeEmpty(); // leaf
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tag rows are read through a narrow projection that leaves the <c>TagConfig</c> JSON blob in the
|
||||
/// database, so this pins that the three fields a tree row actually renders — identity, name and the
|
||||
/// concurrency token the mutation handles round-trip — still arrive intact, from both tag loaders
|
||||
/// (a tag under a group, and a tag hanging directly off a device).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task LoadChildren_yields_tag_rows_carrying_id_name_and_row_version()
|
||||
{
|
||||
var (service, dbName) = Seeded();
|
||||
var cluster = (await service.LoadRootsAsync()).Single().Children.Single();
|
||||
|
||||
// Group-child loader: two tags under "Motors".
|
||||
var folder = (await service.LoadChildrenAsync(cluster)).Single(n => n.Kind == RawNodeKind.Folder);
|
||||
var driver = (await service.LoadChildrenAsync(folder)).ShouldHaveSingleItem();
|
||||
var device = (await service.LoadChildrenAsync(driver)).ShouldHaveSingleItem();
|
||||
var group = (await service.LoadChildrenAsync(device)).ShouldHaveSingleItem();
|
||||
|
||||
var plain = (await service.LoadChildrenAsync(group)).Single(t => t.EntityId == RawTreeTestDb.PlainTagId);
|
||||
plain.DisplayName.ShouldBe("state");
|
||||
plain.RowVersion.ShouldBe(
|
||||
RowVersionOf(dbName, db => db.Tags.Single(t => t.TagId == RawTreeTestDb.PlainTagId).RowVersion));
|
||||
|
||||
// Device-child loader: the cluster-root device's own tag.
|
||||
var rootDriver = (await service.LoadChildrenAsync(cluster)).Single(n => n.Kind == RawNodeKind.Driver);
|
||||
var rootDevice = (await service.LoadChildrenAsync(rootDriver)).ShouldHaveSingleItem();
|
||||
|
||||
var referenced = (await service.LoadChildrenAsync(rootDevice))
|
||||
.Single(t => t.EntityId == RawTreeTestDb.ReferencedTagId);
|
||||
referenced.Kind.ShouldBe(RawNodeKind.Tag);
|
||||
referenced.DisplayName.ShouldBe("temperature");
|
||||
referenced.RowVersion.ShouldBe(
|
||||
RowVersionOf(dbName, db => db.Tags.Single(t => t.TagId == RawTreeTestDb.ReferencedTagId).RowVersion));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------- Create/happy
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user