feat: HistorianSession browse + metadata on the reused session (+ env-gated round-trip)

Browse mirrors ReadRawAsync (collect-then-yield); metadata mirrors ReadAtTimeAsync
(unary). Delegates to the HistorianGrpcTagClient …OnSession seams so a leased session
browses + reads metadata without re-handshaking (pending.md A1 broadening).

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
This commit is contained in:
Joseph Doherty
2026-06-25 10:28:44 -04:00
parent 2f689cbe71
commit 81aff03748
2 changed files with 38 additions and 2 deletions
+23 -1
View File
@@ -7,7 +7,7 @@ namespace AVEVA.Historian.Client;
/// <summary>A live, reusable authenticated Historian session: holds one gRPC connection + one
/// OpenConnection handle and runs ops on them WITHOUT re-handshaking. Reuse across ops amortizes the
/// auth handshake. Idle-expires server-side in ~20-25s — callers keep it warm (PingAsync) or re-open.
/// Reads/historical-write/tag-write/status only; events are NOT exposed (separate channel+auth).</summary>
/// Reads, browse/metadata, historical-write, tag-write and status; events are NOT exposed (separate channel+auth).</summary>
public sealed class HistorianSession : IAsyncDisposable
{
private readonly HistorianGrpcConnection _connection;
@@ -89,6 +89,28 @@ public sealed class HistorianSession : IAsyncDisposable
() => orch.RunAtTimeOnSession(_connection, _session.ClientHandle, tag, timestampsUtc, ct), ct);
}
// --- browse / metadata (call the …OnSession seams, which take the full Session for the string handle) ---
/// <summary>Browses tag names matching <paramref name="filter"/> on the held session.</summary>
public async IAsyncEnumerable<string> BrowseTagNamesAsync(
string filter = "*",
[EnumeratorCancellation] CancellationToken ct = default)
{
ct.ThrowIfCancellationRequested();
List<string> names = await Task.Run(
() => HistorianGrpcTagClient.BrowseTagNamesOnSession(_connection, _session, filter, _options, ct), ct)
.ConfigureAwait(false);
foreach (string name in names)
{
ct.ThrowIfCancellationRequested();
yield return name;
}
}
/// <summary>Reads metadata for <paramref name="tag"/> on the held session (null if unknown).</summary>
public Task<HistorianTagMetadata?> GetTagMetadataAsync(string tag, CancellationToken ct = default)
=> Task.Run(() => HistorianGrpcTagClient.GetTagMetadataOnSession(_connection, _session, tag, _options, ct), ct);
// --- writes (the …OnSession seams take the full Session, since the historical write keys on the
// string handle + tag GUID and the tag-config ops mix string/uint handles) ---