Compare commits
4 Commits
6aebeaa446
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3eb6314ba8 | ||
|
|
aad2f01490 | ||
|
|
de918b1e88 | ||
|
|
298a398067 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -338,3 +338,4 @@ ASALocalRun/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
/EDMIAPI/ErrorLog.txt
|
||||
|
||||
@@ -494,14 +494,30 @@ Public Class MSSQLServer
|
||||
pSqlCommandObject.Connection = pSqlConnection
|
||||
pSqlCommandObject.Transaction = oTransaction
|
||||
pSqlCommandObject.CommandTimeout = pTimeout
|
||||
|
||||
' Sicherstellen, dass Nothing-Werte als DBNull übergeben werden
|
||||
For Each oParam As SqlParameter In pSqlCommandObject.Parameters
|
||||
If oParam.Value Is Nothing Then
|
||||
Logger.Warn("ExecuteNonQueryWithConnectionObject: Parameter [{0}] was Nothing - converting to DBNull.Value", oParam.ParameterName)
|
||||
oParam.Value = DBNull.Value
|
||||
End If
|
||||
Next
|
||||
|
||||
pSqlCommandObject.ExecuteNonQuery()
|
||||
|
||||
Return True
|
||||
Catch ex As SqlException
|
||||
Logger.Warn("ExecuteNonQueryWithConnectionObject: SQL Error while executing command: [{0}]", pSqlCommandObject.CommandText)
|
||||
Logger.Warn("Parameters: [{0}]", GetParameterListAsString(pSqlCommandObject))
|
||||
For Each sqlError As SqlError In ex.Errors
|
||||
Logger.Error("SQL Error #{0} (Severity {1}, State {2}, Line {3}): {4}",
|
||||
sqlError.Number, sqlError.Class, sqlError.State, sqlError.LineNumber, sqlError.Message)
|
||||
Next
|
||||
Return False
|
||||
Catch ex As Exception
|
||||
Dim omsg = $"Unexpected Error in ExecuteNonQueryWithConnectionObject while executing command: [{pSqlCommandObject.CommandText}]"
|
||||
Logger.Warn(omsg)
|
||||
Logger.Warn("Unexpected Error in ExecuteNonQueryWithConnectionObject while executing command: [{0}]", pSqlCommandObject.CommandText)
|
||||
Logger.Warn("Parameters: [{0}]", GetParameterListAsString(pSqlCommandObject))
|
||||
Logger.Error(ex)
|
||||
|
||||
Return False
|
||||
Finally
|
||||
MaybeCommitTransaction(oTransaction, pTransactionMode)
|
||||
|
||||
@@ -13,7 +13,7 @@ Imports System.Runtime.InteropServices
|
||||
<Assembly: AssemblyCompany("Digital Data")>
|
||||
<Assembly: AssemblyProduct("Modules.Database")>
|
||||
<Assembly: AssemblyCopyright("Copyright © 2025")>
|
||||
<Assembly: AssemblyTrademark("2.3.7.0")>
|
||||
<Assembly: AssemblyTrademark("2.4.0.0")>
|
||||
|
||||
<Assembly: ComVisible(False)>
|
||||
|
||||
@@ -31,5 +31,5 @@ Imports System.Runtime.InteropServices
|
||||
' übernehmen, indem Sie "*" eingeben:
|
||||
' <Assembly: AssemblyVersion("1.0.*")>
|
||||
|
||||
<Assembly: AssemblyVersion("2.3.7.0")>
|
||||
<Assembly: AssemblyFileVersion("2.3.7.0")>
|
||||
<Assembly: AssemblyVersion("2.4.0.0")>
|
||||
<Assembly: AssemblyFileVersion("2.4.0.0")>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
Imports System.IO
|
||||
Imports System.ServiceModel
|
||||
Imports System.Threading
|
||||
Imports DigitalData.Modules.Base
|
||||
Imports DigitalData.Modules.EDMI.API.Constants
|
||||
Imports DigitalData.Modules.EDMI.API.EDMIServiceReference
|
||||
@@ -21,8 +22,47 @@ Public Class Client
|
||||
' Runtime Variables
|
||||
Private _ServerAddress As ServerAddressStruct
|
||||
Private _ClientConfig As GlobalStateClientConfiguration
|
||||
|
||||
Private ReadOnly _CachedTablesLock As New ReaderWriterLockSlim()
|
||||
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)
|
||||
Private _IsReconnecting As Integer = 0
|
||||
Private _Channel As IEDMIServiceChannel
|
||||
Private _ClientName As String
|
||||
' Static Registry
|
||||
@@ -52,32 +92,32 @@ Public Class Client
|
||||
End Sub
|
||||
Protected Overridable Sub Dispose(disposing As Boolean)
|
||||
If _Disposed Then Return
|
||||
|
||||
If disposing Then
|
||||
SetOffline()
|
||||
UpdateTimer.Stop()
|
||||
UpdateTimer.Dispose()
|
||||
|
||||
' Auf laufenden Reconnect warten
|
||||
Dim oTimeout = DateTime.Now.AddSeconds(5)
|
||||
While _IsReconnecting = 1 AndAlso DateTime.Now < oTimeout
|
||||
Thread.Sleep(100)
|
||||
End While
|
||||
|
||||
If _Channel IsNot Nothing Then
|
||||
Channel.CloseChannel(_Channel)
|
||||
_Channel = Nothing
|
||||
End If
|
||||
|
||||
' Aus Registry austragen
|
||||
_CachedTablesLock.Dispose() ' ← NEU: Lock freigeben
|
||||
|
||||
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
|
||||
Get
|
||||
Return _CachedTables
|
||||
End Get
|
||||
End Property
|
||||
|
||||
Public ReadOnly Property ClientConfig As GlobalStateClientConfiguration
|
||||
Get
|
||||
@@ -88,13 +128,6 @@ Public Class Client
|
||||
Return _ClientConfig
|
||||
End Get
|
||||
End Property
|
||||
|
||||
Public ReadOnly Property IsOnline As Boolean
|
||||
Get
|
||||
Return _IsOnline
|
||||
End Get
|
||||
End Property
|
||||
|
||||
Public ReadOnly Property ServerAddress As String
|
||||
Get
|
||||
Return $"{_ServerAddress.Host}:{_ServerAddress.Port}"
|
||||
@@ -125,7 +158,7 @@ Public Class Client
|
||||
_ClientId = $"{_ClientName}_{Guid.NewGuid().ToString("N").Substring(0, 8)}" ' ← eindeutig
|
||||
|
||||
UpdateTimer.Interval = 60 * 1000 * UPDATE_INTERVAL_IN_MINUTES
|
||||
UpdateTimer.Start()
|
||||
' Start erfolgt in Connect()
|
||||
|
||||
Try
|
||||
Dim oConnection = New Connection()
|
||||
@@ -154,7 +187,6 @@ Public Class Client
|
||||
_ClientId = $"{_ClientName}_{Guid.NewGuid().ToString("N").Substring(0, 8)}" ' ← eindeutig
|
||||
|
||||
UpdateTimer.Interval = 60 * 1000 * UPDATE_INTERVAL_IN_MINUTES
|
||||
UpdateTimer.Start()
|
||||
|
||||
Try
|
||||
Dim oConnection = New Connection()
|
||||
@@ -188,25 +220,27 @@ Public Class Client
|
||||
|
||||
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)
|
||||
SetCachedTables(oTables.Select(Function(t) t.ToUpper()))
|
||||
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)
|
||||
SetCachedTables(Enumerable.Empty(Of String)())
|
||||
End Try
|
||||
' In Registry eintragen
|
||||
|
||||
SetOnline()
|
||||
UpdateTimer.Start()
|
||||
|
||||
' In Registry eintragen – jetzt mit korrektem IsOnline=True
|
||||
_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
|
||||
|
||||
SetOffline()
|
||||
Logger.Error(ex)
|
||||
Return False
|
||||
End Try
|
||||
@@ -215,7 +249,14 @@ Public Class Client
|
||||
''' <summary>
|
||||
''' Aborts the channel and creates a new connection
|
||||
''' </summary>
|
||||
Public Async Sub Reconnect()
|
||||
Public Async Sub Reconnect(Optional sender As Object = Nothing, Optional e As EventArgs = Nothing)
|
||||
' Nur ein Reconnect gleichzeitig erlauben – atomar prüfen und setzen
|
||||
If Interlocked.CompareExchange(_IsReconnecting, 1, 0) = 1 Then
|
||||
Logger.Debug("Reconnect already in progress – skipping duplicate call.")
|
||||
Return
|
||||
End If
|
||||
|
||||
Try
|
||||
Logger.Warn("⚠ Connection faulted. Trying to reconnect..")
|
||||
|
||||
Dim oRetryDelays = {2, 5, 15, 30}
|
||||
@@ -223,73 +264,80 @@ Public Class Client
|
||||
For Each oDelay In oRetryDelays
|
||||
Try
|
||||
Await Task.Delay(TimeSpan.FromSeconds(oDelay))
|
||||
Logger.Debug($"Reconnect attempt after {oDelay}s delay...")
|
||||
Logger.Debug("Reconnect attempt after [{0}]s delay...", oDelay)
|
||||
|
||||
Channel.CloseChannel(_Channel) ' Sauber schließen statt Abort
|
||||
Channel.CloseChannel(_Channel)
|
||||
_Channel = ChannelManager.GetChannel()
|
||||
_Channel.Open()
|
||||
|
||||
_IsOnline = True
|
||||
Logger.Info("Reconnect successful.")
|
||||
SetOnline()
|
||||
|
||||
If _ActiveClients.ContainsKey(_ClientId) Then
|
||||
_ActiveClients(_ClientId) = GetStatus()
|
||||
End If
|
||||
|
||||
Logger.Info("✔ Reconnect successful.")
|
||||
Return
|
||||
Catch ex As Exception
|
||||
Logger.Warn($"⚠ Reconnect failed: {ex.Message}")
|
||||
_IsOnline = False
|
||||
Logger.Warn("⚠ Reconnect failed after [{0}]s: {1}", oDelay, ex.Message)
|
||||
SetOffline()
|
||||
End Try
|
||||
Next
|
||||
|
||||
Logger.Error("All reconnect attempts failed. Service unavailable.")
|
||||
Finally
|
||||
' Flag immer zurücksetzen – auch bei unerwarteten Fehlern
|
||||
Interlocked.Exchange(_IsReconnecting, 0)
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
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()
|
||||
' ← Snapshot VOR dem Await – schützt gegen _Channel = Nothing nach Resume
|
||||
Dim oChannel = _Channel
|
||||
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)
|
||||
Return
|
||||
End If
|
||||
|
||||
If Not _IsOnline Then
|
||||
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
|
||||
Dim oTables As String() = Await oChannel.GetCachedTablesAsync() ' ← oChannel statt _Channel
|
||||
|
||||
If oTables Is Nothing Then
|
||||
Logger.Warn("⚠ UpdateTimer: GetCachedTablesAsync returned Nothing.")
|
||||
_CachedTables = New List(Of String)
|
||||
SetCachedTables(Enumerable.Empty(Of String)())
|
||||
Return
|
||||
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}]",
|
||||
_Channel.State,
|
||||
Channel.ActiveConnections,
|
||||
_CachedTables.Count)
|
||||
oChannel.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
|
||||
Logger.Warn("⚠ UpdateTimer: TCP connection lost. Triggering reconnect...")
|
||||
SetOffline()
|
||||
Reconnect()
|
||||
|
||||
Catch ex As CommunicationException
|
||||
Logger.Warn("⚠ UpdateTimer: Channel faulted. Triggering reconnect...")
|
||||
_IsOnline = False
|
||||
SetOffline()
|
||||
Reconnect()
|
||||
|
||||
Catch ex As Exception
|
||||
Logger.Warn("⚠ Update of CachedTable was not successful!")
|
||||
Logger.Error(ex)
|
||||
_CachedTables = New List(Of String)
|
||||
SetCachedTables(Enumerable.Empty(Of String)())
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
@@ -603,6 +651,59 @@ Public Class Client
|
||||
End Function
|
||||
|
||||
#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
|
||||
EnsureAvailable()
|
||||
Try
|
||||
@@ -610,6 +711,8 @@ Public Class Client
|
||||
.SqlCommand = pSQL,
|
||||
.NamedDatabase = DatabaseName.IDB
|
||||
})
|
||||
Catch ex As TimeoutException ' ← NEU
|
||||
HandleCommunicationError(ex, NameOf(GetDatatableFromIDB))
|
||||
Catch ex As EndpointNotFoundException
|
||||
HandleCommunicationError(ex, NameOf(GetDatatableFromIDB))
|
||||
Catch ex As CommunicationException
|
||||
@@ -620,6 +723,7 @@ Public Class Client
|
||||
End Try
|
||||
End Function
|
||||
|
||||
' ── GetDatatable ──────────────────────────────────────────
|
||||
Public Function GetDatatableFromECM(pSQL As String) As GetDatatableResponse
|
||||
EnsureAvailable()
|
||||
Try
|
||||
@@ -627,6 +731,8 @@ Public Class Client
|
||||
.SqlCommand = pSQL,
|
||||
.NamedDatabase = DatabaseName.ECM
|
||||
})
|
||||
Catch ex As TimeoutException
|
||||
HandleCommunicationError(ex, NameOf(GetDatatableFromECM))
|
||||
Catch ex As EndpointNotFoundException
|
||||
HandleCommunicationError(ex, NameOf(GetDatatableFromECM))
|
||||
Catch ex As CommunicationException
|
||||
@@ -645,6 +751,8 @@ Public Class Client
|
||||
.NamedDatabase = DatabaseName.None,
|
||||
.ConnectionId = pConnectionId
|
||||
})
|
||||
Catch ex As TimeoutException
|
||||
HandleCommunicationError(ex, NameOf(GetDatatableFromConnection))
|
||||
Catch ex As EndpointNotFoundException
|
||||
HandleCommunicationError(ex, NameOf(GetDatatableFromConnection))
|
||||
Catch ex As CommunicationException
|
||||
@@ -662,6 +770,8 @@ Public Class Client
|
||||
.SqlCommand = pSQL,
|
||||
.NamedDatabase = DatabaseName.IDB
|
||||
})
|
||||
Catch ex As TimeoutException
|
||||
HandleCommunicationError(ex, NameOf(GetDatatableFromIDBAsync))
|
||||
Catch ex As EndpointNotFoundException
|
||||
HandleCommunicationError(ex, NameOf(GetDatatableFromIDBAsync))
|
||||
Catch ex As CommunicationException
|
||||
@@ -680,6 +790,8 @@ Public Class Client
|
||||
.SqlCommand = pSQL,
|
||||
.NamedDatabase = DatabaseName.ECM
|
||||
})
|
||||
Catch ex As TimeoutException
|
||||
HandleCommunicationError(ex, NameOf(GetDatatableFromECMAsync))
|
||||
Catch ex As EndpointNotFoundException
|
||||
HandleCommunicationError(ex, NameOf(GetDatatableFromECMAsync))
|
||||
Catch ex As CommunicationException
|
||||
@@ -698,6 +810,8 @@ Public Class Client
|
||||
.NamedDatabase = DatabaseName.None,
|
||||
.ConnectionId = pConnectionId
|
||||
})
|
||||
Catch ex As TimeoutException
|
||||
HandleCommunicationError(ex, NameOf(GetDatatableFromConnectionAsync))
|
||||
Catch ex As EndpointNotFoundException
|
||||
HandleCommunicationError(ex, NameOf(GetDatatableFromConnectionAsync))
|
||||
Catch ex As CommunicationException
|
||||
@@ -707,26 +821,8 @@ Public Class Client
|
||||
Throw
|
||||
End Try
|
||||
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
|
||||
EnsureAvailable()
|
||||
Try
|
||||
@@ -734,6 +830,8 @@ Public Class Client
|
||||
.SqlCommand = pSQL,
|
||||
.NamedDatabase = DatabaseName.ECM
|
||||
})
|
||||
Catch ex As TimeoutException
|
||||
HandleCommunicationError(ex, NameOf(GetScalarValueFromECM))
|
||||
Catch ex As EndpointNotFoundException
|
||||
HandleCommunicationError(ex, NameOf(GetScalarValueFromECM))
|
||||
Catch ex As CommunicationException
|
||||
@@ -752,6 +850,8 @@ Public Class Client
|
||||
.NamedDatabase = DatabaseName.None,
|
||||
.ConnectionId = pConnectionId
|
||||
})
|
||||
Catch ex As TimeoutException
|
||||
HandleCommunicationError(ex, NameOf(GetScalarValueFromConnection))
|
||||
Catch ex As EndpointNotFoundException
|
||||
HandleCommunicationError(ex, NameOf(GetScalarValueFromConnection))
|
||||
Catch ex As CommunicationException
|
||||
@@ -769,6 +869,8 @@ Public Class Client
|
||||
.SqlCommand = pSQL,
|
||||
.NamedDatabase = DatabaseName.IDB
|
||||
})
|
||||
Catch ex As TimeoutException
|
||||
HandleCommunicationError(ex, NameOf(GetScalarValueFromIDBAsync))
|
||||
Catch ex As EndpointNotFoundException
|
||||
HandleCommunicationError(ex, NameOf(GetScalarValueFromIDBAsync))
|
||||
Catch ex As CommunicationException
|
||||
@@ -786,6 +888,8 @@ Public Class Client
|
||||
.SqlCommand = pSQL,
|
||||
.NamedDatabase = DatabaseName.ECM
|
||||
})
|
||||
Catch ex As TimeoutException
|
||||
HandleCommunicationError(ex, NameOf(GetScalarValueFromECMAsync))
|
||||
Catch ex As EndpointNotFoundException
|
||||
HandleCommunicationError(ex, NameOf(GetScalarValueFromECMAsync))
|
||||
Catch ex As CommunicationException
|
||||
@@ -804,6 +908,8 @@ Public Class Client
|
||||
.NamedDatabase = DatabaseName.None,
|
||||
.ConnectionId = pConnectionId
|
||||
})
|
||||
Catch ex As TimeoutException
|
||||
HandleCommunicationError(ex, NameOf(GetScalarValueFromConnectionAsync))
|
||||
Catch ex As EndpointNotFoundException
|
||||
HandleCommunicationError(ex, NameOf(GetScalarValueFromConnectionAsync))
|
||||
Catch ex As CommunicationException
|
||||
@@ -813,28 +919,8 @@ Public Class Client
|
||||
Throw
|
||||
End Try
|
||||
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))
|
||||
Throw
|
||||
Catch ex As CommunicationException
|
||||
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDB))
|
||||
Throw
|
||||
Catch ex As Exception
|
||||
Logger.Error(ex)
|
||||
Throw
|
||||
End Try
|
||||
End Function
|
||||
|
||||
' ── ExecuteNonQuery ────────────────────────────────────────
|
||||
Public Function ExecuteNonQueryFromECM(pSQL As String) As ExecuteNonQueryResponse
|
||||
EnsureAvailable()
|
||||
Try
|
||||
@@ -842,6 +928,8 @@ Public Class Client
|
||||
.SqlCommand = pSQL,
|
||||
.NamedDatabase = DatabaseName.ECM
|
||||
})
|
||||
Catch ex As TimeoutException
|
||||
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromECM))
|
||||
Catch ex As EndpointNotFoundException
|
||||
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromECM))
|
||||
Catch ex As CommunicationException
|
||||
@@ -860,6 +948,8 @@ Public Class Client
|
||||
.NamedDatabase = DatabaseName.None,
|
||||
.ConnectionId = pConnectionId
|
||||
})
|
||||
Catch ex As TimeoutException
|
||||
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromConnection))
|
||||
Catch ex As EndpointNotFoundException
|
||||
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromConnection))
|
||||
Catch ex As CommunicationException
|
||||
@@ -877,6 +967,8 @@ Public Class Client
|
||||
.SqlCommand = pSQL,
|
||||
.NamedDatabase = DatabaseName.IDB
|
||||
})
|
||||
Catch ex As TimeoutException
|
||||
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDBAsync))
|
||||
Catch ex As EndpointNotFoundException
|
||||
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDBAsync))
|
||||
Catch ex As CommunicationException
|
||||
@@ -894,6 +986,8 @@ Public Class Client
|
||||
.SqlCommand = pSQL,
|
||||
.NamedDatabase = DatabaseName.ECM
|
||||
})
|
||||
Catch ex As TimeoutException
|
||||
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromECMAsync))
|
||||
Catch ex As EndpointNotFoundException
|
||||
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromECMAsync))
|
||||
Catch ex As CommunicationException
|
||||
@@ -912,6 +1006,8 @@ Public Class Client
|
||||
.NamedDatabase = DatabaseName.None,
|
||||
.ConnectionId = pConnectionId
|
||||
})
|
||||
Catch ex As TimeoutException
|
||||
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromConnectionAsync))
|
||||
Catch ex As EndpointNotFoundException
|
||||
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromConnectionAsync))
|
||||
Catch ex As CommunicationException
|
||||
@@ -921,45 +1017,26 @@ Public Class Client
|
||||
Throw
|
||||
End Try
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
Try
|
||||
Dim oResponse As DocumentInfoResponse = _Channel.GetFileInfoByObjectId(New DocumentInfoRequest With {
|
||||
@@ -974,15 +1051,15 @@ Public Class Client
|
||||
|
||||
Catch ex As FaultException(Of ObjectDoesNotExistFault)
|
||||
Logger.Error(ex)
|
||||
Throw ex
|
||||
Throw
|
||||
|
||||
Catch ex As FaultException
|
||||
Logger.Error(ex)
|
||||
Throw ex
|
||||
Throw
|
||||
|
||||
Catch ex As Exception
|
||||
Logger.Error(ex)
|
||||
Throw ex
|
||||
Throw
|
||||
|
||||
End Try
|
||||
End Function
|
||||
@@ -1001,10 +1078,21 @@ Public Class Client
|
||||
}
|
||||
Catch ex As Exception
|
||||
Logger.Error(ex)
|
||||
Throw ex
|
||||
Throw
|
||||
End Try
|
||||
End Function
|
||||
''' <summary>
|
||||
''' Returns a live monitoring snapshot from the service (active sessions, users)
|
||||
''' </summary>
|
||||
Public Function GetServiceStatus() As ServiceStatusResponse
|
||||
EnsureAvailable()
|
||||
Try
|
||||
Return _Channel.GetServiceStatus()
|
||||
Catch ex As Exception
|
||||
Logger.Error(ex)
|
||||
Return Nothing
|
||||
End Try
|
||||
End Function
|
||||
|
||||
|
||||
#Region "Private Functions"
|
||||
|
||||
@@ -1024,12 +1112,25 @@ Public Class Client
|
||||
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}]")
|
||||
If Not IsOnline Then
|
||||
' Wenn Reconnect aktiv läuft: kurze Grace-Period (max 1s) statt 10s
|
||||
If _IsReconnecting = 1 Then
|
||||
Dim oGraceLimit = DateTime.Now.AddMilliseconds(1000)
|
||||
While DateTime.Now < oGraceLimit AndAlso Not IsOnline
|
||||
Thread.Sleep(100)
|
||||
End While
|
||||
End If
|
||||
|
||||
If Not _IsOnline Then
|
||||
Throw New InvalidOperationException("Client is offline.")
|
||||
' Danach sofort werfen – WCF-Timeout nicht weiter aufbrauchen
|
||||
If Not IsOnline Then
|
||||
Throw New InvalidOperationException(
|
||||
$"Client is offline. Reconnect in progress=[{_IsReconnecting = 1}].")
|
||||
End If
|
||||
End If
|
||||
|
||||
If _Channel.State <> CommunicationState.Opened Then
|
||||
Throw New InvalidOperationException(
|
||||
$"Channel is not available. State=[{_Channel.State}]")
|
||||
End If
|
||||
End Sub
|
||||
''' <summary>
|
||||
@@ -1037,16 +1138,18 @@ Public Class Client
|
||||
''' </summary>
|
||||
Private Sub HandleCommunicationError(ex As Exception, callerName As String)
|
||||
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
|
||||
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
|
||||
Logger.Warn("⚠ in {0} during call. Triggering reconnect...", callerName)
|
||||
Logger.Warn("⚠ CommunicationException in [{0}] during call. Triggering reconnect...", callerName)
|
||||
Case Else
|
||||
Logger.Warn("⚠ Unexpected exception in {0} during call. Triggering reconnect...", callerName)
|
||||
Logger.Warn("⚠ Unexpected exception in [{0}] during call. Triggering reconnect...", callerName)
|
||||
Logger.Error(ex)
|
||||
End Select
|
||||
|
||||
_IsOnline = False
|
||||
SetOffline()
|
||||
Reconnect()
|
||||
Throw ex
|
||||
End Sub
|
||||
@@ -1143,15 +1246,16 @@ Public Class Client
|
||||
''' Returns a detailed status snapshot of the current client state
|
||||
''' </summary>
|
||||
Public Function GetStatus() As ClientStatus
|
||||
Dim oTables = CachedTables ' ← Property nutzen: enthält Read-Lock + Snapshot
|
||||
Return New ClientStatus() With {
|
||||
.IsOnline = _IsOnline,
|
||||
.ClientId = _ClientId, ' ← Neu
|
||||
.IsOnline = IsOnline, ' ← Property statt Feld
|
||||
.ClientId = _ClientId,
|
||||
.ClientName = _ClientName,
|
||||
.ServerAddress = Me.ServerAddress,
|
||||
.ChannelState = If(_Channel IsNot Nothing, _Channel.State.ToString(), "null"),
|
||||
.ActiveConnections = Channel.ActiveConnections,
|
||||
.CachedTableCount = _CachedTables.Count,
|
||||
.CachedTables = _CachedTables.ToList(),
|
||||
.CachedTableCount = oTables.Count, ' ← aus Snapshot
|
||||
.CachedTables = oTables, ' ← aus Snapshot
|
||||
.HasClientConfig = _ClientConfig IsNot Nothing,
|
||||
.IsDisposed = _Disposed,
|
||||
.TimerEnabled = UpdateTimer.Enabled,
|
||||
@@ -1162,7 +1266,7 @@ Public Class Client
|
||||
|
||||
Public Class ClientStatus
|
||||
Public Property IsOnline As Boolean
|
||||
Public Property ClientId As String ' ← Neu
|
||||
Public Property ClientId As String
|
||||
Public Property ClientName As String
|
||||
Public Property ServerAddress As String
|
||||
Public Property ChannelState As String
|
||||
@@ -1186,6 +1290,6 @@ Public Class Client
|
||||
"[ClientStatus] Client=[{0}] Id=[{1}] Online=[{2}] Server=[{3}] Channel=[{4}]",
|
||||
ClientName, ClientId, IsOnline, ServerAddress, ChannelState)
|
||||
End Function
|
||||
End Class
|
||||
End Class ' ← ClientStatus endet hier – kein GetServiceStatus mehr drin
|
||||
|
||||
End Class
|
||||
|
||||
@@ -8,10 +8,14 @@ Imports DigitalData.Modules.Logging
|
||||
Public Class Channel
|
||||
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)
|
||||
|
||||
Public Event Reconnect As EventHandler
|
||||
' Verbindungszähler
|
||||
|
||||
Private Shared _ActiveConnections As Integer = 0
|
||||
Public Shared ReadOnly Property ActiveConnections As Integer
|
||||
Get
|
||||
@@ -20,16 +24,18 @@ Public Class Channel
|
||||
End Property
|
||||
|
||||
Public Shared Function GetBinding(Optional AuthenticationMode As TcpClientCredentialType = TcpClientCredentialType.Windows) As NetTcpBinding
|
||||
Dim oMaxConnections = Math.Max(500, Environment.ProcessorCount * 50)
|
||||
|
||||
Return New NetTcpBinding() With {
|
||||
.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,
|
||||
.ListenBacklog = oMaxConnections,
|
||||
.MaxConnections = oMaxConnections,
|
||||
.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
|
||||
.SendTimeout = TimeSpan.FromSeconds(60),
|
||||
.ReceiveTimeout = TimeSpan.FromMinutes(10),
|
||||
.OpenTimeout = TimeSpan.FromSeconds(60),
|
||||
.CloseTimeout = TimeSpan.FromSeconds(30),
|
||||
.Security = New NetTcpSecurity() With {
|
||||
.Mode = SecurityMode.Transport,
|
||||
@@ -46,14 +52,63 @@ Public Class Channel
|
||||
|
||||
Public Sub New(pLogConfig As LogConfig, pServerAddress As ServerAddressStruct)
|
||||
MyBase.New(pLogConfig)
|
||||
ChannelFactory = GetChannelFactory(pServerAddress)
|
||||
ChannelFactory = GetOrCreateSharedFactory(pLogConfig, pServerAddress)
|
||||
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>
|
||||
''' Closes a channel gracefully, falls back to Abort on error
|
||||
''' </summary>
|
||||
Public Shared Sub CloseChannel(pChannel As IEDMIServiceChannel)
|
||||
If pChannel Is Nothing Then Return
|
||||
|
||||
Try
|
||||
If pChannel.State = CommunicationState.Opened Then
|
||||
pChannel.Close()
|
||||
@@ -61,31 +116,27 @@ Public Class Channel
|
||||
pChannel.State = CommunicationState.Closing Then
|
||||
pChannel.Abort()
|
||||
End If
|
||||
' CommunicationState.Created / Closed → nichts tun, Zähler nicht betroffen
|
||||
Catch ex As Exception
|
||||
Try
|
||||
pChannel.Abort()
|
||||
Catch
|
||||
' Ignore
|
||||
End Try
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
''' <summary>
|
||||
''' Creates a channel and adds a Faulted-Handler
|
||||
''' </summary>
|
||||
''' <returns>A channel object</returns>
|
||||
Public Function GetChannel() As IEDMIServiceChannel
|
||||
Try
|
||||
Logger.Debug("Creating channel.")
|
||||
Logger.Debug("Creating channel from shared factory.")
|
||||
Dim oChannel = ChannelFactory.CreateChannel()
|
||||
|
||||
' 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)
|
||||
@@ -93,7 +144,7 @@ Public Class Channel
|
||||
|
||||
AddHandler oChannel.Faulted, Sub()
|
||||
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)
|
||||
End Sub
|
||||
|
||||
@@ -104,20 +155,4 @@ Public Class Channel
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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>
|
||||
<GenerateAsynchronousMethods>false</GenerateAsynchronousMethods>
|
||||
<GenerateTaskBasedAsynchronousMethod>true</GenerateTaskBasedAsynchronousMethod>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
<?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 />
|
||||
<bindings>
|
||||
<binding digest="System.ServiceModel.Configuration.NetTcpBindingElement, System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089:<?xml version="1.0" encoding="utf-16"?><Data name="NetTcpBinding_IEDMIService" transferMode="Streamed"><security><transport sslProtocols="None" /></security></Data>" bindingType="netTcpBinding" name="NetTcpBinding_IEDMIService" />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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>
|
||||
<bindingConfiguration bindingType="netTcpBinding" name="NetTcpBinding_IEDMIService">
|
||||
<properties>
|
||||
|
||||
@@ -342,14 +342,17 @@ Public Class DatabaseWithFallback
|
||||
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)
|
||||
_Logger.Debug("GetDatatableFromServiceAsync called | SQL=[{0}] | DB=[{1}] | ConnId=[{2}]",
|
||||
pSQLCommand, DatabaseType.ToString, pConnectionId)
|
||||
|
||||
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
|
||||
|
||||
Dim oResult As GetDatatableResponse = Nothing
|
||||
Try
|
||||
Dim oResult As GetDatatableResponse = Nothing
|
||||
|
||||
Select Case DatabaseType
|
||||
Case Constants.DatabaseType.ECM
|
||||
oResult = Await _Client.GetDatatableFromECMAsync(pSQLCommand)
|
||||
@@ -360,20 +363,22 @@ Public Class DatabaseWithFallback
|
||||
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))
|
||||
_Logger.Warn("⚠ GetDatatableFromServiceAsync failed | Fallback to DB | Error=[{0}]",
|
||||
If(oResult Is Nothing, "null", oResult.ErrorMessage))
|
||||
Return GetDatatableFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
|
||||
End If
|
||||
|
||||
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
|
||||
Private Function GetDatatableFromDatabase(pSQLCommand As String, pDatabaseType As Constants.DatabaseType, pConnectionId As Integer) As DataTable
|
||||
Try
|
||||
@@ -401,16 +406,16 @@ 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)
|
||||
_Logger.Debug("GetScalarValueFromServiceAsync called | SQL=[{0}] | DB=[{1}] | ConnId=[{2}]",
|
||||
pSQLCommand, DatabaseType.ToString, pConnectionId)
|
||||
|
||||
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
|
||||
|
||||
Dim oResult As GetScalarValueResponse = Nothing
|
||||
Try
|
||||
Dim oResult As GetScalarValueResponse = Nothing
|
||||
|
||||
Select Case DatabaseType
|
||||
Case Constants.DatabaseType.ECM
|
||||
oResult = Await _Client.GetScalarValueFromECMAsync(pSQLCommand)
|
||||
@@ -421,20 +426,21 @@ Public Class DatabaseWithFallback
|
||||
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))
|
||||
_Logger.Warn("⚠ GetScalarValueFromServiceAsync failed | Fallback to DB")
|
||||
Return GetScalarValueFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
|
||||
End If
|
||||
|
||||
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
|
||||
Private Function GetScalarValueFromService(pSQLCommand As String, DatabaseType As Constants.DatabaseType, pConnectionId As Integer) As Object
|
||||
Try
|
||||
@@ -493,16 +499,16 @@ 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)
|
||||
_Logger.Debug("ExecuteNonQueryFromServiceAsync called | SQL=[{0}] | DB=[{1}] | ConnId=[{2}]",
|
||||
pSQLCommand, DatabaseType.ToString, pConnectionId)
|
||||
|
||||
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
|
||||
|
||||
Dim oResult As ExecuteNonQueryResponse = Nothing
|
||||
Try
|
||||
Dim oResult As ExecuteNonQueryResponse = Nothing
|
||||
|
||||
Select Case DatabaseType
|
||||
Case Constants.DatabaseType.ECM
|
||||
oResult = Await _Client.ExecuteNonQueryFromECMAsync(pSQLCommand)
|
||||
@@ -513,20 +519,21 @@ Public Class DatabaseWithFallback
|
||||
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))
|
||||
_Logger.Warn("⚠ ExecuteNonQueryFromServiceAsync failed | Fallback to DB")
|
||||
Return ExecuteNonQueryFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
|
||||
End If
|
||||
|
||||
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
|
||||
Private Function ExecuteNonQueryFromService(pSQLCommand As String, DatabaseType As Constants.DatabaseType, pConnectionId As Integer) As Boolean
|
||||
Try
|
||||
|
||||
@@ -311,6 +311,9 @@
|
||||
<LastGenOutput>Reference.vb</LastGenOutput>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="ErrorLog.txt" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.VisualBasic.targets" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>powershell.exe -command "& { &'$(SolutionDir)copy-binary.ps1' '$(TargetPath)' '$(TargetFileName)' '$(ConfigurationName)' '$(ProjectName)' }"</PostBuildEvent>
|
||||
|
||||
@@ -13,7 +13,7 @@ Imports System.Runtime.InteropServices
|
||||
<Assembly: AssemblyCompany("Digital Data")>
|
||||
<Assembly: AssemblyProduct("EDMIAPI")>
|
||||
<Assembly: AssemblyCopyright("Copyright © 2026")>
|
||||
<Assembly: AssemblyTrademark("2.7.0")>
|
||||
<Assembly: AssemblyTrademark("2.8.0")>
|
||||
|
||||
<Assembly: ComVisible(False)>
|
||||
|
||||
@@ -31,5 +31,5 @@ Imports System.Runtime.InteropServices
|
||||
' übernehmen, indem Sie "*" eingeben:
|
||||
' <Assembly: AssemblyVersion("1.0.*")>
|
||||
|
||||
<Assembly: AssemblyVersion("2.7.0.0")>
|
||||
<Assembly: AssemblyVersion("2.8.0.0")>
|
||||
<Assembly: AssemblyFileVersion("1.6.1.1")>
|
||||
|
||||
3401
EDMIAPI/Reference.vb
Normal file
3401
EDMIAPI/Reference.vb
Normal file
File diff suppressed because it is too large
Load Diff
@@ -510,7 +510,6 @@ Public Class LogConfig
|
||||
' Add debug rule, if configured
|
||||
If _EnableDebugLogging = True Then
|
||||
_config.AddRule(LogLevel.Debug, LogLevel.Error, TARGET_DEBUG)
|
||||
oLogger.Info("DEBUG Logging is now Enabled.")
|
||||
Else
|
||||
oLogger.Debug("DEBUG Logging is now Disabled.")
|
||||
End If
|
||||
|
||||
@@ -13,7 +13,7 @@ Imports System.Runtime.InteropServices
|
||||
<Assembly: AssemblyCompany("Digital Data GmbH, Heuchelheim")>
|
||||
<Assembly: AssemblyProduct("Modules.Logging")>
|
||||
<Assembly: AssemblyCopyright("Copyright © 2025")>
|
||||
<Assembly: AssemblyTrademark("2.6.4.0")>
|
||||
<Assembly: AssemblyTrademark("2.7.0.0")>
|
||||
|
||||
<Assembly: ComVisible(False)>
|
||||
|
||||
@@ -31,5 +31,5 @@ Imports System.Runtime.InteropServices
|
||||
' übernehmen, indem Sie "*" eingeben:
|
||||
' <Assembly: AssemblyVersion("1.0.*")>
|
||||
|
||||
<Assembly: AssemblyVersion("2.6.5.0")>
|
||||
<Assembly: AssemblyFileVersion("2.6.5.0")>
|
||||
<Assembly: AssemblyVersion("2.7.0.0")>
|
||||
<Assembly: AssemblyFileVersion("2.7.0.0")>
|
||||
|
||||
@@ -247,7 +247,7 @@ Namespace Mail
|
||||
|
||||
Try
|
||||
If pSession.User <> String.Empty Then
|
||||
Logger.Info("Logging in with user [{0}] and Auth Type [{1}]", pSession.User, pSession.AuthType)
|
||||
Logger.Debug("Logging in with user [{0}] and Auth Type [{1}]", pSession.User, pSession.AuthType)
|
||||
|
||||
If pSession.AuthType = AUTH_OAUTH2 Then
|
||||
' Use OAuth2 token
|
||||
@@ -273,10 +273,10 @@ Namespace Mail
|
||||
Public Function DisconnectFromServer() As Boolean
|
||||
Try
|
||||
If Client IsNot Nothing Then
|
||||
Logger.Debug("Closing connection to Server [{0}].", Session.Server)
|
||||
Logger.Debug("Closing connection to Server [{0}]...", Session.Server)
|
||||
DoClose(Client)
|
||||
Session.Connected = False
|
||||
Logger.Info("Connection to Server [{0}] closed.", Session.Server)
|
||||
Logger.Debug("Connection to Server [{0}] closed.", Session.Server)
|
||||
Else
|
||||
Logger.Debug("No connection currently open. Exiting.")
|
||||
End If
|
||||
|
||||
@@ -40,6 +40,8 @@ Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Database.Test", "Database.T
|
||||
EndProject
|
||||
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Filesystem", "Filesystem\Filesystem.vbproj", "{991D0231-4623-496D-8BD0-9CA906029CBC}"
|
||||
EndProject
|
||||
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "EDMIService", "..\DDMonorepo\Service.EDMIService\EDMIService.vbproj", "{A8C3F298-76AB-4359-AB3C-986E313B4336}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -114,6 +116,10 @@ Global
|
||||
{991D0231-4623-496D-8BD0-9CA906029CBC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{991D0231-4623-496D-8BD0-9CA906029CBC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{991D0231-4623-496D-8BD0-9CA906029CBC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A8C3F298-76AB-4359-AB3C-986E313B4336}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A8C3F298-76AB-4359-AB3C-986E313B4336}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A8C3F298-76AB-4359-AB3C-986E313B4336}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A8C3F298-76AB-4359-AB3C-986E313B4336}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
Reference in New Issue
Block a user