diff --git a/src/AVEVA.Historian.Client/Grpc/HistorianGrpcSqlClient.cs b/src/AVEVA.Historian.Client/Grpc/HistorianGrpcSqlClient.cs
new file mode 100644
index 0000000..b5bb36d
--- /dev/null
+++ b/src/AVEVA.Historian.Client/Grpc/HistorianGrpcSqlClient.cs
@@ -0,0 +1,114 @@
+using Google.Protobuf;
+using AVEVA.Historian.Client.Models;
+using AVEVA.Historian.Client.Protocol;
+using AVEVA.Historian.Client.Wcf;
+using GrpcRetrieval = ArchestrA.Grpc.Contract.Retrieval;
+
+namespace AVEVA.Historian.Client.Grpc;
+
+///
+/// Executes SQL commands over the 2023 R2 gRPC transport (HCAL R1.1), mirroring
+/// 's two-op ExeC/GetR flow. The 2020 WCF path uses a
+/// dedicated GetRecordSetByteStream op; the gRPC front door has no such RPC, so the NRBF
+/// recordset stream would be fetched through the generic RetrievalService.GetNextQueryResultBuffer
+/// keyed by the query handle ExecuteSqlCommand returns. ExecuteSqlCommand takes the
+/// uppercase string session handle; the result-buffer fetch takes the transient uint client
+/// handle (both come from the one Open2 session).
+///
+/// SERVER-WALLED (captured 2026-06-22). The 2023 R2 front-door
+/// RetrievalService.ExecuteSqlCommand faults server-side before returning a query handle:
+/// the response carries native error 38 wrapping a managed
+/// System.IndexOutOfRangeException ... at aahClientAccessPoint.CSrvDbConnection.ExecuteSqlCommand.
+/// This is a server-side CSrvDbConnection (SQL DB-connection) precondition that the pure
+/// managed gRPC session does not establish — the same class of wall as
+/// StorageService.OpenStorageConnection (whose real precondition is the front-door
+/// HistoryService.RegisterTags family). Priming Retr.GetV does not clear it. The request
+/// framing here is the captured/expected shape; the op stays bounded behind
+/// until the DB-connection registration is reproduced.
+///
+///
+internal static class HistorianGrpcSqlClient
+{
+ // GetNextQueryResultBuffer is byte-stream-paged; a small record set returns in one page. Runaway guard.
+ private const int MaxPages = 4096;
+
+ public static Task ExecuteSqlCommandAsync(
+ HistorianClientOptions options,
+ string command,
+ HistorianSqlExecuteOption option,
+ CancellationToken cancellationToken)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(command);
+ return Task.Run(() => ExecuteSqlCommand(options, command, option, cancellationToken), cancellationToken);
+ }
+
+ private static HistorianSqlResult ExecuteSqlCommand(
+ HistorianClientOptions options,
+ string command,
+ HistorianSqlExecuteOption option,
+ CancellationToken cancellationToken)
+ {
+ using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(options);
+ HistorianGrpcHandshake.Session session = HistorianGrpcHandshake.OpenSession(connection, options, cancellationToken);
+ var retrievalClient = new GrpcRetrieval.RetrievalService.RetrievalServiceClient(connection.Channel);
+ DateTime Deadline() => DateTime.UtcNow.Add(options.RequestTimeout);
+
+ // Prime the Retrieval service version handshake (Retr.GetV) before the string-handle SQL op, as
+ // the native WCF SQL path does — the server-side ExecuteSqlCommand otherwise faults.
+ retrievalClient.GetRetrievalInterfaceVersion(
+ new GrpcRetrieval.GetRetrievalInterfaceVersionRequest(), connection.Metadata, Deadline(), cancellationToken);
+
+ GrpcRetrieval.ExecuteSqlCommandResponse exec = retrievalClient.ExecuteSqlCommand(
+ new GrpcRetrieval.ExecuteSqlCommandRequest
+ {
+ StrHandle = session.StringHandle,
+ StrCommand = command,
+ UiOption = (uint)option,
+ UiQueryHandle = 0
+ },
+ connection.Metadata,
+ Deadline(),
+ cancellationToken);
+
+ if (!(exec.Status?.BSuccess ?? false))
+ {
+ // Captured 2026-06-22: the server-side CSrvDbConnection.ExecuteSqlCommand throws
+ // IndexOutOfRange (native error 38) — a DB-connection precondition the pure managed gRPC
+ // session doesn't establish. Surface the SDK's evidence-missing signal rather than a raw
+ // server fault. See the class remarks.
+ throw new ProtocolEvidenceMissingException(
+ "ExecuteSqlCommand over gRPC: server-side CSrvDbConnection.ExecuteSqlCommand faults " +
+ "(IndexOutOfRange / native error 38) — an unmet DB-connection precondition (gRPC transport). Use WCF.");
+ }
+
+ int returnValue = exec.IRetValue;
+ uint queryHandle = exec.UiQueryHandle;
+
+ using MemoryStream accumulated = new();
+ for (int page = 0; page < MaxPages; page++)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ GrpcRetrieval.GetNextQueryResultBufferResponse buffer = retrievalClient.GetNextQueryResultBuffer(
+ new GrpcRetrieval.GetNextQueryResultBufferRequest { UiHandle = session.ClientHandle, UiQueryHandle = queryHandle },
+ connection.Metadata,
+ Deadline(),
+ cancellationToken);
+
+ byte[] resultBuffer = buffer.BtQueryResult?.ToByteArray() ?? [];
+
+ // GetR is false-even-on-success: the final page returns false with the data still in the
+ // buffer, so always consume the buffer first, then stop on a false status or an empty page.
+ if (resultBuffer.Length > 0)
+ {
+ accumulated.Write(resultBuffer, 0, resultBuffer.Length);
+ }
+
+ if (!(buffer.Status?.BSuccess ?? false) || resultBuffer.Length == 0)
+ {
+ break;
+ }
+ }
+
+ return HistorianSqlResultProtocol.Parse(accumulated.ToArray(), returnValue);
+ }
+}
diff --git a/src/AVEVA.Historian.Client/Grpc/HistorianGrpcStatusClient.cs b/src/AVEVA.Historian.Client/Grpc/HistorianGrpcStatusClient.cs
index f96141a..3e67538 100644
--- a/src/AVEVA.Historian.Client/Grpc/HistorianGrpcStatusClient.cs
+++ b/src/AVEVA.Historian.Client/Grpc/HistorianGrpcStatusClient.cs
@@ -1,5 +1,7 @@
+using Google.Protobuf;
using Grpc.Core;
using AVEVA.Historian.Client.Models;
+using AVEVA.Historian.Client.Wcf;
using GrpcStatus = ArchestrA.Grpc.Contract.Status;
namespace AVEVA.Historian.Client.Grpc;
@@ -142,4 +144,46 @@ internal static class HistorianGrpcStatusClient
string? value = response.StrSystemTimeZoneName;
return string.IsNullOrEmpty(value) ? null : value;
}
+
+ ///
+ /// Reads a Historian runtime parameter over gRPC (StatusService.GetRuntimeParameter).
+ /// The request/response byte buffers are the proven 2020 GETRP wire format
+ /// () carried unchanged inside the protobuf
+ /// btRequest/btResponse fields; the op keys on the uppercase string session handle.
+ ///
+ public static Task GetRuntimeParameterAsync(
+ HistorianClientOptions options,
+ string parameterName,
+ CancellationToken cancellationToken)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(parameterName);
+ return Task.Run(() => GetRuntimeParameter(options, parameterName, cancellationToken), cancellationToken);
+ }
+
+ private static string? GetRuntimeParameter(HistorianClientOptions options, string parameterName, CancellationToken cancellationToken)
+ {
+ using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(options);
+ HistorianGrpcHandshake.Session session = HistorianGrpcHandshake.OpenSession(connection, options, cancellationToken);
+
+ byte[] request = HistorianRuntimeParameterProtocol.SerializeRequest(parameterName);
+
+ var statusClient = new GrpcStatus.StatusService.StatusServiceClient(connection.Channel);
+ GrpcStatus.GetRuntimeParameterResponse response = statusClient.GetRuntimeParameter(
+ new GrpcStatus.GetRuntimeParameterRequest
+ {
+ StrHandle = session.StringHandle,
+ BtRequest = ByteString.CopyFrom(request)
+ },
+ connection.Metadata,
+ DateTime.UtcNow.Add(options.RequestTimeout),
+ cancellationToken);
+
+ if (!(response.Status?.BSuccess ?? false))
+ {
+ return null;
+ }
+
+ byte[] responseBuffer = response.BtResponse?.ToByteArray() ?? [];
+ return HistorianRuntimeParameterProtocol.ParseSingleStringResult(responseBuffer);
+ }
}
diff --git a/src/AVEVA.Historian.Client/Grpc/HistorianGrpcTagClient.cs b/src/AVEVA.Historian.Client/Grpc/HistorianGrpcTagClient.cs
index 7600d80..e8476b0 100644
--- a/src/AVEVA.Historian.Client/Grpc/HistorianGrpcTagClient.cs
+++ b/src/AVEVA.Historian.Client/Grpc/HistorianGrpcTagClient.cs
@@ -92,6 +92,78 @@ internal static class HistorianGrpcTagClient
return response.BtTagInfos?.ToByteArray() ?? [];
}
+ // GetTagExtendedPropertiesFromName is sequence-paged; a single tag returns everything on page 0
+ // and an empty/false buffer next. The cap is a runaway guard (mirrors the WCF path).
+ private const int MaxExtendedPropertyPages = 64;
+
+ ///
+ /// Reads a tag's extended (user-defined) properties over gRPC
+ /// (RetrievalService.GetTagExtendedPropertiesFromName, a string-handle op). The request
+ /// btTagNames and response btTeps buffers are the proven 2020 GetTepByNm wire
+ /// format () carried unchanged; paging follows
+ /// the same sequence loop as the WCF path.
+ ///
+ public static Task> GetTagExtendedPropertiesAsync(
+ HistorianClientOptions options,
+ string tag,
+ CancellationToken cancellationToken)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(tag);
+ return Task.Run(() => GetTagExtendedProperties(options, tag, cancellationToken), cancellationToken);
+ }
+
+ private static IReadOnlyList GetTagExtendedProperties(
+ HistorianClientOptions options, string tag, CancellationToken cancellationToken)
+ {
+ using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(options);
+ HistorianGrpcHandshake.Session session = HistorianGrpcHandshake.OpenSession(connection, options, cancellationToken);
+ var retrievalClient = new GrpcRetrieval.RetrievalService.RetrievalServiceClient(connection.Channel);
+
+ byte[] tagNames = HistorianTagExtendedPropertyProtocol.SerializeRequest(tag);
+ List properties = [];
+ uint sequence = 0;
+
+ for (int page = 0; page < MaxExtendedPropertyPages; page++)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ GrpcRetrieval.GetTagExtendedPropertiesFromNameResponse response = retrievalClient.GetTagExtendedPropertiesFromName(
+ new GrpcRetrieval.GetTagExtendedPropertiesFromNameRequest
+ {
+ StrHandle = session.StringHandle,
+ BtTagNames = ByteString.CopyFrom(tagNames),
+ UiSequence = sequence
+ },
+ connection.Metadata,
+ DateTime.UtcNow.Add(options.RequestTimeout),
+ cancellationToken);
+
+ if (!(response.Status?.BSuccess ?? false))
+ {
+ // A non-success terminates paging. The server signals "no more rows" with a
+ // CClientUtil::FillBufferFromVector marker (live-confirmed) — including on page 0 when
+ // the tag has no user-defined properties, which is a legitimate empty result, not an
+ // error. This mirrors the WCF path, which also breaks (returns empty) rather than throws.
+ break;
+ }
+
+ IReadOnlyList rows =
+ HistorianTagExtendedPropertyProtocol.ParseResponse(response.BtTeps?.ToByteArray() ?? []);
+ if (rows.Count == 0)
+ {
+ break;
+ }
+
+ foreach (HistorianTagExtendedPropertyRow row in rows)
+ {
+ properties.Add(new HistorianTagExtendedProperty(row.PropertyName, row.Value));
+ }
+
+ sequence = response.UiSequence;
+ }
+
+ return properties;
+ }
+
// QueryTag (browse paging) request framing, recovered from the .rdata packet-descriptor table
// in aahClientManaged.dll (entries {0x6751,1}=StartTagQuery, {0x6752,1}=QueryTag) and confirmed
// live: btRequest = u16 marker(0x6752) + u16 version(1) + u16 queryType + u32 startIndex + u32 count.
diff --git a/src/AVEVA.Historian.Client/Grpc/HistorianGrpcTagWriteOrchestrator.cs b/src/AVEVA.Historian.Client/Grpc/HistorianGrpcTagWriteOrchestrator.cs
new file mode 100644
index 0000000..480b991
--- /dev/null
+++ b/src/AVEVA.Historian.Client/Grpc/HistorianGrpcTagWriteOrchestrator.cs
@@ -0,0 +1,197 @@
+using Google.Protobuf;
+using AVEVA.Historian.Client.Models;
+using AVEVA.Historian.Client.Wcf;
+using GrpcHistory = ArchestrA.Grpc.Contract.History;
+
+namespace AVEVA.Historian.Client.Grpc;
+
+///
+/// Tag-configuration write ops over the 2023 R2 gRPC transport, mirroring
+/// . Each op opens a write-enabled Open2 session
+/// (0x401) and reuses the proven 2020 byte serializers verbatim inside the protobuf
+/// bytes fields:
+///
+/// - → HistoryService.EnsureTags (string handle,
+/// btTagInfos = )
+/// - → HistoryService.DeleteTags (uint handle,
+/// btTagnames = )
+/// - → HistoryService.StartJob (string handle,
+/// btInput = )
+/// - → HistoryService.AddTagExtendedProperties
+/// (string handle, btTeps = )
+///
+///
+/// Tooled but not yet live-verified. The request framing reuses the WCF serializers proven on
+/// the 2020 transport, and the read-side config ops confirm WCF config buffers ride the gRPC RPC
+/// unchanged — but these mutate server state (create/delete/rename tags, write properties), so they
+/// are gated behind a sandbox-tag in the integration tests and have not been run destructively against
+/// a shared live server. The WCF path additionally runs a priming "discovery dance" (UpdC3 + system
+/// parameters + cross-service GetV) before the write; the gRPC front door established the equivalent
+/// session state in the M3 non-streamed-write probe without it, so it is omitted here pending live
+/// confirmation. If a live run is rejected, that priming is the first thing to add.
+///
+///
+internal sealed class HistorianGrpcTagWriteOrchestrator
+{
+ private const uint WriteEnabledConnectionMode = HistorianWcfAuthChainHelper.NativeIntegratedWriteEnabledConnectionMode;
+
+ private readonly HistorianClientOptions _options;
+
+ public HistorianGrpcTagWriteOrchestrator(HistorianClientOptions options)
+ {
+ _options = options ?? throw new ArgumentNullException(nameof(options));
+ }
+
+ public Task EnsureTagAsync(HistorianTagDefinition definition, CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(definition);
+ ArgumentException.ThrowIfNullOrWhiteSpace(definition.TagName, nameof(definition));
+ // Surface unsupported (non-analog) types early, exactly as the WCF path does.
+ _ = HistorianTagWriteProtocol.GetAnalogDataTypeCode(definition.DataType);
+ return Task.Run(() => EnsureTag(definition, cancellationToken), cancellationToken);
+ }
+
+ private bool EnsureTag(HistorianTagDefinition definition, CancellationToken cancellationToken)
+ {
+ using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(_options);
+ HistorianGrpcHandshake.Session session = HistorianGrpcHandshake.OpenSession(connection, _options, cancellationToken, WriteEnabledConnectionMode);
+
+ byte[] payload = HistorianTagWriteProtocol.SerializeAnalogCTagMetadata(
+ tagName: definition.TagName,
+ description: definition.Description,
+ engineeringUnit: definition.EngineeringUnit,
+ dateCreatedUtc: DateTime.UtcNow,
+ dataType: definition.DataType,
+ minEU: definition.MinEU,
+ maxEU: definition.MaxEU,
+ minRaw: definition.MinRaw,
+ maxRaw: definition.MaxRaw,
+ storageRateMs: definition.StorageRateMs,
+ applyScaling: definition.ApplyScaling,
+ storageType: definition.StorageType,
+ integralDivisor: definition.IntegralDivisor);
+
+ var historyClient = new GrpcHistory.HistoryService.HistoryServiceClient(connection.Channel);
+ GrpcHistory.EnsureTagsResponse response = historyClient.EnsureTags(
+ new GrpcHistory.EnsureTagsRequest
+ {
+ StrHandle = session.StringHandle,
+ BtTagInfos = ByteString.CopyFrom(payload),
+ ElementCount = 1
+ },
+ connection.Metadata,
+ DateTime.UtcNow.Add(_options.RequestTimeout),
+ cancellationToken);
+
+ return response.Status?.BSuccess ?? false;
+ }
+
+ public Task DeleteTagAsync(string tagName, CancellationToken cancellationToken)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(tagName);
+ return Task.Run(() => DeleteTag(tagName, cancellationToken), cancellationToken);
+ }
+
+ private bool DeleteTag(string tagName, CancellationToken cancellationToken)
+ {
+ using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(_options);
+ HistorianGrpcHandshake.Session session = HistorianGrpcHandshake.OpenSession(connection, _options, cancellationToken, WriteEnabledConnectionMode);
+
+ // DeleteTags takes the transient uint client handle (not the string handle), per the WCF wire capture.
+ byte[] tagNames = HistorianTagWriteProtocol.SerializeDeleteTagNames([tagName]);
+ var historyClient = new GrpcHistory.HistoryService.HistoryServiceClient(connection.Channel);
+ GrpcHistory.DeleteTagsResponse response = historyClient.DeleteTags(
+ new GrpcHistory.DeleteTagsRequest
+ {
+ UiHandle = session.ClientHandle,
+ BtTagnames = ByteString.CopyFrom(tagNames)
+ },
+ connection.Metadata,
+ DateTime.UtcNow.Add(_options.RequestTimeout),
+ cancellationToken);
+
+ return response.Status?.BSuccess ?? false;
+ }
+
+ public Task AddTagExtendedPropertiesAsync(
+ string tagName, IReadOnlyList properties, CancellationToken cancellationToken)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(tagName);
+ ArgumentNullException.ThrowIfNull(properties);
+ if (properties.Count == 0)
+ {
+ throw new ArgumentException("At least one extended property is required.", nameof(properties));
+ }
+ return Task.Run(() => AddTagExtendedProperties(tagName, properties, cancellationToken), cancellationToken);
+ }
+
+ private bool AddTagExtendedProperties(
+ string tagName, IReadOnlyList properties, CancellationToken cancellationToken)
+ {
+ using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(_options);
+ HistorianGrpcHandshake.Session session = HistorianGrpcHandshake.OpenSession(connection, _options, cancellationToken, WriteEnabledConnectionMode);
+
+ byte[] inBuff = HistorianTagExtendedPropertyProtocol.SerializeAddRequest(tagName, properties);
+ var historyClient = new GrpcHistory.HistoryService.HistoryServiceClient(connection.Channel);
+ GrpcHistory.AddTagExtendedPropertiesResponse response = historyClient.AddTagExtendedProperties(
+ new GrpcHistory.AddTagExtendedPropertiesRequest
+ {
+ StrHandle = session.StringHandle,
+ BtTeps = ByteString.CopyFrom(inBuff)
+ },
+ connection.Metadata,
+ DateTime.UtcNow.Add(_options.RequestTimeout),
+ cancellationToken);
+
+ return response.Status?.BSuccess ?? false;
+ }
+
+ public Task RenameTagsAsync(
+ IReadOnlyList<(string OldName, string NewName)> pairs, CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(pairs);
+ if (pairs.Count == 0)
+ {
+ throw new ArgumentException("At least one (old,new) name pair is required.", nameof(pairs));
+ }
+ foreach ((string oldName, string newName) in pairs)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(oldName, nameof(pairs));
+ ArgumentException.ThrowIfNullOrWhiteSpace(newName, nameof(pairs));
+ }
+ return Task.Run(() => RenameTags(pairs, cancellationToken), cancellationToken);
+ }
+
+ private HistorianTagRenameResult RenameTags(IReadOnlyList<(string OldName, string NewName)> pairs, CancellationToken cancellationToken)
+ {
+ using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(_options);
+ HistorianGrpcHandshake.Session session = HistorianGrpcHandshake.OpenSession(connection, _options, cancellationToken, WriteEnabledConnectionMode);
+
+ byte[] jobBuffer = HistorianTagRenameProtocol.SerializeRenameJob(pairs);
+ var historyClient = new GrpcHistory.HistoryService.HistoryServiceClient(connection.Channel);
+ GrpcHistory.StartJobResponse response = historyClient.StartJob(
+ new GrpcHistory.StartJobRequest
+ {
+ StrHandle = session.StringHandle,
+ BtInput = ByteString.CopyFrom(jobBuffer)
+ },
+ connection.Metadata,
+ DateTime.UtcNow.Add(_options.RequestTimeout),
+ cancellationToken);
+
+ bool ok = response.Status?.BSuccess ?? false;
+ Guid parsedJobId = Guid.Empty;
+ if (!string.IsNullOrWhiteSpace(response.StrJobid))
+ {
+ Guid.TryParse(response.StrJobid.Trim().Trim('$', '{', '}'), out parsedJobId);
+ }
+
+ return new HistorianTagRenameResult
+ {
+ Accepted = ok,
+ JobId = parsedJobId,
+ PairCount = pairs.Count,
+ Error = ok ? null : "Server rejected the rename job (StartJob returned false). Check that the 'AllowRenameTags' system parameter is enabled.",
+ };
+ }
+}
diff --git a/src/AVEVA.Historian.Client/HistorianClient.cs b/src/AVEVA.Historian.Client/HistorianClient.cs
index d044514..fd7f8d7 100644
--- a/src/AVEVA.Historian.Client/HistorianClient.cs
+++ b/src/AVEVA.Historian.Client/HistorianClient.cs
@@ -237,7 +237,9 @@ public sealed class HistorianClient : IAsyncDisposable
{
ArgumentException.ThrowIfNullOrWhiteSpace(tag);
ArgumentNullException.ThrowIfNull(properties);
- return new HistorianWcfTagWriteOrchestrator(_options).AddTagExtendedPropertiesAsync(tag, properties, cancellationToken);
+ return _options.Transport == HistorianTransport.RemoteGrpc
+ ? new Grpc.HistorianGrpcTagWriteOrchestrator(_options).AddTagExtendedPropertiesAsync(tag, properties, cancellationToken)
+ : new HistorianWcfTagWriteOrchestrator(_options).AddTagExtendedPropertiesAsync(tag, properties, cancellationToken);
}
/// Convenience overload of for a single
@@ -285,7 +287,9 @@ public sealed class HistorianClient : IAsyncDisposable
public Task EnsureTagAsync(HistorianTagDefinition definition, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(definition);
- return new HistorianWcfTagWriteOrchestrator(_options).EnsureTagAsync(definition, cancellationToken);
+ return _options.Transport == HistorianTransport.RemoteGrpc
+ ? new Grpc.HistorianGrpcTagWriteOrchestrator(_options).EnsureTagAsync(definition, cancellationToken)
+ : new HistorianWcfTagWriteOrchestrator(_options).EnsureTagAsync(definition, cancellationToken);
}
///
@@ -299,7 +303,9 @@ public sealed class HistorianClient : IAsyncDisposable
public Task DeleteTagAsync(string tagName, CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(tagName);
- return new HistorianWcfTagWriteOrchestrator(_options).DeleteTagAsync(tagName, cancellationToken);
+ return _options.Transport == HistorianTransport.RemoteGrpc
+ ? new Grpc.HistorianGrpcTagWriteOrchestrator(_options).DeleteTagAsync(tagName, cancellationToken)
+ : new HistorianWcfTagWriteOrchestrator(_options).DeleteTagAsync(tagName, cancellationToken);
}
///
@@ -325,7 +331,9 @@ public sealed class HistorianClient : IAsyncDisposable
public Task RenameTagsAsync(IReadOnlyList<(string OldName, string NewName)> pairs, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(pairs);
- return new HistorianWcfTagWriteOrchestrator(_options).RenameTagsAsync(pairs, cancellationToken);
+ return _options.Transport == HistorianTransport.RemoteGrpc
+ ? new Grpc.HistorianGrpcTagWriteOrchestrator(_options).RenameTagsAsync(pairs, cancellationToken)
+ : new HistorianWcfTagWriteOrchestrator(_options).RenameTagsAsync(pairs, cancellationToken);
}
public ValueTask DisposeAsync()
diff --git a/src/AVEVA.Historian.Client/Protocol/Historian2020ProtocolDialect.cs b/src/AVEVA.Historian.Client/Protocol/Historian2020ProtocolDialect.cs
index 82b4290..b13d90b 100644
--- a/src/AVEVA.Historian.Client/Protocol/Historian2020ProtocolDialect.cs
+++ b/src/AVEVA.Historian.Client/Protocol/Historian2020ProtocolDialect.cs
@@ -95,21 +95,27 @@ internal sealed class Historian2020ProtocolDialect
{
cancellationToken.ThrowIfCancellationRequested();
ArgumentException.ThrowIfNullOrWhiteSpace(name);
- return Wcf.HistorianWcfStatusClient.GetRuntimeParameterAsync(_options, name, cancellationToken);
+ return UseGrpc
+ ? HistorianGrpcStatusClient.GetRuntimeParameterAsync(_options, name, cancellationToken)
+ : Wcf.HistorianWcfStatusClient.GetRuntimeParameterAsync(_options, name, cancellationToken);
}
public Task> GetTagExtendedPropertiesAsync(string tag, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
ArgumentException.ThrowIfNullOrWhiteSpace(tag);
- return Wcf.HistorianWcfTagExtendedPropertyClient.GetTagExtendedPropertiesAsync(_options, tag, cancellationToken);
+ return UseGrpc
+ ? Grpc.HistorianGrpcTagClient.GetTagExtendedPropertiesAsync(_options, tag, cancellationToken)
+ : Wcf.HistorianWcfTagExtendedPropertyClient.GetTagExtendedPropertiesAsync(_options, tag, cancellationToken);
}
public Task ExecuteSqlCommandAsync(string command, HistorianSqlExecuteOption option, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
ArgumentException.ThrowIfNullOrWhiteSpace(command);
- return Wcf.HistorianWcfSqlClient.ExecuteSqlCommandAsync(_options, command, option, cancellationToken);
+ return UseGrpc
+ ? Grpc.HistorianGrpcSqlClient.ExecuteSqlCommandAsync(_options, command, option, cancellationToken)
+ : Wcf.HistorianWcfSqlClient.ExecuteSqlCommandAsync(_options, command, option, cancellationToken);
}
private static async IAsyncEnumerable Missing(