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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user