Verbesserung von Stabilität und Fehlerbehandlung

- Implementierung von `IDisposable` in der Klasse `Client`, um Ressourcen wie Timer und Kanäle ordnungsgemäß freizugeben.
- Einführung von `ActiveClients` und `ClientStatus` zur Verfolgung und Statusanzeige aktiver Clients.
- Verbesserte Fehlerbehandlung mit der Methode `HandleCommunicationError`, die Kommunikationsfehler protokolliert, den Client offline setzt und einen Reconnect auslöst.
- Optimierung der Reconnect-Logik mit asynchronen Wiederverbindungsversuchen und gestaffelten Verzögerungen.
- Erweiterung der Konstruktoren in `Client` um einen optionalen `pClientName`-Parameter und Generierung eines eindeutigen `ClientId`.
- Verbesserte Timer-Logik und asynchrone Verarbeitung in `UpdateTimer_Elapsed`.
- Einführung der Methode `EnsureAvailable`, um sicherzustellen, dass der Kanal vor Service-Aufrufen verfügbar ist.
- Hinzufügen asynchroner Varianten für Service-Methoden (`GetDatatable`, `GetScalarValue`, `ExecuteNonQuery`) und Fallback auf direkte Datenbankzugriffe.
- Verbesserte Timeout-Einstellungen und Verbindungsverwaltung in der Klasse `Channel`.
- Erweiterungen in `DatabaseWithFallback` für asynchrone Methoden und detaillierteres Logging.
- Aktualisierung der Assembly-Version auf `2.7.0`.
This commit is contained in:
Developer01
2026-09-22 16:36:20 +02:00
parent 857e1366fe
commit ed655879f9
5 changed files with 603 additions and 165 deletions

View File

