diff --git a/EDMIAPI/Client.vb b/EDMIAPI/Client.vb
index a6cd4227..ee2e3bf6 100644
--- a/EDMIAPI/Client.vb
+++ b/EDMIAPI/Client.vb
@@ -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
+ '''
+ ''' Returns a snapshot of all currently registered client instances
+ '''
+ 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
+
+ '''
+ ''' Closes the channel and releases all resources
+ '''
+ 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
'''
''' LogConfig object
''' The IP address/hostname and port, separated by semicolon or colon, ex. 1.2.3.4:9000
- Public Sub New(pLogConfig As LogConfig, pServiceAdress As String)
+ ''' Optional identifier for this client instance (e.g. application name or hostname)
+ 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
''' LogConfig object
''' The IP address to connect to
''' The Port number to use for the connection
- Public Sub New(pLogConfig As LogConfig, pIPAddress As String, pPortNumber As Integer)
+ ''' Optional identifier for this client instance (e.g. application name or hostname)
+ 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
-
'''
''' Connect to the service
'''
@@ -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
'''
''' Aborts the channel and creates a new connection
'''
- 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
'''
''' 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
+ '''
+ ''' Throws InvalidOperationException if the channel is not ready for calls
+ '''
+ 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
+ '''
+ ''' Logs the communication error, sets offline state, triggers reconnect and rethrows
+ '''
+ 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
+ '''
+ ''' Returns a detailed status snapshot of the current client state
+ '''
+ 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
diff --git a/EDMIAPI/Client/Channel.vb b/EDMIAPI/Client/Channel.vb
index 8a8f1d41..53f75c26 100644
--- a/EDMIAPI/Client/Channel.vb
+++ b/EDMIAPI/Client/Channel.vb
@@ -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
+ '''
+ ''' Closes a channel gracefully, falls back to Abort on error
+ '''
+ 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
'''
''' Creates a channel and adds a Faulted-Handler
'''
@@ -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
diff --git a/EDMIAPI/DatabaseWithFallback.vb b/EDMIAPI/DatabaseWithFallback.vb
index 552b8da8..80f35f53 100644
--- a/EDMIAPI/DatabaseWithFallback.vb
+++ b/EDMIAPI/DatabaseWithFallback.vb
@@ -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)
diff --git a/EDMIAPI/Modules/IDB/SetAttributeValue.vb b/EDMIAPI/Modules/IDB/SetAttributeValue.vb
index 2504720e..c9140f19 100644
--- a/EDMIAPI/Modules/IDB/SetAttributeValue.vb
+++ b/EDMIAPI/Modules/IDB/SetAttributeValue.vb
@@ -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
diff --git a/EDMIAPI/My Project/AssemblyInfo.vb b/EDMIAPI/My Project/AssemblyInfo.vb
index 553314f0..1ef4ff0e 100644
--- a/EDMIAPI/My Project/AssemblyInfo.vb
+++ b/EDMIAPI/My Project/AssemblyInfo.vb
@@ -13,7 +13,7 @@ Imports System.Runtime.InteropServices
-
+
@@ -31,5 +31,5 @@ Imports System.Runtime.InteropServices
' übernehmen, indem Sie "*" eingeben:
'
-
+