Verbesserungen: Thread-Sicherheit, Logging, WCF-Updates

Die Änderungen umfassen:
- Einführung von `ReaderWriterLockSlim` für Thread-Sicherheit.
- Optimierung von `IsOnline` mit atomaren Operationen.
- Verbesserte Reconnect-Logik und Fehlerbehandlung.
- Neue Methoden für Datenbankoperationen (`GetDatatableByName` etc.).
- Gemeinsame `ChannelFactory` zur Ressourcenschonung.
- Logging-Verbesserungen mit präziseren Meldungen und Symbolen.
- Aktualisierte WCF-Bindings für höhere Verbindungszahlen und kürzere Timeouts.
- Aktualisierte Service-Referenzen mit asynchronen Methoden.
- Bereinigung von veralteten Methoden und Eigenschaften.
- Assembly-Version auf `2.8.0.0` aktualisiert.
This commit is contained in:
Developer01
2026-10-13 12:06:50 +02:00
parent aad2f01490
commit 3eb6314ba8
9 changed files with 4244 additions and 659 deletions

View File

@@ -22,8 +22,45 @@ Public Class Client
' Runtime Variables ' Runtime Variables
Private _ServerAddress As ServerAddressStruct Private _ServerAddress As ServerAddressStruct
Private _ClientConfig As GlobalStateClientConfiguration Private _ClientConfig As GlobalStateClientConfiguration
Private ReadOnly _CachedTablesLock As New ReaderWriterLockSlim()
Private _CachedTables As New List(Of String) Private _CachedTables As New List(Of String)
Private _IsOnline As Boolean
Public ReadOnly Property CachedTables As List(Of String)
Get
_CachedTablesLock.EnterReadLock()
Try
Return _CachedTables.ToList() ' Snapshot zurückgeben
Finally
_CachedTablesLock.ExitReadLock()
End Try
End Get
End Property
' Hilfsmethode – intern für alle Schreibzugriffe verwenden
Private Sub SetCachedTables(pTables As IEnumerable(Of String))
_CachedTablesLock.EnterWriteLock()
Try
_CachedTables = pTables.ToList()
Finally
_CachedTablesLock.ExitWriteLock()
End Try
End Sub
' Ersetzt: Private _IsOnline As Boolean
Private _IsOnline As Integer = 0 ' 0 = offline, 1 = online (Interlocked-kompatibel)
Public ReadOnly Property IsOnline As Boolean
Get
Return Interlocked.CompareExchange(_IsOnline, 0, 0) = 1
End Get
End Property
' Setter-Hilfsmethoden für alle Schreibstellen:
Private Sub SetOnline()
Interlocked.Exchange(_IsOnline, 1)
End Sub
Private Sub SetOffline()
Interlocked.Exchange(_IsOnline, 0)
End Sub
' Verhindert parallele Reconnect-Versuche (0 = frei, 1 = läuft) ' Verhindert parallele Reconnect-Versuche (0 = frei, 1 = läuft)
Private _IsReconnecting As Integer = 0 Private _IsReconnecting As Integer = 0
Private _Channel As IEDMIServiceChannel Private _Channel As IEDMIServiceChannel
@@ -56,14 +93,14 @@ Public Class Client
Protected Overridable Sub Dispose(disposing As Boolean) Protected Overridable Sub Dispose(disposing As Boolean)
If _Disposed Then Return If _Disposed Then Return
If disposing Then If disposing Then
_IsOnline = False ' ← Reconnect-Guard deaktivieren SetOffline()
UpdateTimer.Stop() UpdateTimer.Stop()
UpdateTimer.Dispose() UpdateTimer.Dispose()
' Auf laufenden Reconnect warten ' Auf laufenden Reconnect warten
Dim oTimeout = DateTime.Now.AddSeconds(5) Dim oTimeout = DateTime.Now.AddSeconds(5)
While _IsReconnecting = 1 AndAlso DateTime.Now < oTimeout While _IsReconnecting = 1 AndAlso DateTime.Now < oTimeout
Task.Delay(100).Wait() Thread.Sleep(100)
End While End While
If _Channel IsNot Nothing Then If _Channel IsNot Nothing Then
@@ -71,6 +108,8 @@ Public Class Client
_Channel = Nothing _Channel = Nothing
End If End If
_CachedTablesLock.Dispose() ' ← NEU: Lock freigeben
Dim oRemoved As ClientStatus = Nothing Dim oRemoved As ClientStatus = Nothing
_ActiveClients.TryRemove(_ClientId, oRemoved) _ActiveClients.TryRemove(_ClientId, oRemoved)
Logger.Debug("Client [{0}] unregistered. Total active clients: [{1}]", _ClientName, _ActiveClients.Count) Logger.Debug("Client [{0}] unregistered. Total active clients: [{1}]", _ClientName, _ActiveClients.Count)
@@ -79,11 +118,6 @@ Public Class Client
End Sub End Sub
' Public Variables ' Public Variables
Public ReadOnly Property CachedTables
Get
Return _CachedTables
End Get
End Property
Public ReadOnly Property ClientConfig As GlobalStateClientConfiguration Public ReadOnly Property ClientConfig As GlobalStateClientConfiguration
Get Get
@@ -94,13 +128,6 @@ Public Class Client
Return _ClientConfig Return _ClientConfig
End Get End Get
End Property End Property
Public ReadOnly Property IsOnline As Boolean
Get
Return _IsOnline
End Get
End Property
Public ReadOnly Property ServerAddress As String Public ReadOnly Property ServerAddress As String
Get Get
Return $"{_ServerAddress.Host}:{_ServerAddress.Port}" Return $"{_ServerAddress.Host}:{_ServerAddress.Port}"
@@ -197,14 +224,14 @@ Public Class Client
' CachedTables sofort befüllen – nicht auf den ersten Timer-Tick warten ' CachedTables sofort befüllen – nicht auf den ersten Timer-Tick warten
Try Try
Dim oTables As String() = _Channel.GetCachedTables() Dim oTables As String() = _Channel.GetCachedTables()
_CachedTables = oTables.Select(Function(t) t.ToUpper).ToList() SetCachedTables(oTables.Select(Function(t) t.ToUpper()))
Logger.Debug("✔ CachedTables loaded on connect: [{0}]", _CachedTables.Count) Logger.Debug("✔ CachedTables loaded on connect: [{0}]", CachedTables.Count)
Catch ex As Exception Catch ex As Exception
Logger.Warn("⚠ CachedTables could not be loaded on connect: {0}", ex.Message) Logger.Warn("⚠ CachedTables could not be loaded on connect: {0}", ex.Message)
_CachedTables = New List(Of String) SetCachedTables(Enumerable.Empty(Of String)())
End Try End Try
_IsOnline = True ' ← vor GetStatus() SetOnline()
UpdateTimer.Start() UpdateTimer.Start()
' In Registry eintragen – jetzt mit korrektem IsOnline=True ' In Registry eintragen – jetzt mit korrektem IsOnline=True
@@ -213,7 +240,7 @@ Public Class Client
Return True Return True
Catch ex As Exception Catch ex As Exception
_IsOnline = False SetOffline()
Logger.Error(ex) Logger.Error(ex)
Return False Return False
End Try End Try
@@ -243,7 +270,7 @@ Public Class Client
_Channel = ChannelManager.GetChannel() _Channel = ChannelManager.GetChannel()
_Channel.Open() _Channel.Open()
_IsOnline = True SetOnline()
If _ActiveClients.ContainsKey(_ClientId) Then If _ActiveClients.ContainsKey(_ClientId) Then
_ActiveClients(_ClientId) = GetStatus() _ActiveClients(_ClientId) = GetStatus()
@@ -253,7 +280,7 @@ Public Class Client
Return Return
Catch ex As Exception Catch ex As Exception
Logger.Warn("⚠ Reconnect failed after [{0}]s: {1}", oDelay, ex.Message) Logger.Warn("⚠ Reconnect failed after [{0}]s: {1}", oDelay, ex.Message)
_IsOnline = False SetOffline()
End Try End Try
Next Next
@@ -266,53 +293,51 @@ Public Class Client
Private Async Sub UpdateTimer_Elapsed(sender As Object, e As Timers.ElapsedEventArgs) Handles UpdateTimer.Elapsed Private Async Sub UpdateTimer_Elapsed(sender As Object, e As Timers.ElapsedEventArgs) Handles UpdateTimer.Elapsed
Try Try
If _Channel Is Nothing OrElse _Channel.State <> CommunicationState.Opened Then ' ← Snapshot VOR dem Await – schützt gegen _Channel = Nothing nach Resume
Logger.Warn("⚠ UpdateTimer skipped: Channel not in Opened state [{0}]", If(_Channel IsNot Nothing, _Channel.State.ToString(), "null")) Dim oChannel = _Channel
_IsOnline = False If oChannel Is Nothing OrElse oChannel.State <> CommunicationState.Opened Then
Logger.Warn("⚠ UpdateTimer skipped: Channel not in Opened state [{0}]",
If(oChannel IsNot Nothing, oChannel.State.ToString(), "null"))
SetOffline()
Reconnect(sender, e) Reconnect(sender, e)
Return Return
End If End If
If Not _IsOnline Then If Not IsOnline Then
Logger.Debug("UpdateTimer skipped: Client is offline.") Logger.Debug("UpdateTimer skipped: Client is offline.")
Return Return
End If End If
Dim oTables As String() = Await _Channel.GetCachedTablesAsync() Dim oTables As String() = Await oChannel.GetCachedTablesAsync() ' ← oChannel statt _Channel
' ◄ NullReferenceException-Schutz: Service kann Nothing zurückgeben
If oTables Is Nothing Then If oTables Is Nothing Then
Logger.Warn("⚠ UpdateTimer: GetCachedTablesAsync returned Nothing.") Logger.Warn("⚠ UpdateTimer: GetCachedTablesAsync returned Nothing.")
_CachedTables = New List(Of String) SetCachedTables(Enumerable.Empty(Of String)())
Return Return
End If End If
_CachedTables = oTables.
Select(Function(table) table.ToUpper).
ToList()
SetCachedTables(oTables.Select(Function(table) table.ToUpper()))
' ← oChannel.State statt _Channel.State
Logger.Info("✔ Channel state: [{0}] | Active connections: [{1}] | CachedTables: [{2}]", Logger.Info("✔ Channel state: [{0}] | Active connections: [{1}] | CachedTables: [{2}]",
_Channel.State, oChannel.State, Channel.ActiveConnections, CachedTables.Count)
Channel.ActiveConnections,
_CachedTables.Count)
If _ActiveClients.ContainsKey(_ClientId) Then If _ActiveClients.ContainsKey(_ClientId) Then
_ActiveClients(_ClientId) = GetStatus() _ActiveClients(_ClientId) = GetStatus()
End If End If
Catch ex As TimeoutException Catch ex As TimeoutException
' Stale TCP connection (z.B. Firewall/Citrix Idle-Timeout) Logger.Warn("⚠ UpdateTimer: TCP connection lost. Triggering reconnect...")
Logger.Warn("⚠ UpdateTimer: TCP connection lost (idle timeout / firewall). Triggering reconnect...") SetOffline()
_IsOnline = False
Reconnect() Reconnect()
Catch ex As CommunicationException Catch ex As CommunicationException
Logger.Warn("⚠ UpdateTimer: Channel faulted. Triggering reconnect...") Logger.Warn("⚠ UpdateTimer: Channel faulted. Triggering reconnect...")
_IsOnline = False SetOffline()
Reconnect() Reconnect()
Catch ex As Exception Catch ex As Exception
Logger.Warn("⚠ Update of CachedTable was not successful!") Logger.Warn("⚠ Update of CachedTable was not successful!")
Logger.Error(ex) Logger.Error(ex)
_CachedTables = New List(Of String) SetCachedTables(Enumerable.Empty(Of String)())
End Try End Try
End Sub End Sub
@@ -626,6 +651,59 @@ Public Class Client
End Function End Function
#Region "GetDatatable" #Region "GetDatatable"
Public Function GetDatatableByName(DatatableName As String, Optional FilterExpression As String = "", Optional SortByColumn As String = "") As TableResult
EnsureAvailable()
Try
Return _Channel.ReturnDatatableFromCache(DatatableName, FilterExpression, SortByColumn)
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(GetDatatableByName))
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableByName))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetDatatableByName))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
Public Function GetScalarValueFromIDB(pSQL As String) As GetScalarValueResponse
EnsureAvailable()
Try
Return _Channel.ReturnScalarValue(New GetScalarValueRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB
})
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(GetScalarValueFromIDB))
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetScalarValueFromIDB))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetScalarValueFromIDB))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
Public Function ExecuteNonQueryFromIDB(pSQL As String) As ExecuteNonQueryResponse
EnsureAvailable()
Try
Return _Channel.ExecuteNonQuery(New ExecuteNonQueryRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB
})
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDB))
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDB))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDB))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
Public Function GetDatatableFromIDB(pSQL As String) As GetDatatableResponse Public Function GetDatatableFromIDB(pSQL As String) As GetDatatableResponse
EnsureAvailable() EnsureAvailable()
Try Try
@@ -633,6 +711,8 @@ Public Class Client
.SqlCommand = pSQL, .SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB .NamedDatabase = DatabaseName.IDB
}) })
Catch ex As TimeoutException ' ← NEU
HandleCommunicationError(ex, NameOf(GetDatatableFromIDB))
Catch ex As EndpointNotFoundException Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableFromIDB)) HandleCommunicationError(ex, NameOf(GetDatatableFromIDB))
Catch ex As CommunicationException Catch ex As CommunicationException
@@ -643,6 +723,7 @@ Public Class Client
End Try End Try
End Function End Function
' ── GetDatatable ──────────────────────────────────────────
Public Function GetDatatableFromECM(pSQL As String) As GetDatatableResponse Public Function GetDatatableFromECM(pSQL As String) As GetDatatableResponse
EnsureAvailable() EnsureAvailable()
Try Try
@@ -650,6 +731,8 @@ Public Class Client
.SqlCommand = pSQL, .SqlCommand = pSQL,
.NamedDatabase = DatabaseName.ECM .NamedDatabase = DatabaseName.ECM
}) })
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(GetDatatableFromECM))
Catch ex As EndpointNotFoundException Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableFromECM)) HandleCommunicationError(ex, NameOf(GetDatatableFromECM))
Catch ex As CommunicationException Catch ex As CommunicationException
@@ -668,6 +751,8 @@ Public Class Client
.NamedDatabase = DatabaseName.None, .NamedDatabase = DatabaseName.None,
.ConnectionId = pConnectionId .ConnectionId = pConnectionId
}) })
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(GetDatatableFromConnection))
Catch ex As EndpointNotFoundException Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableFromConnection)) HandleCommunicationError(ex, NameOf(GetDatatableFromConnection))
Catch ex As CommunicationException Catch ex As CommunicationException
@@ -685,6 +770,8 @@ Public Class Client
.SqlCommand = pSQL, .SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB .NamedDatabase = DatabaseName.IDB
}) })
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(GetDatatableFromIDBAsync))
Catch ex As EndpointNotFoundException Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableFromIDBAsync)) HandleCommunicationError(ex, NameOf(GetDatatableFromIDBAsync))
Catch ex As CommunicationException Catch ex As CommunicationException
@@ -703,6 +790,8 @@ Public Class Client
.SqlCommand = pSQL, .SqlCommand = pSQL,
.NamedDatabase = DatabaseName.ECM .NamedDatabase = DatabaseName.ECM
}) })
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(GetDatatableFromECMAsync))
Catch ex As EndpointNotFoundException Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableFromECMAsync)) HandleCommunicationError(ex, NameOf(GetDatatableFromECMAsync))
Catch ex As CommunicationException Catch ex As CommunicationException
@@ -721,6 +810,8 @@ Public Class Client
.NamedDatabase = DatabaseName.None, .NamedDatabase = DatabaseName.None,
.ConnectionId = pConnectionId .ConnectionId = pConnectionId
}) })
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(GetDatatableFromConnectionAsync))
Catch ex As EndpointNotFoundException Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableFromConnectionAsync)) HandleCommunicationError(ex, NameOf(GetDatatableFromConnectionAsync))
Catch ex As CommunicationException Catch ex As CommunicationException
@@ -730,26 +821,8 @@ Public Class Client
Throw Throw
End Try End Try
End Function End Function
#End Region
#Region "GetScalarValue"
Public Function GetScalarValueFromIDB(pSQL As String) As GetScalarValueResponse
EnsureAvailable()
Try
Return _Channel.ReturnScalarValue(New GetScalarValueRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB
})
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetScalarValueFromIDB))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetScalarValueFromIDB))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
' ── GetScalarValue ─────────────────────────────────────────
Public Function GetScalarValueFromECM(pSQL As String) As GetScalarValueResponse Public Function GetScalarValueFromECM(pSQL As String) As GetScalarValueResponse
EnsureAvailable() EnsureAvailable()
Try Try
@@ -757,6 +830,8 @@ Public Class Client
.SqlCommand = pSQL, .SqlCommand = pSQL,
.NamedDatabase = DatabaseName.ECM .NamedDatabase = DatabaseName.ECM
}) })
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(GetScalarValueFromECM))
Catch ex As EndpointNotFoundException Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetScalarValueFromECM)) HandleCommunicationError(ex, NameOf(GetScalarValueFromECM))
Catch ex As CommunicationException Catch ex As CommunicationException
@@ -775,6 +850,8 @@ Public Class Client
.NamedDatabase = DatabaseName.None, .NamedDatabase = DatabaseName.None,
.ConnectionId = pConnectionId .ConnectionId = pConnectionId
}) })
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(GetScalarValueFromConnection))
Catch ex As EndpointNotFoundException Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetScalarValueFromConnection)) HandleCommunicationError(ex, NameOf(GetScalarValueFromConnection))
Catch ex As CommunicationException Catch ex As CommunicationException
@@ -792,6 +869,8 @@ Public Class Client
.SqlCommand = pSQL, .SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB .NamedDatabase = DatabaseName.IDB
}) })
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(GetScalarValueFromIDBAsync))
Catch ex As EndpointNotFoundException Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetScalarValueFromIDBAsync)) HandleCommunicationError(ex, NameOf(GetScalarValueFromIDBAsync))
Catch ex As CommunicationException Catch ex As CommunicationException
@@ -809,6 +888,8 @@ Public Class Client
.SqlCommand = pSQL, .SqlCommand = pSQL,
.NamedDatabase = DatabaseName.ECM .NamedDatabase = DatabaseName.ECM
}) })
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(GetScalarValueFromECMAsync))
Catch ex As EndpointNotFoundException Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetScalarValueFromECMAsync)) HandleCommunicationError(ex, NameOf(GetScalarValueFromECMAsync))
Catch ex As CommunicationException Catch ex As CommunicationException
@@ -827,6 +908,8 @@ Public Class Client
.NamedDatabase = DatabaseName.None, .NamedDatabase = DatabaseName.None,
.ConnectionId = pConnectionId .ConnectionId = pConnectionId
}) })
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(GetScalarValueFromConnectionAsync))
Catch ex As EndpointNotFoundException Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetScalarValueFromConnectionAsync)) HandleCommunicationError(ex, NameOf(GetScalarValueFromConnectionAsync))
Catch ex As CommunicationException Catch ex As CommunicationException
@@ -836,26 +919,8 @@ Public Class Client
Throw Throw
End Try End Try
End Function End Function
#End Region
#Region "ExecuteNonQuery"
Public Function ExecuteNonQueryFromIDB(pSQL As String) As ExecuteNonQueryResponse
EnsureAvailable()
Try
Return _Channel.ExecuteNonQuery(New ExecuteNonQueryRequest() With {
.SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB
})
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDB))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDB))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
' ── ExecuteNonQuery ────────────────────────────────────────
Public Function ExecuteNonQueryFromECM(pSQL As String) As ExecuteNonQueryResponse Public Function ExecuteNonQueryFromECM(pSQL As String) As ExecuteNonQueryResponse
EnsureAvailable() EnsureAvailable()
Try Try
@@ -863,6 +928,8 @@ Public Class Client
.SqlCommand = pSQL, .SqlCommand = pSQL,
.NamedDatabase = DatabaseName.ECM .NamedDatabase = DatabaseName.ECM
}) })
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromECM))
Catch ex As EndpointNotFoundException Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromECM)) HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromECM))
Catch ex As CommunicationException Catch ex As CommunicationException
@@ -881,6 +948,8 @@ Public Class Client
.NamedDatabase = DatabaseName.None, .NamedDatabase = DatabaseName.None,
.ConnectionId = pConnectionId .ConnectionId = pConnectionId
}) })
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromConnection))
Catch ex As EndpointNotFoundException Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromConnection)) HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromConnection))
Catch ex As CommunicationException Catch ex As CommunicationException
@@ -898,6 +967,8 @@ Public Class Client
.SqlCommand = pSQL, .SqlCommand = pSQL,
.NamedDatabase = DatabaseName.IDB .NamedDatabase = DatabaseName.IDB
}) })
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDBAsync))
Catch ex As EndpointNotFoundException Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDBAsync)) HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDBAsync))
Catch ex As CommunicationException Catch ex As CommunicationException
@@ -915,6 +986,8 @@ Public Class Client
.SqlCommand = pSQL, .SqlCommand = pSQL,
.NamedDatabase = DatabaseName.ECM .NamedDatabase = DatabaseName.ECM
}) })
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromECMAsync))
Catch ex As EndpointNotFoundException Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromECMAsync)) HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromECMAsync))
Catch ex As CommunicationException Catch ex As CommunicationException
@@ -933,6 +1006,8 @@ Public Class Client
.NamedDatabase = DatabaseName.None, .NamedDatabase = DatabaseName.None,
.ConnectionId = pConnectionId .ConnectionId = pConnectionId
}) })
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromConnectionAsync))
Catch ex As EndpointNotFoundException Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromConnectionAsync)) HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromConnectionAsync))
Catch ex As CommunicationException Catch ex As CommunicationException
@@ -942,45 +1017,26 @@ Public Class Client
Throw Throw
End Try End Try
End Function End Function
' ── Sonstige ───────────────────────────────────────────────
Public Async Function GetDatatableByNameAsync(DatatableName As String, Optional FilterExpression As String = "", Optional SortByColumn As String = "") As Task(Of TableResult)
EnsureAvailable()
Try
Return Await _Channel.ReturnDatatableFromCacheAsync(DatatableName, FilterExpression, SortByColumn)
Catch ex As TimeoutException
HandleCommunicationError(ex, NameOf(GetDatatableByNameAsync))
Catch ex As EndpointNotFoundException
HandleCommunicationError(ex, NameOf(GetDatatableByNameAsync))
Catch ex As CommunicationException
HandleCommunicationError(ex, NameOf(GetDatatableByNameAsync))
Catch ex As Exception
Logger.Error(ex)
Throw
End Try
End Function
#End Region #End Region
Public Function GetDatatableByName(DatatableName As String, Optional FilterExpression As String = "", Optional SortByColumn As String = "") As TableResult
EnsureAvailable()
Try
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
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
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
End Try
End Function
''' <summary>
''' Return infos about a file object
''' </summary>
''' <param name="UserId"></param>
''' <param name="ObjectId"></param>
''' <returns></returns>
Public Function GetDocumentInfo(UserId As Long, ObjectId As Long) As DocumentInfo Public Function GetDocumentInfo(UserId As Long, ObjectId As Long) As DocumentInfo
Try Try
Dim oResponse As DocumentInfoResponse = _Channel.GetFileInfoByObjectId(New DocumentInfoRequest With { Dim oResponse As DocumentInfoResponse = _Channel.GetFileInfoByObjectId(New DocumentInfoRequest With {
@@ -1056,27 +1112,25 @@ Public Class Client
Throw New InvalidOperationException("Channel is not initialized. Call Connect() first.") Throw New InvalidOperationException("Channel is not initialized. Call Connect() first.")
End If End If
If Not _IsOnline Then If Not IsOnline Then
' Kurze Grace-Period abwarten, damit Reconnect() seinen _IsReconnecting-Flag setzen kann ' Wenn Reconnect aktiv läuft: kurze Grace-Period (max 1s) statt 10s
Dim oGraceLimit = DateTime.Now.AddMilliseconds(600) If _IsReconnecting = 1 Then
While DateTime.Now < oGraceLimit AndAlso _IsReconnecting = 0 AndAlso Not _IsOnline Dim oGraceLimit = DateTime.Now.AddMilliseconds(1000)
Task.Delay(100).Wait() While DateTime.Now < oGraceLimit AndAlso Not IsOnline
Thread.Sleep(100)
End While End While
End If
' Jetzt auf aktiven Reconnect warten ' Danach sofort werfen – WCF-Timeout nicht weiter aufbrauchen
Dim oWaitLimit = DateTime.Now.AddSeconds(10) If Not IsOnline Then
While Not _IsOnline AndAlso DateTime.Now < oWaitLimit AndAlso _IsReconnecting = 1 Throw New InvalidOperationException(
Task.Delay(500).Wait() $"Client is offline. Reconnect in progress=[{_IsReconnecting = 1}].")
Logger.Debug("EnsureAvailable: Waiting for reconnect... Channel=[{0}]", _Channel.State.ToString())
End While
If Not _IsOnline Then
Throw New InvalidOperationException("Client is offline.")
End If End If
End If End If
If _Channel.State <> CommunicationState.Opened Then If _Channel.State <> CommunicationState.Opened Then
Throw New InvalidOperationException($"Channel is not available. State=[{_Channel.State}]") Throw New InvalidOperationException(
$"Channel is not available. State=[{_Channel.State}]")
End If End If
End Sub End Sub
''' <summary> ''' <summary>
@@ -1084,6 +1138,8 @@ Public Class Client
''' </summary> ''' </summary>
Private Sub HandleCommunicationError(ex As Exception, callerName As String) Private Sub HandleCommunicationError(ex As Exception, callerName As String)
Select Case True Select Case True
Case TypeOf ex Is TimeoutException
Logger.Warn("⚠ TimeoutException in [{0}]. Channel may be stale. Triggering reconnect...", callerName)
Case TypeOf ex Is EndpointNotFoundException Case TypeOf ex Is EndpointNotFoundException
Logger.Warn("⚠ Service not reachable (EndpointNotFoundException in [{0}]). Triggering reconnect...", callerName) Logger.Warn("⚠ Service not reachable (EndpointNotFoundException in [{0}]). Triggering reconnect...", callerName)
Case TypeOf ex Is CommunicationException Case TypeOf ex Is CommunicationException
@@ -1093,7 +1149,7 @@ Public Class Client
Logger.Error(ex) Logger.Error(ex)
End Select End Select
_IsOnline = False SetOffline()
Reconnect() Reconnect()
Throw ex Throw ex
End Sub End Sub
@@ -1190,15 +1246,16 @@ Public Class Client
''' Returns a detailed status snapshot of the current client state ''' Returns a detailed status snapshot of the current client state
''' </summary> ''' </summary>
Public Function GetStatus() As ClientStatus Public Function GetStatus() As ClientStatus
Dim oTables = CachedTables ' ← Property nutzen: enthält Read-Lock + Snapshot
Return New ClientStatus() With { Return New ClientStatus() With {
.IsOnline = _IsOnline, .IsOnline = IsOnline, ' ← Property statt Feld
.ClientId = _ClientId, ' ← Neu .ClientId = _ClientId,
.ClientName = _ClientName, .ClientName = _ClientName,
.ServerAddress = Me.ServerAddress, .ServerAddress = Me.ServerAddress,
.ChannelState = If(_Channel IsNot Nothing, _Channel.State.ToString(), "null"), .ChannelState = If(_Channel IsNot Nothing, _Channel.State.ToString(), "null"),
.ActiveConnections = Channel.ActiveConnections, .ActiveConnections = Channel.ActiveConnections,
.CachedTableCount = _CachedTables.Count, .CachedTableCount = oTables.Count, ' ← aus Snapshot
.CachedTables = _CachedTables.ToList(), .CachedTables = oTables, ' ← aus Snapshot
.HasClientConfig = _ClientConfig IsNot Nothing, .HasClientConfig = _ClientConfig IsNot Nothing,
.IsDisposed = _Disposed, .IsDisposed = _Disposed,
.TimerEnabled = UpdateTimer.Enabled, .TimerEnabled = UpdateTimer.Enabled,

View File

@@ -8,10 +8,14 @@ Imports DigitalData.Modules.Logging
Public Class Channel Public Class Channel
Inherits BaseClass Inherits BaseClass
Private Shared ReadOnly _FactoryLock As New Object()
Private Shared _SharedFactory As ChannelFactory(Of IEDMIServiceChannel) = Nothing
Private Shared _SharedFactoryEndpoint As String = String.Empty
Private ReadOnly ChannelFactory As ChannelFactory(Of IEDMIServiceChannel) Private ReadOnly ChannelFactory As ChannelFactory(Of IEDMIServiceChannel)
Public Event Reconnect As EventHandler Public Event Reconnect As EventHandler
' Verbindungszähler
Private Shared _ActiveConnections As Integer = 0 Private Shared _ActiveConnections As Integer = 0
Public Shared ReadOnly Property ActiveConnections As Integer Public Shared ReadOnly Property ActiveConnections As Integer
Get Get
@@ -20,16 +24,18 @@ Public Class Channel
End Property End Property
Public Shared Function GetBinding(Optional AuthenticationMode As TcpClientCredentialType = TcpClientCredentialType.Windows) As NetTcpBinding Public Shared Function GetBinding(Optional AuthenticationMode As TcpClientCredentialType = TcpClientCredentialType.Windows) As NetTcpBinding
Dim oMaxConnections = Math.Max(500, Environment.ProcessorCount * 50)
Return New NetTcpBinding() With { Return New NetTcpBinding() With {
.MaxReceivedMessageSize = Constants.ChannelSettings.MAX_RECEIVED_MESSAGE_SIZE, .MaxReceivedMessageSize = Constants.ChannelSettings.MAX_RECEIVED_MESSAGE_SIZE,
.MaxBufferSize = Constants.ChannelSettings.MAX_BUFFER_SIZE, .MaxBufferSize = Constants.ChannelSettings.MAX_BUFFER_SIZE,
.MaxBufferPoolSize = Constants.ChannelSettings.MAX_BUFFER_POOL_SIZE, .MaxBufferPoolSize = Constants.ChannelSettings.MAX_BUFFER_POOL_SIZE,
.ListenBacklog = Constants.ChannelSettings.MAX_CONNECTIONS, .ListenBacklog = oMaxConnections,
.MaxConnections = Constants.ChannelSettings.MAX_CONNECTIONS, .MaxConnections = oMaxConnections,
.TransferMode = TransferMode.Streamed, .TransferMode = TransferMode.Streamed,
.SendTimeout = TimeSpan.FromSeconds(30), ' ← War: 5min → Schnellere Fehlererkennung .SendTimeout = TimeSpan.FromSeconds(60),
.ReceiveTimeout = TimeSpan.FromMinutes(10), ' Keepalive < Firewall-Timeout (3600s) ✓ .ReceiveTimeout = TimeSpan.FromMinutes(10),
.OpenTimeout = TimeSpan.FromSeconds(30), ' ← Neu: Verbindungsaufbau begrenzen .OpenTimeout = TimeSpan.FromSeconds(60),
.CloseTimeout = TimeSpan.FromSeconds(30), .CloseTimeout = TimeSpan.FromSeconds(30),
.Security = New NetTcpSecurity() With { .Security = New NetTcpSecurity() With {
.Mode = SecurityMode.Transport, .Mode = SecurityMode.Transport,
@@ -46,14 +52,63 @@ Public Class Channel
Public Sub New(pLogConfig As LogConfig, pServerAddress As ServerAddressStruct) Public Sub New(pLogConfig As LogConfig, pServerAddress As ServerAddressStruct)
MyBase.New(pLogConfig) MyBase.New(pLogConfig)
ChannelFactory = GetChannelFactory(pServerAddress) ChannelFactory = GetOrCreateSharedFactory(pLogConfig, pServerAddress)
End Sub End Sub
''' <summary>
''' Gibt eine gemeinsame ChannelFactory zurück.
''' Ein Connection-Pool für alle Client-Instanzen desselben Endpoints.
''' </summary>
Private Shared Function GetOrCreateSharedFactory(pLogConfig As LogConfig, pServerAddress As ServerAddressStruct) As ChannelFactory(Of IEDMIServiceChannel)
Dim oEndpoint = $"net.tcp://{pServerAddress.Host}:{pServerAddress.Port}/DigitalData/Services/Main"
' ← Volatile.Read: garantiert aktuellen Wert auch ohne Lock
Dim oCurrentFactory = Volatile.Read(_SharedFactory)
If oCurrentFactory IsNot Nothing AndAlso
_SharedFactoryEndpoint = oEndpoint AndAlso
(oCurrentFactory.State = CommunicationState.Opened OrElse
oCurrentFactory.State = CommunicationState.Created) Then
Return oCurrentFactory
End If
SyncLock _FactoryLock
' Double-check nach Lock
If _SharedFactory IsNot Nothing AndAlso
_SharedFactoryEndpoint = oEndpoint AndAlso
(_SharedFactory.State = CommunicationState.Opened OrElse
_SharedFactory.State = CommunicationState.Created) Then
Return _SharedFactory
End If
If _SharedFactory IsNot Nothing Then
If _SharedFactory.State = CommunicationState.Faulted OrElse
_SharedFactory.State = CommunicationState.Closing Then
Try
_SharedFactory.Abort()
Catch
End Try
End If
_SharedFactory = Nothing
End If
Dim oLogger = pLogConfig.GetLogger()
Dim oBinding = GetBinding()
Dim oAddress = New EndpointAddress(oEndpoint)
oLogger.Debug("Creating shared ChannelFactory | Endpoint=[{0}] | MaxConnections=[{1}] | SendTimeout=[{2}] | OpenTimeout=[{3}]",
oEndpoint, oBinding.MaxConnections, oBinding.SendTimeout, oBinding.OpenTimeout)
_SharedFactory = New ChannelFactory(Of IEDMIServiceChannel)(oBinding, oAddress)
_SharedFactoryEndpoint = oEndpoint
Return _SharedFactory
End SyncLock
End Function
''' <summary> ''' <summary>
''' Closes a channel gracefully, falls back to Abort on error ''' Closes a channel gracefully, falls back to Abort on error
''' </summary> ''' </summary>
Public Shared Sub CloseChannel(pChannel As IEDMIServiceChannel) Public Shared Sub CloseChannel(pChannel As IEDMIServiceChannel)
If pChannel Is Nothing Then Return If pChannel Is Nothing Then Return
Try Try
If pChannel.State = CommunicationState.Opened Then If pChannel.State = CommunicationState.Opened Then
pChannel.Close() pChannel.Close()
@@ -61,31 +116,27 @@ Public Class Channel
pChannel.State = CommunicationState.Closing Then pChannel.State = CommunicationState.Closing Then
pChannel.Abort() pChannel.Abort()
End If End If
' CommunicationState.Created / Closed → nichts tun, Zähler nicht betroffen
Catch ex As Exception Catch ex As Exception
Try Try
pChannel.Abort() pChannel.Abort()
Catch Catch
' Ignore
End Try End Try
End Try End Try
End Sub End Sub
''' <summary> ''' <summary>
''' Creates a channel and adds a Faulted-Handler ''' Creates a channel and adds a Faulted-Handler
''' </summary> ''' </summary>
''' <returns>A channel object</returns>
Public Function GetChannel() As IEDMIServiceChannel Public Function GetChannel() As IEDMIServiceChannel
Try Try
Logger.Debug("Creating channel.") Logger.Debug("Creating channel from shared factory.")
Dim oChannel = ChannelFactory.CreateChannel() Dim oChannel = ChannelFactory.CreateChannel()
' Verbindung zählen beim Öffnen
AddHandler oChannel.Opened, Sub() AddHandler oChannel.Opened, Sub()
Dim oCount = Interlocked.Increment(_ActiveConnections) Dim oCount = Interlocked.Increment(_ActiveConnections)
Logger.Debug("Channel opened. Active connections: [{0}]", oCount) Logger.Debug("Channel opened. Active connections: [{0}]", oCount)
End Sub End Sub
' Verbindung abziehen beim Schließen oder Fehler
AddHandler oChannel.Closed, Sub() AddHandler oChannel.Closed, Sub()
Dim oCount = Interlocked.Decrement(_ActiveConnections) Dim oCount = Interlocked.Decrement(_ActiveConnections)
Logger.Debug("Channel closed. Active connections: [{0}]", oCount) Logger.Debug("Channel closed. Active connections: [{0}]", oCount)
@@ -93,7 +144,7 @@ Public Class Channel
AddHandler oChannel.Faulted, Sub() AddHandler oChannel.Faulted, Sub()
Dim oCount = Interlocked.Decrement(_ActiveConnections) Dim oCount = Interlocked.Decrement(_ActiveConnections)
Logger.Warn("Channel faulted. Active connections: [{0}]", oCount) Logger.Warn("⚠ Channel faulted. Active connections: [{0}]", oCount)
RaiseEvent Reconnect(Me, Nothing) RaiseEvent Reconnect(Me, Nothing)
End Sub End Sub
@@ -104,20 +155,4 @@ Public Class Channel
Throw Throw
End Try End Try
End Function 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 End Class

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<ReferenceGroup xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" ID="1b516c6d-d2d0-4dfc-9557-3dd0593fa624" xmlns="urn:schemas-microsoft-com:xml-wcfservicemap"> <ReferenceGroup xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ID="1b516c6d-d2d0-4dfc-9557-3dd0593fa624" xmlns="urn:schemas-microsoft-com:xml-wcfservicemap">
<ClientOptions> <ClientOptions>
<GenerateAsynchronousMethods>false</GenerateAsynchronousMethods> <GenerateAsynchronousMethods>false</GenerateAsynchronousMethods>
<GenerateTaskBasedAsynchronousMethod>true</GenerateTaskBasedAsynchronousMethod> <GenerateTaskBasedAsynchronousMethod>true</GenerateTaskBasedAsynchronousMethod>

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<configurationSnapshot xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="urn:schemas-microsoft-com:xml-wcfconfigurationsnapshot"> <configurationSnapshot xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:schemas-microsoft-com:xml-wcfconfigurationsnapshot">
<behaviors /> <behaviors />
<bindings> <bindings>
<binding digest="System.ServiceModel.Configuration.NetTcpBindingElement, System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089:&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;Data name=&quot;NetTcpBinding_IEDMIService&quot; transferMode=&quot;Streamed&quot;&gt;&lt;security&gt;&lt;transport sslProtocols=&quot;None&quot; /&gt;&lt;/security&gt;&lt;/Data&gt;" bindingType="netTcpBinding" name="NetTcpBinding_IEDMIService" /> <binding digest="System.ServiceModel.Configuration.NetTcpBindingElement, System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089:&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;Data name=&quot;NetTcpBinding_IEDMIService&quot; transferMode=&quot;Streamed&quot;&gt;&lt;security&gt;&lt;transport sslProtocols=&quot;None&quot; /&gt;&lt;/security&gt;&lt;/Data&gt;" bindingType="netTcpBinding" name="NetTcpBinding_IEDMIService" />

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<SavedWcfConfigurationInformation xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Version="9.1" CheckSum="LUfq4Z7qL259JRuSf1xmDS/kLlBkZTtgk2vJyQH0Jvo="> <SavedWcfConfigurationInformation xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Version="9.1" CheckSum="4uBt9Y5m0Ue4fIPG6eooT5TT5MJ97hiEcthL6+tUJHw=">
<bindingConfigurations> <bindingConfigurations>
<bindingConfiguration bindingType="netTcpBinding" name="NetTcpBinding_IEDMIService"> <bindingConfiguration bindingType="netTcpBinding" name="NetTcpBinding_IEDMIService">
<properties> <properties>

View File

@@ -342,14 +342,17 @@ Public Class DatabaseWithFallback
End Function End Function
Private Async Function GetDatatableFromServiceAsync(pSQLCommand As String, DatabaseType As Constants.DatabaseType, pConnectionId As Integer) As Task(Of DataTable) 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 called | SQL=[{0}] | DB=[{1}] | ConnId=[{2}]",
_Logger.Debug("GetDatatableFromServiceAsync: GetDatatableFromServiceAsync called with SQL [{0}], DatabaseType [{1}], ConnectionId [{2}]", pSQLCommand, DatabaseType.ToString, pConnectionId) pSQLCommand, DatabaseType.ToString, pConnectionId)
If _Client Is Nothing OrElse _ClientConfig?.ForceDirectDatabaseAccess Then If _Client Is Nothing OrElse _ClientConfig?.ForceDirectDatabaseAccess Then
Return Await Task.Run(Function() GetDatatableFromDatabase(pSQLCommand, DatabaseType, pConnectionId)) ' ← Kein Task.Run: direkt synchron, Thread ist ohnehin frei
Return GetDatatableFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
End If End If
Dim oResult As GetDatatableResponse = Nothing
Try Try
Dim oResult As GetDatatableResponse = Nothing
Select Case DatabaseType Select Case DatabaseType
Case Constants.DatabaseType.ECM Case Constants.DatabaseType.ECM
oResult = Await _Client.GetDatatableFromECMAsync(pSQLCommand) oResult = Await _Client.GetDatatableFromECMAsync(pSQLCommand)
@@ -360,20 +363,22 @@ Public Class DatabaseWithFallback
End Select End Select
If oResult Is Nothing OrElse oResult.OK = False Then If oResult Is Nothing OrElse oResult.OK = False Then
_Logger.Warn("⚠ GetDatatableFromServiceAsync failed. Falling back to direct database access.") _Logger.Warn("⚠ GetDatatableFromServiceAsync failed | Fallback to DB | Error=[{0}]",
oFallback = True If(oResult Is Nothing, "null", oResult.ErrorMessage))
End If Return GetDatatableFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
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 End If
Return oResult.Table Return oResult.Table
Catch ex As InvalidOperationException
_Logger.Warn("⚠ GetDatatableFromServiceAsync offline: [{0}] | Fallback to DB", ex.Message)
Return GetDatatableFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
Catch ex As Exception
_Logger.Warn("⚠ GetDatatableFromServiceAsync failed | Fallback to DB")
_Logger.Error(ex)
Return GetDatatableFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
End Try
End Function End Function
Private Function GetDatatableFromDatabase(pSQLCommand As String, pDatabaseType As Constants.DatabaseType, pConnectionId As Integer) As DataTable Private Function GetDatatableFromDatabase(pSQLCommand As String, pDatabaseType As Constants.DatabaseType, pConnectionId As Integer) As DataTable
Try Try
@@ -401,16 +406,16 @@ Public Class DatabaseWithFallback
End Try End Try
End Function End Function
Private Async Function GetScalarValueFromServiceAsync(pSQLCommand As String, DatabaseType As Constants.DatabaseType, pConnectionId As Integer) As Task(Of Object) 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 | SQL=[{0}] | DB=[{1}] | ConnId=[{2}]",
pSQLCommand, DatabaseType.ToString, pConnectionId)
_Logger.Debug("GetScalarValueFromServiceAsync called with SQL [{0}], DatabaseType [{1}], ConnectionId [{2}]", pSQLCommand, DatabaseType.ToString, pConnectionId)
If _Client Is Nothing OrElse _ClientConfig?.ForceDirectDatabaseAccess Then If _Client Is Nothing OrElse _ClientConfig?.ForceDirectDatabaseAccess Then
Return Await Task.Run(Function() GetScalarValueFromDatabase(pSQLCommand, DatabaseType, pConnectionId)) Return GetScalarValueFromDatabase(pSQLCommand, DatabaseType, pConnectionId) ' ← kein Task.Run
End If End If
Dim oResult As GetScalarValueResponse = Nothing
Try Try
Dim oResult As GetScalarValueResponse = Nothing
Select Case DatabaseType Select Case DatabaseType
Case Constants.DatabaseType.ECM Case Constants.DatabaseType.ECM
oResult = Await _Client.GetScalarValueFromECMAsync(pSQLCommand) oResult = Await _Client.GetScalarValueFromECMAsync(pSQLCommand)
@@ -421,20 +426,21 @@ Public Class DatabaseWithFallback
End Select End Select
If oResult Is Nothing OrElse oResult.OK = False Then If oResult Is Nothing OrElse oResult.OK = False Then
_Logger.Warn("⚠ GetScalarValueFromServiceAsync failed. Falling back to direct database access.") _Logger.Warn("⚠ GetScalarValueFromServiceAsync failed | Fallback to DB")
oFallback = True Return GetScalarValueFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
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 End If
Return oResult.Scalar Return oResult.Scalar
Catch ex As InvalidOperationException
_Logger.Warn("⚠ GetScalarValueFromServiceAsync offline: [{0}] | Fallback to DB", ex.Message)
Return GetScalarValueFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
Catch ex As Exception
_Logger.Warn("⚠ GetScalarValueFromServiceAsync failed | Fallback to DB")
_Logger.Error(ex)
Return GetScalarValueFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
End Try
End Function End Function
Private Function GetScalarValueFromService(pSQLCommand As String, DatabaseType As Constants.DatabaseType, pConnectionId As Integer) As Object Private Function GetScalarValueFromService(pSQLCommand As String, DatabaseType As Constants.DatabaseType, pConnectionId As Integer) As Object
Try Try
@@ -493,16 +499,16 @@ Public Class DatabaseWithFallback
End Try End Try
End Function End Function
Private Async Function ExecuteNonQueryFromServiceAsync(pSQLCommand As String, DatabaseType As Constants.DatabaseType, pConnectionId As Integer) As Task(Of Boolean) 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 | SQL=[{0}] | DB=[{1}] | ConnId=[{2}]",
pSQLCommand, DatabaseType.ToString, pConnectionId)
_Logger.Debug("ExecuteNonQueryFromServiceAsync called with SQL [{0}], DatabaseType [{1}], ConnectionId [{2}]", pSQLCommand, DatabaseType.ToString, pConnectionId)
If _Client Is Nothing OrElse _ClientConfig?.ForceDirectDatabaseAccess Then If _Client Is Nothing OrElse _ClientConfig?.ForceDirectDatabaseAccess Then
Return Await Task.Run(Function() ExecuteNonQueryFromDatabase(pSQLCommand, DatabaseType, pConnectionId)) Return ExecuteNonQueryFromDatabase(pSQLCommand, DatabaseType, pConnectionId) ' ← kein Task.Run
End If End If
Dim oResult As ExecuteNonQueryResponse = Nothing
Try Try
Dim oResult As ExecuteNonQueryResponse = Nothing
Select Case DatabaseType Select Case DatabaseType
Case Constants.DatabaseType.ECM Case Constants.DatabaseType.ECM
oResult = Await _Client.ExecuteNonQueryFromECMAsync(pSQLCommand) oResult = Await _Client.ExecuteNonQueryFromECMAsync(pSQLCommand)
@@ -513,20 +519,21 @@ Public Class DatabaseWithFallback
End Select End Select
If oResult Is Nothing OrElse oResult.OK = False Then If oResult Is Nothing OrElse oResult.OK = False Then
_Logger.Warn("⚠ ExecuteNonQueryFromServiceAsync failed. Falling back to direct database access.") _Logger.Warn("⚠ ExecuteNonQueryFromServiceAsync failed | Fallback to DB")
oFallback = True Return ExecuteNonQueryFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
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 End If
Return oResult.Result Return oResult.Result
Catch ex As InvalidOperationException
_Logger.Warn("⚠ ExecuteNonQueryFromServiceAsync offline: [{0}] | Fallback to DB", ex.Message)
Return ExecuteNonQueryFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
Catch ex As Exception
_Logger.Warn("⚠ ExecuteNonQueryFromServiceAsync failed | Fallback to DB")
_Logger.Error(ex)
Return ExecuteNonQueryFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
End Try
End Function End Function
Private Function ExecuteNonQueryFromService(pSQLCommand As String, DatabaseType As Constants.DatabaseType, pConnectionId As Integer) As Boolean Private Function ExecuteNonQueryFromService(pSQLCommand As String, DatabaseType As Constants.DatabaseType, pConnectionId As Integer) As Boolean
Try Try

View File

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

3401
EDMIAPI/Reference.vb Normal file

File diff suppressed because it is too large Load Diff