@@ -7,6 +7,7 @@ Imports DigitalData.Modules.EDMI.API.Rights
Imports DigitalData.Modules.Logging
Public Class Client
Implements IDisposable
' Constants
Private Const UPDATE_INTERVAL_IN_MINUTES As Integer = 1
@@ -23,9 +24,53 @@ Public Class Client
Private _CachedTables As New List(Of String)
Private _IsOnline As Boolean
Private _Channel As IEDMIServiceChannel
Private _ClientName As String
' Static Registry
Private Shared ReadOnly _ActiveClients As New Collections.Concurrent.ConcurrentDictionary(Of String, ClientStatus)
Private ReadOnly _ClientId As String ' ← Neu: eindeutiger interner Key
Public Shared ReadOnly Property ActiveClients As IReadOnlyDictionary(Of String, ClientStatus)
Get
Return _ActiveClients
End Get
End Property
''' <summary>
''' Returns a snapshot of all currently registered client instances
''' </summary>
Public Shared Function GetAllClientStatus() As List(Of ClientStatus)
Return _ActiveClients.Values.ToList()
End Function
' Update Timer
Private WithEvents UpdateTimer As New Timers.Timer
Private _Disposed As Boolean = False
''' <summary>
''' Closes the channel and releases all resources
''' </summary>
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
Protected Overridable Sub Dispose(disposing As Boolean)
If _Disposed Then Return
If disposing Then
UpdateTimer.Stop()
UpdateTimer.Dispose()
If _Channel IsNot Nothing Then
Channel.CloseChannel(_Channel)
_Channel = Nothing
End If
' Aus Registry austragen
Dim oRemoved As ClientStatus = Nothing
_ActiveClients.TryRemove(_ClientId, oRemoved)
Logger.Debug("Client [{0}] unregistered. Total active clients: [{1}]", _ClientName, _ActiveClients.Count)
Logger.Debug("Client disposed. Channel closed.")
End If
_Disposed = True
End Sub
' Public Variables
Public ReadOnly Property CachedTables
@@ -72,9 +117,12 @@ Public Class Client
''' </summary>
''' <param name="pLogConfig">LogConfig object</param>
''' <param name="pServiceAdress">The IP address/hostname and port, separated by semicolon or colon, ex. 1.2.3.4:9000</param>
Public Sub New(pLogConfig As LogConfig, pServiceAdress As String)
''' <param name="pClientName">Optional identifier for this client instance (e.g. application name or hostname)</param>
Public Sub New(pLogConfig As LogConfig, pServiceAdress As String, Optional pClientName As String = "")
LogConfig = pLogConfig
Logger = pLogConfig.GetLogger()
_ClientName = If(String.IsNullOrWhiteSpace(pClientName), Environment.MachineName, pClientName)
_ClientId = $"{_ClientName}_{Guid.NewGuid().ToString("N").Substring(0, 8)}" ' ← eindeutig
UpdateTimer.Interval = 60 * 1000 * UPDATE_INTERVAL_IN_MINUTES
UpdateTimer.Start()
@@ -85,8 +133,8 @@ Public Class Client
ChannelManager = New Channel(pLogConfig, _ServerAddress)
AddHandler ChannelManager.Reconnect, AddressOf Reconnect
Logger.Debug("Ready for connection to Service at: [{0}:{1}]", _ServerAddress.Host, _ServerAddress.Port)
Logger.Debug("Ready for connection to Service at: [{0}:{1}] | ClientName=[{2}] | ClientId=[{3}]",
_ServerAddress.Host, _ServerAddress.Port, _ClientName, _ClientId)
Catch ex As Exception
Logger.Error(ex)
End Try
@@ -98,9 +146,12 @@ Public Class Client
''' <param name="pLogConfig">LogConfig object</param>
''' <param name="pIPAddress">The IP address to connect to</param>
''' <param name="pPortNumber">The Port number to use for the connection</param>
Public Sub New(pLogConfig As LogConfig, pIPAddress As String, pPortNumber As Integer)
''' <param name="pClientName">Optional identifier for this client instance (e.g. application name or hostname)</param>
Public Sub New(pLogConfig As LogConfig, pIPAddress As String, pPortNumber As Integer, Optional pClientName As String = "")
LogConfig = pLogConfig
Logger = pLogConfig.GetLogger()
_ClientName = If(String.IsNullOrWhiteSpace(pClientName), Environment.MachineName, pClientName)
_ClientId = $"{_ClientName}_{Guid.NewGuid().ToString("N").Substring(0, 8)}" ' ← eindeutig
UpdateTimer.Interval = 60 * 1000 * UPDATE_INTERVAL_IN_MINUTES
UpdateTimer.Start()
@@ -111,13 +162,12 @@ Public Class Client
ChannelManager = New Channel(pLogConfig, _ServerAddress)
AddHandler ChannelManager.Reconnect, AddressOf Reconnect
Logger.Debug("Ready for connection to Service at: [{0}:{1}]", _ServerAddress.Host, _ServerAddress.Port)
Logger.Debug("Ready for connection to Service at: [{0}:{1}] | ClientName=[{2}] | ClientId=[{3}]",
_ServerAddress.Host, _ServerAddress.Port, _ClientName, _ClientId)
Catch ex As Exception
Logger.Error(ex)
End Try
End Sub
''' <summary>
''' Connect to the service
''' </summary>
@@ -133,13 +183,26 @@ Public Class Client
If oResponse.OK Then
_ClientConfig = oResponse.ClientConfig
Else
Logger.Warn("Client Configuration could not be loaded: [{0}]", oResponse.ErrorMessage)
Logger.Warn("⚠Client Configuration could not be loaded: [{0}]", oResponse.ErrorMessage)
End If
Logger.Info($"Connection to AppService [{ServerAddress}] successfully established!")
Logger.Info("✔✔Connection to AppService [{0}] successfully established! | Client=[{1}] | Id=[{2}]",
ServerAddress, _ClientName, _ClientId)
_IsOnline = True
' CachedTables sofort befüllen – nicht auf den ersten Timer-Tick warten
Try
Dim oTables As String() = _Channel.GetCachedTables()
_CachedTables = oTables.Select(Function(t) t.ToUpper).ToList()
Logger.Debug("✔ CachedTables loaded on connect: [{0}]", _CachedTables.Count)
Catch ex As Exception
Logger.Warn("⚠ CachedTables could not be loaded on connect: {0}", ex.Message)
_CachedTables = New List(Of String)
End Try
' In Registry eintragen
_ActiveClients(_ClientId) = GetStatus()
Logger.Info("Client [{0}] registered with Id=[{1}]. Total active clients: [{2}]", _ClientName, _ClientId, _ActiveClients.Count)
Return True
Catch ex As Exception
_IsOnline = False
@@ -152,32 +215,83 @@ Public Class Client
''' <summary>
''' Aborts the channel and creates a new connection
''' </summary>
Public Sub Reconnect()
Logger.Warn("Connection faulted. Trying to reconnect..")
Public Async Sub Reconnect()
Logger.Warn("⚠ Connection faulted. Trying to reconnect..")
Try
_Channel.Abort()
_Channel = ChannelManager.GetChannel()
_Channel.Open()
Dim oRetryDelays = {2, 5, 15, 30}
_IsOnline = True
Catch ex As Exception
Logger.Error(ex)
End Try
For Each oDelay In oRetryDelays
Try
Await Task.Delay(TimeSpan.FromSeconds(oDelay))
Logger.Debug($"Reconnect attempt after {oDelay}s delay...")
Channel.CloseChannel(_Channel) ' Sauber schließen statt Abort
_Channel = ChannelManager.GetChannel()
_Channel.Open()
_IsOnline = True
Logger.Info("Reconnect successful.")
Return
Catch ex As Exception
Logger.Warn($"⚠ Reconnect failed: {ex.Message}")
_IsOnline = False
End Try
Next
Logger.Error("All reconnect attempts failed. Service unavailable.")
End Sub
Private Async Function UpdateTimer_Elapsed(sender As Object, e As Timers.ElapsedEventArgs) As Task Handles UpdateTimer.Elapsed
Private Async Sub UpdateTimer_Elapsed(sender As Object, e As Timers.ElapsedEventArgs) Handles UpdateTimer.Elapsed
Try
If _Channel Is Nothing OrElse _Channel.State <> CommunicationState.Opened Then
Logger.Warn("⚠ UpdateTimer skipped: Channel not in Opened state [{0}]", If(_Channel IsNot Nothing, _Channel.State.ToString(), "null"))
_IsOnline = False
Reconnect()
Return
End If
If Not _IsOnline Then
Logger.Debug("UpdateTimer skipped: Client is offline.")
Return
End If
Dim oTables As String() = Await _Channel.GetCachedTablesAsync()
' ◄ NullReferenceException-Schutz: Service kann Nothing zurückgeben
If oTables Is Nothing Then
Logger.Warn("⚠ UpdateTimer: GetCachedTablesAsync returned Nothing.")
_CachedTables = New List(Of String)
Return
End If
_CachedTables = oTables.
Select(Function(table) table.ToUpper).
ToList()
Select(Function(table) table.ToUpper).
ToList()
Logger.Info("✔ Channel state: [{0}] | Active connections: [{1}] | CachedTables: [{2}]",
_Channel.State,
Channel.ActiveConnections,
_CachedTables.Count)
If _ActiveClients.ContainsKey(_ClientId) Then
_ActiveClients(_ClientId) = GetStatus()
End If
Catch ex As TimeoutException
' Stale TCP connection (z.B. Firewall/Citrix Idle-Timeout)
Logger.Warn("⚠ UpdateTimer: TCP connection lost (idle timeout / firewall). Triggering reconnect...")
_IsOnline = False
Reconnect()
Catch ex As CommunicationException
Logger.Warn("⚠ UpdateTimer: Channel faulted. Triggering reconnect...")
_IsOnline = False
Reconnect()
Catch ex As Exception
Logger.Warn("Update of CachedTable was not successful!")
Logger.Warn("⚠ Update of CachedTable was not successful!")
Logger.Error(ex)
_CachedTables = New List(Of String)
End Try
End Function
End Sub
''' <summary>
''' Imports a file from a filepath, creating a IDB ObjectId and Filesystem Object
@@ -460,7 +574,7 @@ Public Class Client
Dim oResult = _Channel.ExecuteNonQuery_MSSQL_IDB(oSql)
If oResult.OK = False Then
Logger.Warn("Error while deleting Term object")
Logger.Warn("⚠ Error while IDBNewObjectData")
Logger.Error(oResult.ErrorMessage)
End If
@@ -477,7 +591,7 @@ Public Class Client
Dim oResult = _Channel.ExecuteNonQuery_MSSQL_IDB(oDELSQL)
If oResult.OK = False Then
Logger.Warn("Error while deleting Term object")
Logger.Warn("⚠ Error while deleting Term object")
Logger.Error(oResult.ErrorMessage)
End If
@@ -490,268 +604,351 @@ Public Class Client
#Region "GetDatatable"
Public Function GetDatatableFromIDB(pSQL As String) As GetDatatableResponse
EnsureAvailable()
Try
Dim oResponse = _Channel.ReturnDatatable(New GetDatatableRequest() With {
Return _Channel.ReturnDatatable(New GetDatatableRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB
})
Return oResponse
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableFromIDB))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetDatatableFromIDB))
Catch ex As Exception
Logger.Error(ex)
Throw ex
Throw
End Try
End Function
Public Function GetDatatableFromECM(pSQL As String) As GetDatatableResponse
EnsureAvailable()
Try
Dim oResponse = _Channel.ReturnDatatable(New GetDatatableRequest() With {
Return _Channel.ReturnDatatable(New GetDatatableRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.ECM
})
Return oResponse
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableFromECM))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetDatatableFromECM))
Catch ex As Exception
Logger.Error(ex)
Throw ex
Throw
End Try
End Function
Public Function GetDatatableFromConnection(pSQL As String, pConnectionId As Integer) As GetDatatableResponse
EnsureAvailable()
Try
Dim oResponse = _Channel.ReturnDatatable(New GetDatatableRequest() With {
Return _Channel.ReturnDatatable(New GetDatatableRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.None,
.ConnectionId = pConnectionId
})
Return oResponse
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableFromConnection))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetDatatableFromConnection))
Catch ex As Exception
Logger.Error(ex)
Throw ex
Throw
End Try
End Function
Public Async Function GetDatatableFromIDBAsync(pSQL As String) As Task(Of GetDatatableResponse)
EnsureAvailable()
Try
Dim oResponse = Await _Channel.ReturnDatatableAsync(New GetDatatableRequest() With {
Return Await _Channel.ReturnDatatableAsync(New GetDatatableRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB
})
Return oResponse
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableFromIDBAsync))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetDatatableFromIDBAsync))
Catch ex As Exception
Logger.Error(ex)
Throw ex
Throw
End Try
End Function
Public Async Function GetDatatableFromECMAsync(pSQL As String) As Task(Of GetDatatableResponse)
EnsureAvailable()
Try
Dim oResponse = Await _Channel.ReturnDatatableAsync(New GetDatatableRequest() With {
Logger.Debug("GetDatatableFromECMAsync will be called with SQL [{0}]", pSQL)
Return Await _Channel.ReturnDatatableAsync(New GetDatatableRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.ECM
})
Return oResponse
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableFromECMAsync))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetDatatableFromECMAsync))
Catch ex As Exception
Logger.Error(ex)
Throw ex
Throw
End Try
End Function
Public Async Function GetDatatableFromConnectionAsync(pSQL As String, Optional pConnectionId As Integer = 0) As Task(Of GetDatatableResponse)
EnsureAvailable()
Try
Dim oResponse = Await _Channel.ReturnDatatableAsync(New GetDatatableRequest() With {
Return Await _Channel.ReturnDatatableAsync(New GetDatatableRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.None,
.ConnectionId = pConnectionId
})
Return oResponse
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableFromConnectionAsync))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetDatatableFromConnectionAsync))
Catch ex As Exception
Logger.Error(ex)
Throw ex
Throw
End Try
End Function
#End Region
#Region "GetScalarValue"
Public Function GetScalarValueFromIDB(pSQL As String) As GetScalarValueResponse
EnsureAvailable()
Try
Dim oResponse = _Channel.ReturnScalarValue(New GetScalarValueRequest() With {
Return _Channel.ReturnScalarValue(New GetScalarValueRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB
})
Return oResponse
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetScalarValueFromIDB))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetScalarValueFromIDB))
Catch ex As Exception
Logger.Error(ex)
Throw ex
Throw
End Try
End Function
Public Function GetScalarValueFromECM(pSQL As String) As GetScalarValueResponse
EnsureAvailable()
Try
Dim oResponse = _Channel.ReturnScalarValue(New GetScalarValueRequest() With {
Return _Channel.ReturnScalarValue(New GetScalarValueRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.ECM
})
Return oResponse
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetScalarValueFromECM))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetScalarValueFromECM))
Catch ex As Exception
Logger.Error(ex)
Throw ex
Throw
End Try
End Function
Public Function GetScalarValueFromConnection(pSQL As String, pConnectionId As Integer) As GetScalarValueResponse
EnsureAvailable()
Try
Dim oResponse = _Channel.ReturnScalarValue(New GetScalarValueRequest() With {
Return _Channel.ReturnScalarValue(New GetScalarValueRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.None,
.ConnectionId = pConnectionId
})
Return oResponse
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetScalarValueFromConnection))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetScalarValueFromConnection))
Catch ex As Exception
Logger.Error(ex)
Throw ex
Throw
End Try
End Function
Public Async Function GetScalarValueFromIDBAsync(pSQL As String) As Task(Of GetScalarValueResponse)
EnsureAvailable()
Try
Dim oResponse = Await _Channel.ReturnScalarValueAsync(New GetScalarValueRequest() With {
Return Await _Channel.ReturnScalarValueAsync(New GetScalarValueRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB
})
Return oResponse
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetScalarValueFromIDBAsync))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetScalarValueFromIDBAsync))
Catch ex As Exception
Logger.Error(ex)
Throw ex
Throw
End Try
End Function
Public Async Function GetScalarValueFromECMAsync(pSQL As String) As Task(Of GetScalarValueResponse)
EnsureAvailable()
Try
Dim oResponse = Await _Channel.ReturnScalarValueAsync(New GetScalarValueRequest() With {
Return Await _Channel.ReturnScalarValueAsync(New GetScalarValueRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.ECM
})
Return oResponse
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetScalarValueFromECMAsync))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetScalarValueFromECMAsync))
Catch ex As Exception
Logger.Error(ex)
Throw ex
Throw
End Try
End Function
Public Async Function GetScalarValueFromConnectionAsync(pSQL As String, pConnectionId As Integer) As Task(Of GetScalarValueResponse)
EnsureAvailable()
Try
Dim oResponse = Await _Channel.ReturnScalarValueAsync(New GetScalarValueRequest() With {
Return Await _Channel.ReturnScalarValueAsync(New GetScalarValueRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.None,
.ConnectionId = pConnectionId
})
Return oResponse
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetScalarValueFromConnectionAsync))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetScalarValueFromConnectionAsync))
Catch ex As Exception
Logger.Error(ex)
Throw ex
Throw
End Try
End Function
#End Region
#Region "ExecuteNonQuery"
Public Function ExecuteNonQueryFromIDB(pSQL As String) As ExecuteNonQueryResponse
EnsureAvailable()
Try
Dim oResponse = _Channel.ExecuteNonQuery(New ExecuteNonQueryRequest() With {
Return _Channel.ExecuteNonQuery(New ExecuteNonQueryRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB
})
Return oResponse
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDB))
Throw
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDB))
Throw
Catch ex As Exception
Logger.Error(ex)
Throw ex
Throw
End Try
End Function
Public Function ExecuteNonQueryFromECM(pSQL As String) As ExecuteNonQueryResponse
EnsureAvailable()
Try
Dim oResponse = _Channel.ExecuteNonQuery(New ExecuteNonQueryRequest() With {
Return _Channel.ExecuteNonQuery(New ExecuteNonQueryRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.ECM
})
Return oResponse
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromECM))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromECM))
Catch ex As Exception
Logger.Error(ex)
Throw ex
Throw
End Try
End Function
Public Function ExecuteNonQueryFromConnection(pSQL As String, pConnectionId As Integer) As ExecuteNonQueryResponse
EnsureAvailable()
Try
Dim oResponse = _Channel.ExecuteNonQuery(New ExecuteNonQueryRequest() With {
Return _Channel.ExecuteNonQuery(New ExecuteNonQueryRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.None,
.ConnectionId = pConnectionId
})
Return oResponse
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromConnection))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromConnection))
Catch ex As Exception
Logger.Error(ex)
Throw ex
Throw
End Try
End Function
Public Async Function ExecuteNonQueryFromIDBAsync(pSQL As String) As Task(Of ExecuteNonQueryResponse)
EnsureAvailable()
Try
Dim oResponse = Await _Channel.ExecuteNonQueryAsync(New ExecuteNonQueryRequest() With {
Return Await _Channel.ExecuteNonQueryAsync(New ExecuteNonQueryRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB
})
Return oResponse
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDBAsync))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDBAsync))
Catch ex As Exception
Logger.Error(ex)
Throw ex
Throw
End Try
End Function
Public Async Function ExecuteNonQueryFromECMAsync(pSQL As String) As Task(Of ExecuteNonQueryResponse)
EnsureAvailable()
Try
Dim oResponse = Await _Channel.ExecuteNonQueryAsync(New ExecuteNonQueryRequest() With {
Return Await _Channel.ExecuteNonQueryAsync(New ExecuteNonQueryRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.ECM
})
Return oResponse
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromECMAsync))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromECMAsync))
Catch ex As Exception
Logger.Error(ex)
Throw ex
Throw
End Try
End Function
Public Async Function ExecuteNonQueryFromConnectionAsync(pSQL As String, pConnectionId As Integer) As Task(Of ExecuteNonQueryResponse)
EnsureAvailable()
Try
Dim oResponse = Await _Channel.ExecuteNonQueryAsync(New ExecuteNonQueryRequest() With {
Return Await _Channel.ExecuteNonQueryAsync(New ExecuteNonQueryRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.None,
.ConnectionId = pConnectionId
})
Return oResponse
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromConnectionAsync))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromConnectionAsync))
Catch ex As Exception
Logger.Error(ex)
Throw ex
Throw
End Try
End Function
#End Region
Public Function GetDatatableByName(DatatableName As String, Optional FilterExpression As String = "", Optional SortByColumn As String = "") As TableResult
EnsureAvailable()
Try
Dim oResponse = _Channel.ReturnDatatableFromCache(DatatableName, FilterExpression, SortByColumn)
Return oResponse
Return _Channel.ReturnDatatableFromCache(DatatableName, FilterExpression, SortByColumn)
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableByName))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetDatatableByName))
Catch ex As Exception
Logger.Error(ex)
Throw ex
Throw
End Try
End Function
Public Async Function GetDatatableByNameAsync(DatatableName As String, Optional FilterExpression As String = "", Optional SortByColumn As String = "") As Task(Of TableResult)
EnsureAvailable()
Try
Dim oResponse = Await _Channel.ReturnDatatableFromCacheAsync(DatatableName, FilterExpression, SortByColumn)
Return oResponse
Return Await _Channel.ReturnDatatableFromCacheAsync(DatatableName, FilterExpression, SortByColumn)
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableByNameAsync))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetDatatableByNameAsync))
Catch ex As Exception
Logger.Error(ex)
Throw ex
Throw
End Try
End Function
@@ -819,6 +1016,40 @@ Public Class Client
Private Function GetUserName(pOverrideName) As String
Return ObjectEx.NotNull(pOverrideName, Environment.UserName)
End Function
''' <summary>
''' Throws InvalidOperationException if the channel is not ready for calls
''' </summary>
Private Sub EnsureAvailable()
If _Channel Is Nothing Then
Throw New InvalidOperationException("Channel is not initialized. Call Connect() first.")
End If
If _Channel.State <> CommunicationState.Opened Then
Throw New InvalidOperationException($"Channel is not available. State=[{_Channel.State}]")
End If
If Not _IsOnline Then
Throw New InvalidOperationException("Client is offline.")
End If
End Sub
''' <summary>
''' Logs the communication error, sets offline state, triggers reconnect and rethrows
''' </summary>
Private Sub HandleCommunicationError(ex As Exception, callerName As String)
Select Case True
Case TypeOf ex Is EndpointNotFoundException
Logger.Warn("⚠ Service not reachable (EndpointNotFoundException in {0}). Triggering reconnect...", callerName)
Case TypeOf ex Is CommunicationException
Logger.Warn("⚠ in {0} during call. Triggering reconnect...", callerName)
Case Else
Logger.Warn("⚠ Unexpected exception in {0} during call. Triggering reconnect...", callerName)
Logger.Error(ex)
End Select
_IsOnline = False
Reconnect()
Throw ex
End Sub
#End Region
#Region "Response Classes"
@@ -887,34 +1118,74 @@ Public Class Client
Select Case Type
Case AttributeTypeName.VECTOR_INTEGER
Return ValueBigInt
Case AttributeTypeName.BIG_INTEGER
Return ValueBigInt
Case AttributeTypeName.VECTOR_STRING
Return ValueText
Case AttributeTypeName.VARCHAR
Return ValueText
Case AttributeTypeName.BIT
Return IIf(ValueBigInt = 1, True, False)
Case AttributeTypeName.DATE
Return ValueDate
Case AttributeTypeName.DATETIME
Return ValueDate
Case AttributeTypeName.DECIMAL
Return ValueDecimal
Case AttributeTypeName.FLOAT
Return ValueDecimal
Case Else
Return Nothing
End Select
End Function
End Class ' ← ObjectAttribute endet hier
''' <summary>
''' Returns a detailed status snapshot of the current client state
''' </summary>
Public Function GetStatus() As ClientStatus
Return New ClientStatus() With {
.IsOnline = _IsOnline,
.ClientId = _ClientId, ' ← Neu
.ClientName = _ClientName,
.ServerAddress = Me.ServerAddress,
.ChannelState = If(_Channel IsNot Nothing, _Channel.State.ToString(), "null"),
.ActiveConnections = Channel.ActiveConnections,
.CachedTableCount = _CachedTables.Count,
.CachedTables = _CachedTables.ToList(),
.HasClientConfig = _ClientConfig IsNot Nothing,
.IsDisposed = _Disposed,
.TimerEnabled = UpdateTimer.Enabled,
.TimerIntervalSeconds = CInt(UpdateTimer.Interval / 1000),
.Timestamp = DateTime.Now
}
End Function
Public Class ClientStatus
Public Property IsOnline As Boolean
Public Property ClientId As String ' ← Neu
Public Property ClientName As String
Public Property ServerAddress As String
Public Property ChannelState As String
Public Property ActiveConnections As Integer
Public Property CachedTableCount As Integer
Public Property CachedTables As List(Of String)
Public Property HasClientConfig As Boolean
Public Property IsDisposed As Boolean
Public Property TimerEnabled As Boolean
Public Property TimerIntervalSeconds As Integer
Public Property Timestamp As DateTime
Public Overrides Function ToString() As String
Return String.Format(
"[ClientStatus] ClientName=[{0}] Id=[{1}] Online=[{2}] Channel=[{3}]",
ClientName, ClientId, IsOnline, ChannelState)
End Function
Public Function Client_Who_ToString() As String
Return String.Format(
"[ClientStatus] Client=[{0}] Id=[{1}] Online=[{2}] Server=[{3}] Channel=[{4}]",
ClientName, ClientId, IsOnline, ServerAddress, ChannelState)
End Function
End Class
End Class