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

View File

@@ -1,4 +1,5 @@
Imports System.ServiceModel
Imports System.Threading
Imports System.Xml
Imports DigitalData.Modules.Base
Imports DigitalData.Modules.EDMI.API.EDMIServiceReference
@@ -10,31 +11,65 @@ Public Class Channel
Private ReadOnly ChannelFactory As ChannelFactory(Of IEDMIServiceChannel)
Public Event Reconnect As EventHandler
' Verbindungszähler
Private Shared _ActiveConnections As Integer = 0
Public Shared ReadOnly Property ActiveConnections As Integer
Get
Return _ActiveConnections
End Get
End Property
Public Shared Function GetBinding(Optional AuthenticationMode As TcpClientCredentialType = TcpClientCredentialType.Windows) As NetTcpBinding
Return New NetTcpBinding() With {
.MaxReceivedMessageSize = Constants.ChannelSettings.MAX_RECEIVED_MESSAGE_SIZE,
.MaxBufferSize = Constants.ChannelSettings.MAX_BUFFER_SIZE,
.MaxBufferPoolSize = Constants.ChannelSettings.MAX_BUFFER_POOL_SIZE,
.TransferMode = TransferMode.Streamed,
.Security = New NetTcpSecurity() With {
.Mode = SecurityMode.Transport,
.Transport = New TcpTransportSecurity() With {
.ClientCredentialType = AuthenticationMode
}
},
.ReaderQuotas = New XmlDictionaryReaderQuotas() With {
.MaxArrayLength = Constants.ChannelSettings.MAX_ARRAY_LENGTH,
.MaxStringContentLength = Constants.ChannelSettings.MAX_STRING_CONTENT_LENGTH
.MaxReceivedMessageSize = Constants.ChannelSettings.MAX_RECEIVED_MESSAGE_SIZE,
.MaxBufferSize = Constants.ChannelSettings.MAX_BUFFER_SIZE,
.MaxBufferPoolSize = Constants.ChannelSettings.MAX_BUFFER_POOL_SIZE,
.ListenBacklog = Constants.ChannelSettings.MAX_CONNECTIONS,
.MaxConnections = Constants.ChannelSettings.MAX_CONNECTIONS,
.TransferMode = TransferMode.Streamed,
.SendTimeout = TimeSpan.FromSeconds(30), ' ← War: 5min → Schnellere Fehlererkennung
.ReceiveTimeout = TimeSpan.FromMinutes(10), ' Keepalive < Firewall-Timeout (3600s) ✓
.OpenTimeout = TimeSpan.FromSeconds(30), ' ← Neu: Verbindungsaufbau begrenzen
.CloseTimeout = TimeSpan.FromSeconds(30),
.Security = New NetTcpSecurity() With {
.Mode = SecurityMode.Transport,
.Transport = New TcpTransportSecurity() With {
.ClientCredentialType = AuthenticationMode
}
},
.ReaderQuotas = New XmlDictionaryReaderQuotas() With {
.MaxArrayLength = Constants.ChannelSettings.MAX_ARRAY_LENGTH,
.MaxStringContentLength = Constants.ChannelSettings.MAX_STRING_CONTENT_LENGTH
}
}
End Function
Public Sub New(pLogConfig As LogConfig, pServerAddress As ServerAddressStruct)
MyBase.New(pLogConfig)
ChannelFactory = GetChannelFactory(pServerAddress)
End Sub
''' <summary>
''' Closes a channel gracefully, falls back to Abort on error
''' </summary>
Public Shared Sub CloseChannel(pChannel As IEDMIServiceChannel)
If pChannel Is Nothing Then Return
Try
If pChannel.State = CommunicationState.Opened Then
pChannel.Close()
ElseIf pChannel.State = CommunicationState.Faulted OrElse
pChannel.State = CommunicationState.Closing Then
pChannel.Abort()
End If
' CommunicationState.Created / Closed → nichts tun, Zähler nicht betroffen
Catch ex As Exception
Try
pChannel.Abort()
Catch
' Ignore
End Try
End Try
End Sub
''' <summary>
''' Creates a channel and adds a Faulted-Handler
''' </summary>
@@ -44,21 +79,45 @@ Public Class Channel
Logger.Debug("Creating channel.")
Dim oChannel = ChannelFactory.CreateChannel()
AddHandler oChannel.Faulted, Sub() RaiseEvent Reconnect(Me, Nothing)
' Verbindung zählen beim Öffnen
AddHandler oChannel.Opened, Sub()
Dim oCount = Interlocked.Increment(_ActiveConnections)
Logger.Debug("Channel opened. Active connections: [{0}]", oCount)
End Sub
' Verbindung abziehen beim Schließen oder Fehler
AddHandler oChannel.Closed, Sub()
Dim oCount = Interlocked.Decrement(_ActiveConnections)
Logger.Debug("Channel closed. Active connections: [{0}]", oCount)
End Sub
AddHandler oChannel.Faulted, Sub()
Dim oCount = Interlocked.Decrement(_ActiveConnections)
Logger.Warn("Channel faulted. Active connections: [{0}]", oCount)
RaiseEvent Reconnect(Me, Nothing)
End Sub
Logger.Debug("Channel created. State=[{0}]", oChannel.State)
Return oChannel
Catch ex As Exception
Logger.Error(ex)
Throw ex
Throw
End Try
End Function
Private Function GetChannelFactory(pServerAddress As ServerAddressStruct) As ChannelFactory(Of IEDMIServiceChannel)
Dim oBinding = GetBinding()
Dim oAddress = New EndpointAddress($"net.tcp://{pServerAddress.Host}:{pServerAddress.Port}/DigitalData/Services/Main")
Logger.Debug("Creating ChannelFactory with Binding: SendTimeout=[{0}], ReceiveTimeout=[{1}], MaxConnections=[{2}], MaxBufferSize=[{3}], TransferMode=[{4}]",
oBinding.SendTimeout,
oBinding.ReceiveTimeout,
oBinding.MaxConnections,
oBinding.MaxBufferSize,
oBinding.TransferMode)
Dim oFactory = New ChannelFactory(Of IEDMIServiceChannel)(oBinding, oAddress)
Return oFactory
End Function
End Class

View File

@@ -1,4 +1,5 @@
Imports DigitalData.Modules.Database
Imports System.Data.SqlClient
Imports DigitalData.Modules.Database
Imports DigitalData.Modules.EDMI.API
Imports DigitalData.Modules.EDMI.API.Constants
Imports DigitalData.Modules.EDMI.API.EDMIServiceReference
@@ -78,7 +79,7 @@ Public Class DatabaseWithFallback
End Function
Public Async Function GetDatatableECMAsync(pSQL As String) As Task(Of DataTable)
Return Await Task.Run(Function() GetDatatableECM(pSQL))
Return Await GetDatatableFromServiceAsync(pSQL, Constants.DatabaseType.ECM, 0)
End Function
Public Function GetDatatableIDB(pSQL As String) As DataTable
@@ -86,7 +87,7 @@ Public Class DatabaseWithFallback
End Function
Public Async Function GetDatatableIDBAsync(pSQL As String) As Task(Of DataTable)
Return Await Task.Run(Function() GetDatatableIDB(pSQL))
Return Await GetDatatableFromServiceAsync(pSQL, Constants.DatabaseType.IDB, 0)
End Function
Public Function GetDatatableWithConnection(pSQL As String, pConnectionId As Integer) As DataTable
@@ -94,7 +95,7 @@ Public Class DatabaseWithFallback
End Function
Public Async Function GetDatatableWithConnectionAsync(pSQL As String, pConnectionId As Integer) As Task(Of DataTable)
Return Await Task.Run(Function() GetDatatableWithConnection(pSQL, pConnectionId))
Return Await GetDatatableFromServiceAsync(pSQL, Constants.DatabaseType.None, pConnectionId)
End Function
#End Region
@@ -104,7 +105,7 @@ Public Class DatabaseWithFallback
End Function
Public Async Function GetScalarValueECMAsync(pSQL As String) As Task(Of Object)
Return Await Task.Run(Function() GetScalarValueECM(pSQL))
Return Await GetScalarValueFromServiceAsync(pSQL, Constants.DatabaseType.ECM, 0)
End Function
Public Function GetScalarValueIDB(pSQL As String) As Object
@@ -112,7 +113,7 @@ Public Class DatabaseWithFallback
End Function
Public Async Function GetScalarValueIDBAsync(pSQL As String) As Task(Of Object)
Return Await Task.Run(Function() GetScalarValueIDB(pSQL))
Return Await GetScalarValueFromServiceAsync(pSQL, Constants.DatabaseType.IDB, 0)
End Function
Public Function GetScalarValueWithConnection(pSQL As String, pConnectionId As Integer) As Object
@@ -120,7 +121,7 @@ Public Class DatabaseWithFallback
End Function
Public Async Function GetScalarValueWithConnectionAsync(pSQL As String, pConnectionId As Integer) As Task(Of Object)
Return Await Task.Run(Function() GetScalarValueWithConnection(pSQL, pConnectionId))
Return Await GetScalarValueFromServiceAsync(pSQL, Constants.DatabaseType.None, pConnectionId)
End Function
#End Region
@@ -130,7 +131,7 @@ Public Class DatabaseWithFallback
End Function
Public Async Function ExecuteNonQueryECMAsync(pSQL As String) As Task(Of Boolean)
Return Await Task.Run(Function() ExecuteNonQueryECM(pSQL))
Return Await ExecuteNonQueryFromServiceAsync(pSQL, Constants.DatabaseType.ECM, 0)
End Function
Public Function ExecuteNonQueryIDB(pSQL As String) As Boolean
@@ -138,7 +139,7 @@ Public Class DatabaseWithFallback
End Function
Public Async Function ExecuteNonQueryIDBAsync(pSQL As String) As Task(Of Boolean)
Return Await Task.Run(Function() ExecuteNonQueryIDB(pSQL))
Return Await ExecuteNonQueryFromServiceAsync(pSQL, Constants.DatabaseType.IDB, 0)
End Function
Public Function ExecuteNonQueryWithConnection(pSQL As String, pConnectionId As Integer) As Boolean
@@ -146,7 +147,7 @@ Public Class DatabaseWithFallback
End Function
Public Async Function ExecuteNonQueryWithConnectionAsync(pSQL As String, pConnectionId As Integer) As Task(Of Boolean)
Return Await Task.Run(Function() ExecuteNonQueryWithConnection(pSQL, pConnectionId))
Return Await ExecuteNonQueryFromServiceAsync(pSQL, Constants.DatabaseType.None, pConnectionId)
End Function
#End Region
@@ -187,33 +188,34 @@ Public Class DatabaseWithFallback
Try
Dim oResult As DataTable = Nothing
Dim oTableResult As TableResult = Nothing
_Logger.Debug("GetDatatable: Fetching datatable [{0}] , Filter Expression [{1}], Sort By Column [{2}], Force Fallback [{3}]",
pDataTableName, pFilterExpression, pSortByColumn, pForceFallback)
' If there is no client, we assume there is no service (configured)
If _Client Is Nothing Then
_Logger.Debug("Client is empty, falling back to direct database access.")
_Logger.Debug("GetDatatable: Client is empty, falling back to direct database access.")
Return GetDatatableFromDatabase(pFallbackSQL, pFallbackType, pConnectionId)
End If
' If ForceFallback flag is set, we go to database immediately
If pForceFallback Or _ClientConfig.ForceDirectDatabaseAccess Then
_Logger.Debug("ForceFallback is True, falling back to direct database access.")
_Logger.Debug("GetDatatable: ForceFallback is True, falling back to direct database access.")
Return GetDatatableFromDatabase(pFallbackSQL, pFallbackType, pConnectionId)
End If
' If the table is not cached, we try going through the service
If Not IsTableCached(pDataTableName) Then
_Logger.Debug("Datatable is not chached, fetching data from service.")
_Logger.Debug("GetDatatable: Datatable [{0}] is not chached, fetching data from service.", pDataTableName)
Return GetDatatableFromService(pFallbackSQL, pFallbackType, pConnectionId)
End If
' If there is a proper ConnectionId, we try going through the service
If pConnectionId > 0 Then
_Logger.Debug("ConnectionId is set, fetching data from service.")
_Logger.Debug("GetDatatable: ConnectionId is set, fetching data from service.")
Return GetDatatableFromService(pFallbackSQL, pFallbackType, pConnectionId)
End If
Try
_Logger.Debug("Datatable is chached, fetching data from cache.")
_Logger.Debug("GetDatatable: Datatable is chached, fetching data from cache.")
oTableResult = _Client.GetDatatableByName(pDataTableName, pFilterExpression, pSortByColumn)
Catch ex As Exception
_Logger.Error(ex)
@@ -221,9 +223,10 @@ Public Class DatabaseWithFallback
End Try
If oTableResult Is Nothing OrElse oTableResult.OK = False Then
_Logger.Warn("Datatable [{0}] could not be fetched from AppServer Cache. Falling back to direct Database Access.", pDataTableName)
_Logger.Warn("⚠ GetDatatable: Datatable [{0}] could not be fetched from AppServer Cache. Falling back to direct Database Access.", pDataTableName)
Return GetDatatableFromDatabase(pFallbackSQL, pFallbackType, pConnectionId)
Else
_Logger.Debug("✔ GetDatatable: Datatable [{0}] fetched from AppServer Cache.", pDataTableName)
Return oTableResult.Table
End If
@@ -276,11 +279,13 @@ Public Class DatabaseWithFallback
' If there is no client, we assume there is no service (configured)
If _Client Is Nothing Then
_Logger.Debug("ExecuteNonQuery - Client is empty, falling back to direct database access.")
Return ExecuteNonQueryFromDatabase(pSQL, pDatabaseType, pConnectionId)
End If
' If ForceFallback flag is set, we go to database immediately
If pForceFallback Or _ClientConfig.ForceDirectDatabaseAccess Then
_Logger.Debug("ExecuteNonQuery - ForceFallback is set, falling back to direct database access.")
Return ExecuteNonQueryFromDatabase(pSQL, pDatabaseType, pConnectionId)
End If
@@ -294,7 +299,9 @@ Public Class DatabaseWithFallback
End Function
Private Function IsTableCached(pName As String) As Boolean
_Logger.Debug("IsTableCached: Checking if table [{0}] is cached.", pName)
If _Client Is Nothing Then
_Logger.Warn("⚠ IsTableCached: Client is nothing, cannot check if table is cached.")
Return False
End If
@@ -303,42 +310,74 @@ Public Class DatabaseWithFallback
Private Function GetDatatableFromService(pSQLCommand As String, DatabaseType As Constants.DatabaseType, pConnectionId As Integer) As DataTable
Try
_Logger.Debug("GetDatatableFromService: Fetching data from service [{0}] with Connection Id [{1}] and Command [{2}]", DatabaseType.ToString, pConnectionId, pSQLCommand)
Dim oResult As GetDatatableResponse = Nothing
Select Case DatabaseType
Case Constants.DatabaseType.ECM
oResult = _Client.GetDatatableFromECM(pSQLCommand)
Case Constants.DatabaseType.IDB
oResult = _Client.GetDatatableFromIDB(pSQLCommand)
Case Else
Return GetDatatableFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
End Select
If oResult Is Nothing Then
Throw New ApplicationException("Unexpected server error ocurred!")
End If
If oResult.OK = False Then
Throw New ApplicationException(oResult.ErrorMessage)
If oResult Is Nothing OrElse oResult.OK = False Then
_Logger.Warn("⚠ GetDatatableFromService: Service returned invalid response [{0}]. Falling back to direct database access.", If(oResult Is Nothing, "null", oResult.ErrorMessage))
Return GetDatatableFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
End If
Return oResult.Table
Catch ex As InvalidOperationException
_Logger.Warn("⚠ GetDatatableFromService skipped: [{0}] Falling back to direct database access.", ex.Message)
Return GetDatatableFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
Catch ex As Exception
_Logger.Warn("GetDatatableFromService failed. Falling back to direct database access.")
_Logger.Warn("⚠ GetDatatableFromService failed. Falling back to direct database access.")
_Logger.Error(ex)
Return GetDatatableFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
End Try
End Function
Private Async Function GetDatatableFromServiceAsync(pSQLCommand As String, DatabaseType As Constants.DatabaseType, pConnectionId As Integer) As Task(Of DataTable)
Dim oFallback As Boolean = False
_Logger.Debug("GetDatatableFromServiceAsync: GetDatatableFromServiceAsync called with SQL [{0}], DatabaseType [{1}], ConnectionId [{2}]", pSQLCommand, DatabaseType.ToString, pConnectionId)
If _Client Is Nothing OrElse _ClientConfig?.ForceDirectDatabaseAccess Then
Return Await Task.Run(Function() GetDatatableFromDatabase(pSQLCommand, DatabaseType, pConnectionId))
End If
Dim oResult As GetDatatableResponse = Nothing
Try
Select Case DatabaseType
Case Constants.DatabaseType.ECM
oResult = Await _Client.GetDatatableFromECMAsync(pSQLCommand)
Case Constants.DatabaseType.IDB
oResult = Await _Client.GetDatatableFromIDBAsync(pSQLCommand)
Case Else
oResult = Await _Client.GetDatatableFromConnectionAsync(pSQLCommand, pConnectionId)
End Select
If oResult Is Nothing OrElse oResult.OK = False Then
_Logger.Warn("⚠ GetDatatableFromServiceAsync failed. Falling back to direct database access.")
oFallback = True
End If
Catch ex As Exception
_Logger.Warn("⚠ GetDatatableFromServiceAsync failed. Falling back to direct database access.")
_Logger.Error(ex)
oFallback = True
End Try
If oFallback Then
Return Await Task.Run(Function() GetDatatableFromDatabase(pSQLCommand, DatabaseType, pConnectionId))
End If
Return oResult.Table
End Function
Private Function GetDatatableFromDatabase(pSQLCommand As String, pDatabaseType As Constants.DatabaseType, pConnectionId As Integer) As DataTable
Try
_Logger.Debug("Fetching data from database [{0}] with Connection Id [{0}]", pDatabaseType.ToString, pConnectionId)
_Logger.Debug("GetDatatableFromDatabase: Fetching data from database [{0}] with Connection Id [{1}] and Command [{2}]", pDatabaseType.ToString, pConnectionId, pSQLCommand)
Select Case pDatabaseType
Case Constants.DatabaseType.IDB
@@ -361,35 +400,68 @@ Public Class DatabaseWithFallback
End Try
End Function
Private Async Function GetScalarValueFromServiceAsync(pSQLCommand As String, DatabaseType As Constants.DatabaseType, pConnectionId As Integer) As Task(Of Object)
Dim oFallback As Boolean = False
_Logger.Debug("GetScalarValueFromServiceAsync called with SQL [{0}], DatabaseType [{1}], ConnectionId [{2}]", pSQLCommand, DatabaseType.ToString, pConnectionId)
If _Client Is Nothing OrElse _ClientConfig?.ForceDirectDatabaseAccess Then
Return Await Task.Run(Function() GetScalarValueFromDatabase(pSQLCommand, DatabaseType, pConnectionId))
End If
Dim oResult As GetScalarValueResponse = Nothing
Try
Select Case DatabaseType
Case Constants.DatabaseType.ECM
oResult = Await _Client.GetScalarValueFromECMAsync(pSQLCommand)
Case Constants.DatabaseType.IDB
oResult = Await _Client.GetScalarValueFromIDBAsync(pSQLCommand)
Case Else
oResult = Await _Client.GetScalarValueFromConnectionAsync(pSQLCommand, pConnectionId)
End Select
If oResult Is Nothing OrElse oResult.OK = False Then
_Logger.Warn("⚠ GetScalarValueFromServiceAsync failed. Falling back to direct database access.")
oFallback = True
End If
Catch ex As Exception
_Logger.Warn("⚠ GetScalarValueFromServiceAsync failed. Falling back to direct database access.")
_Logger.Error(ex)
oFallback = True
End Try
If oFallback Then
Return Await Task.Run(Function() GetScalarValueFromDatabase(pSQLCommand, DatabaseType, pConnectionId))
End If
Return oResult.Scalar
End Function
Private Function GetScalarValueFromService(pSQLCommand As String, DatabaseType As Constants.DatabaseType, pConnectionId As Integer) As Object
Try
Dim oResult As GetScalarValueResponse = Nothing
_Logger.Debug("GetScalarValueFromService [{0}] with Connection Id [{1}] and Command [{2}]", DatabaseType.ToString, pConnectionId, pSQLCommand)
Select Case DatabaseType
Case Constants.DatabaseType.ECM
oResult = _Client.GetScalarValueFromECM(pSQLCommand)
Case Constants.DatabaseType.IDB
oResult = _Client.GetScalarValueFromIDB(pSQLCommand)
Case Else
Return GetScalarValueFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
End Select
If oResult Is Nothing Then
Throw New ApplicationException("Unexpected server error ocurred!")
End If
If oResult.OK = False Then
Throw New ApplicationException(oResult.ErrorMessage)
If oResult Is Nothing OrElse oResult.OK = False Then
_Logger.Warn("⚠ GetScalarValueFromService: Service returned invalid response [{0}]. Falling back to direct database access.", If(oResult Is Nothing, "null", oResult.ErrorMessage))
Return GetScalarValueFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
End If
Return oResult.Scalar
Catch ex As InvalidOperationException
_Logger.Warn("⚠ GetScalarValueFromService skipped: [{0}] Falling back to direct database access.", ex.Message)
Return GetScalarValueFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
Catch ex As Exception
_Logger.Warn("GetScalarValueFromService failed. Falling back to direct database access.")
_Logger.Warn("⚠ GetScalarValueFromService failed. Falling back to direct database access.")
_Logger.Error(ex)
Return GetScalarValueFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
@@ -399,6 +471,7 @@ Public Class DatabaseWithFallback
Private Function GetScalarValueFromDatabase(pSQLCommand As String, DatabaseType As Constants.DatabaseType, pConnectionId As Integer) As Object
Try
_Logger.Debug("DBwFF GetScalarValueFromDatabase [{0}] with Connection Id [{1}] and Command [{2}]", DatabaseType.ToString, pConnectionId, pSQLCommand)
Select Case DatabaseType
Case Constants.DatabaseType.IDB
Return _DatabaseIDB.GetScalarValue(pSQLCommand)
@@ -419,35 +492,68 @@ Public Class DatabaseWithFallback
End Try
End Function
Private Async Function ExecuteNonQueryFromServiceAsync(pSQLCommand As String, DatabaseType As Constants.DatabaseType, pConnectionId As Integer) As Task(Of Boolean)
Dim oFallback As Boolean = False
_Logger.Debug("ExecuteNonQueryFromServiceAsync called with SQL [{0}], DatabaseType [{1}], ConnectionId [{2}]", pSQLCommand, DatabaseType.ToString, pConnectionId)
If _Client Is Nothing OrElse _ClientConfig?.ForceDirectDatabaseAccess Then
Return Await Task.Run(Function() ExecuteNonQueryFromDatabase(pSQLCommand, DatabaseType, pConnectionId))
End If
Dim oResult As ExecuteNonQueryResponse = Nothing
Try
Select Case DatabaseType
Case Constants.DatabaseType.ECM
oResult = Await _Client.ExecuteNonQueryFromECMAsync(pSQLCommand)
Case Constants.DatabaseType.IDB
oResult = Await _Client.ExecuteNonQueryFromIDBAsync(pSQLCommand)
Case Else
oResult = Await _Client.ExecuteNonQueryFromConnectionAsync(pSQLCommand, pConnectionId)
End Select
If oResult Is Nothing OrElse oResult.OK = False Then
_Logger.Warn("⚠ ExecuteNonQueryFromServiceAsync failed. Falling back to direct database access.")
oFallback = True
End If
Catch ex As Exception
_Logger.Warn("⚠ ExecuteNonQueryFromServiceAsync failed. Falling back to direct database access.")
_Logger.Error(ex)
oFallback = True
End Try
If oFallback Then
Return Await Task.Run(Function() ExecuteNonQueryFromDatabase(pSQLCommand, DatabaseType, pConnectionId))
End If
Return oResult.Result
End Function
Private Function ExecuteNonQueryFromService(pSQLCommand As String, DatabaseType As Constants.DatabaseType, pConnectionId As Integer) As Boolean
Try
Dim oResult As ExecuteNonQueryResponse = Nothing
_Logger.Debug("ExecuteNonQueryFromService [{0}] with Connection Id [{1}] and Command [{2}]", DatabaseType.ToString, pConnectionId, pSQLCommand)
Select Case DatabaseType
Case Constants.DatabaseType.ECM
oResult = _Client.ExecuteNonQueryFromECM(pSQLCommand)
Case Constants.DatabaseType.IDB
oResult = _Client.ExecuteNonQueryFromIDB(pSQLCommand)
Case Else
Return ExecuteNonQueryFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
End Select
If oResult Is Nothing Then
Throw New ApplicationException("Unexpected server error ocurred!")
End If
If oResult.OK = False Then
Throw New ApplicationException(oResult.ErrorMessage)
If oResult Is Nothing OrElse oResult.OK = False Then
_Logger.Warn("⚠ ExecuteNonQueryFromService: Service returned invalid response [{0}]. Falling back to direct database access.", If(oResult Is Nothing, "null", oResult.ErrorMessage))
Return ExecuteNonQueryFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
End If
Return oResult.Result
Catch ex As InvalidOperationException
_Logger.Warn("⚠ ExecuteNonQueryFromService skipped: [{0}] Falling back to direct database access.", ex.Message)
Return ExecuteNonQueryFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
Catch ex As Exception
_Logger.Warn("ExecuteNonQueryFromService failed. Falling back to direct database access.")
_Logger.Warn("⚠ ExecuteNonQueryFromService failed. Falling back to direct database access.")
_Logger.Error(ex)
Return ExecuteNonQueryFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
@@ -456,6 +562,8 @@ Public Class DatabaseWithFallback
Private Function ExecuteNonQueryFromDatabase(pSQLCommand As String, DatabaseType As Constants.DatabaseType, pConnectionId As Integer) As Boolean
Try
_Logger.Debug("ExecuteNonQueryFromDatabase [{0}] with Connection Id [{1}] and Command [{2}]", DatabaseType.ToString, pConnectionId, pSQLCommand)
Select Case DatabaseType
Case Constants.DatabaseType.IDB
Return _DatabaseIDB.ExecuteNonQuery(pSQLCommand)

View File

@@ -25,7 +25,7 @@ Namespace Modules.IDB
Return True
End If
Logger.Warn("Error while setting Attribute Value: [{0}]", oResponse.ErrorMessage)
Logger.Warn("⚠ Error while setting Attribute Value: [{0}]", oResponse.ErrorMessage)
Return False
Catch ex As Exception

View File

@@ -13,7 +13,7 @@ Imports System.Runtime.InteropServices
<Assembly: AssemblyCompany("Digital Data")>
<Assembly: AssemblyProduct("EDMIAPI")>
<Assembly: AssemblyCopyright("Copyright © 2026")>
<Assembly: AssemblyTrademark("2.0.0")>
<Assembly: AssemblyTrademark("2.7.0")>
<Assembly: ComVisible(False)>
@@ -31,5 +31,5 @@ Imports System.Runtime.InteropServices
' übernehmen, indem Sie "*" eingeben:
' <Assembly: AssemblyVersion("1.0.*")>
<Assembly: AssemblyVersion("2.0.0.0")>
<Assembly: AssemblyVersion("2.7.0.0")>
<Assembly: AssemblyFileVersion("1.6.1.1")>