Die Änderungen umfassen das Hinzufügen von neuen `.resx`-Dateien für verschiedene Formulare, die Ressourcen wie Bilder und Texte enthalten. XML-Schemata und Metadaten wurden hinzugefügt, um die Struktur der Ressourcen zu definieren. Neue `.vbproj`-Dateien wurden hinzugefügt, die die Kompilierungs- und Build-Konfigurationen spezifizieren, einschließlich Referenzen zu DevExpress-Komponenten und anderen Bibliotheken. Eingebettete Ressourcen und Projektverweise wurden ebenfalls hinzugefügt. Build-Ziele und Bedingungen wurden definiert, um die Einbindung aller erforderlichen NuGet-Pakete sicherzustellen. Verbesserungen und Caching-Updates In `MSSQLServer.vb` wurde `NOLOCK` zur SQL-Abfrage hinzugefügt, um die Leistung zu verbessern. Die Assembly-Versionen in `AssemblyInfo.vb` wurden aktualisiert. In `DatabaseWithFallback.vb` wurden umfangreiche Änderungen vorgenommen, darunter die Einführung von Caches und Unterstützung für URI-Cache-Syntax. Einige Projekte wurden aus `Modules.sln` entfernt. DevExpress-Referenzen in `Patterns.vbproj` wurden aktualisiert. Eine Backup-Datei `Patterns.vbproj.bak` wurde hinzugefügt.
911 lines
46 KiB
VB.net
911 lines
46 KiB
VB.net
Imports System.Collections.Concurrent
|
||
Imports System.Data.SqlClient
|
||
Imports DigitalData.Modules.Database
|
||
Imports DigitalData.Modules.EDMI.API
|
||
Imports DigitalData.Modules.EDMI.API.Constants
|
||
Imports DigitalData.Modules.EDMI.API.EDMIServiceReference
|
||
Imports DigitalData.Modules.Logging
|
||
|
||
Public Class DatabaseWithFallback
|
||
Private ReadOnly _Logger As Logger
|
||
Private ReadOnly _Client As Client
|
||
Private ReadOnly _ClientConfig As GlobalStateClientConfiguration
|
||
Private ReadOnly _DatabaseECM As MSSQLServer
|
||
Private _DatabaseIDB As MSSQLServer
|
||
''' <summary>
|
||
''' Lokaler Override-Cache für ExecuteAppServiceWrite-Änderungen.
|
||
''' Schlüssel: "TABELLENNAME|FILTERAUSDRUCK" → geänderter DataTable-Snapshot.
|
||
''' Wird beim nächsten Server-Refresh (Client.UpdateTimer) automatisch obsolet.
|
||
''' </summary>
|
||
Private ReadOnly _localOverrideCache As New ConcurrentDictionary(Of String, DataTable)()
|
||
''' <summary>Erstellt den Cache-Key aus Tabellenname und Filter.</summary>
|
||
Private Shared Function GetOverrideCacheKey(pTable As String, pFilter As String) As String
|
||
Return $"{pTable.ToUpper}|{pFilter}"
|
||
End Function
|
||
|
||
''' <summary>Leert den lokalen Override-Cache (z.B. nach manuellem Refresh).</summary>
|
||
Public Sub InvalidateLocalOverrideCache()
|
||
_localOverrideCache.Clear()
|
||
_Logger.Debug("InvalidateLocalOverrideCache: Lokaler Override-Cache geleert.")
|
||
End Sub
|
||
|
||
''' <summary>Leert den Override-Cache für eine bestimmte Tabelle.</summary>
|
||
Public Sub InvalidateLocalOverrideCache(pTableName As String)
|
||
Dim oKeysToRemove = _localOverrideCache.Keys.
|
||
Where(Function(k) k.StartsWith(pTableName.ToUpper & "|")).ToList()
|
||
For Each oKey In oKeysToRemove
|
||
Dim oRemoved As DataTable = Nothing
|
||
_localOverrideCache.TryRemove(oKey, oRemoved)
|
||
Next
|
||
_Logger.Debug("InvalidateLocalOverrideCache: Override-Cache für Tabelle [{0}] geleert.", pTableName)
|
||
End Sub
|
||
''' <summary>
|
||
''' Thread-sicherer Cache für Connection Strings, indexiert nach Connection-ID.
|
||
''' Verhindert wiederholte DB-Roundtrips für SELECT * FROM TBDD_CONNECTION.
|
||
''' </summary>
|
||
Private ReadOnly _connectionStringCache As New ConcurrentDictionary(Of Integer, String)
|
||
|
||
''' <summary>
|
||
''' Liefert den Connection String für eine bekannte Connection-ID.
|
||
''' Beim ersten Aufruf wird der Wert aus der DB geladen und gecacht.
|
||
''' Hinweis: Bei parallelen Erstabrufen derselben ID kann die Factory mehrfach
|
||
''' aufgerufen werden – da der Wert idempotent ist, ist dies funktional korrekt.
|
||
''' </summary>
|
||
Private Function GetCachedConnectionString(pConnectionId As Integer) As String
|
||
Return _connectionStringCache.GetOrAdd(pConnectionId,
|
||
Function(id)
|
||
_Logger.Debug("GetCachedConnectionString: Cache miss für ConnectionId [{0}] - lade aus DB", id)
|
||
Dim oConnectionString = _DatabaseECM.Get_ConnectionStringforID(id)
|
||
_Logger.Debug("GetCachedConnectionString: ConnectionId [{0}] gecacht", id)
|
||
Return oConnectionString
|
||
End Function)
|
||
End Function
|
||
''' <summary>
|
||
''' Leert den Connection-String-Cache, z.B. nach Konfigurationsänderungen.
|
||
''' </summary>
|
||
Public Sub InvalidateConnectionCache()
|
||
_connectionStringCache.Clear()
|
||
_Logger.Debug("InvalidateConnectionCache: Connection-String-Cache geleert")
|
||
End Sub
|
||
''' <summary>
|
||
''' Leert den Cache für eine bestimmte Connection-ID.
|
||
''' </summary>
|
||
Public Sub InvalidateConnectionCache(pConnectionId As Integer)
|
||
Dim removed As String = Nothing
|
||
If _connectionStringCache.TryRemove(pConnectionId, removed) Then
|
||
_Logger.Debug("InvalidateConnectionCache: ConnectionId [{0}] aus Cache entfernt", pConnectionId)
|
||
End If
|
||
End Sub
|
||
''' <summary>
|
||
''' Options for GetDatatable
|
||
''' </summary>
|
||
Public Class GetDatatableOptions
|
||
Public ReadOnly FallbackSQL
|
||
Public ReadOnly FallbackType
|
||
|
||
''' <summary>
|
||
''' Filter expression for the cached Datatable
|
||
''' </summary>
|
||
Public Property FilterExpression As String = ""
|
||
''' <summary>
|
||
''' Columns to sort the cached Datatable by
|
||
''' </summary>
|
||
Public Property SortByColumn As String = ""
|
||
''' <summary>
|
||
''' Force the fallback, skipping the service completely
|
||
''' </summary>
|
||
Public Property ForceFallback As Boolean = False
|
||
''' <summary>
|
||
''' Connection Id to use, references TBDD_CONNECTION
|
||
''' </summary>
|
||
Public Property ConnectionId As Integer = 0
|
||
''' <summary>
|
||
''' Erkennt URI-Cache-Syntax: "TABELLENNAME?FILTERAUSDRUCK"
|
||
''' Beispiel: "TBDD_USER?USER_ID=4524"
|
||
''' </summary>
|
||
Public ReadOnly Property IsCacheQuery As Boolean
|
||
Get
|
||
If String.IsNullOrWhiteSpace(FallbackSQL) Then Return False
|
||
Dim oTrimmed As String = FallbackSQL.TrimStart()
|
||
' Nur wenn kein SQL-Schlüsselwort vorhanden → Cache-Routing-Syntax
|
||
Return FallbackSQL.Contains("?") AndAlso
|
||
Not oTrimmed.StartsWith("SELECT", StringComparison.OrdinalIgnoreCase) AndAlso
|
||
Not oTrimmed.StartsWith("INSERT", StringComparison.OrdinalIgnoreCase) AndAlso
|
||
Not oTrimmed.StartsWith("UPDATE", StringComparison.OrdinalIgnoreCase) AndAlso
|
||
Not oTrimmed.StartsWith("DELETE", StringComparison.OrdinalIgnoreCase) AndAlso
|
||
Not oTrimmed.Contains("DECLARE") AndAlso
|
||
Not oTrimmed.StartsWith("EXEC", StringComparison.OrdinalIgnoreCase) AndAlso
|
||
Not oTrimmed.StartsWith("DECLARE", StringComparison.OrdinalIgnoreCase)
|
||
End Get
|
||
End Property
|
||
Public ReadOnly Property IsCacheWriteQuery As Boolean
|
||
Get
|
||
If String.IsNullOrWhiteSpace(FallbackSQL) Then Return False
|
||
Dim oTrimmed As String = FallbackSQL.TrimStart()
|
||
Return FallbackSQL.Contains("?") AndAlso
|
||
FallbackSQL.Contains("!") AndAlso
|
||
Not oTrimmed.StartsWith("SELECT", StringComparison.OrdinalIgnoreCase) AndAlso
|
||
Not oTrimmed.StartsWith("INSERT", StringComparison.OrdinalIgnoreCase) AndAlso
|
||
Not oTrimmed.StartsWith("UPDATE", StringComparison.OrdinalIgnoreCase) AndAlso
|
||
Not oTrimmed.StartsWith("DELETE", StringComparison.OrdinalIgnoreCase) AndAlso
|
||
Not oTrimmed.StartsWith("EXEC", StringComparison.OrdinalIgnoreCase) AndAlso
|
||
Not oTrimmed.StartsWith("DECLARE", StringComparison.OrdinalIgnoreCase) AndAlso
|
||
Not oTrimmed.StartsWith("--", StringComparison.OrdinalIgnoreCase) AndAlso
|
||
Not oTrimmed.StartsWith("/*", StringComparison.OrdinalIgnoreCase)
|
||
End Get
|
||
End Property
|
||
''' <summary>"TBDD_USER?USER_ID=4524" → "TBDD_USER"</summary>
|
||
Public ReadOnly Property CacheQueryTable As String
|
||
Get
|
||
If Not IsCacheQuery Then Return String.Empty
|
||
Return FallbackSQL.Substring(0, FallbackSQL.IndexOf("?")).Trim()
|
||
End Get
|
||
End Property
|
||
|
||
''' <summary>"TBDD_USER?USER_ID=4524" → "USER_ID=4524"</summary>
|
||
Public ReadOnly Property CacheQueryFilter As String
|
||
Get
|
||
If Not IsCacheQuery Then Return String.Empty
|
||
Return FallbackSQL.Substring(FallbackSQL.IndexOf("?") + 1).Trim()
|
||
End Get
|
||
End Property
|
||
''' <summary>"TBDD_USER?USER_ID=4524!STATUS=1" → "TBDD_USER"</summary>
|
||
Public ReadOnly Property CacheWriteTable As String
|
||
Get
|
||
If Not IsCacheWriteQuery Then Return String.Empty
|
||
Return FallbackSQL.Substring(0, FallbackSQL.IndexOf("?")).Trim()
|
||
End Get
|
||
End Property
|
||
|
||
''' <summary>"TBDD_USER?USER_ID=4524!STATUS=1" → "USER_ID=4524"</summary>
|
||
Public ReadOnly Property CacheWriteFilter As String
|
||
Get
|
||
If Not IsCacheWriteQuery Then Return String.Empty
|
||
Dim oStart As Integer = FallbackSQL.IndexOf("?") + 1
|
||
Dim oLength As Integer = FallbackSQL.IndexOf("!") - oStart
|
||
Return FallbackSQL.Substring(oStart, oLength).Trim()
|
||
End Get
|
||
End Property
|
||
|
||
''' <summary>"TBDD_USER?USER_ID=4524!STATUS=1" → "STATUS=1" | "DELETE"</summary>
|
||
Public ReadOnly Property CacheWriteAction As String
|
||
Get
|
||
If Not IsCacheWriteQuery Then Return String.Empty
|
||
Return FallbackSQL.Substring(FallbackSQL.IndexOf("!") + 1).Trim()
|
||
End Get
|
||
End Property
|
||
|
||
''' <summary>True wenn die Aktion ein DELETE ist</summary>
|
||
Public ReadOnly Property CacheWriteIsDelete As Boolean
|
||
Get
|
||
Return IsCacheWriteQuery AndAlso
|
||
CacheWriteAction.Equals("DELETE", StringComparison.OrdinalIgnoreCase)
|
||
End Get
|
||
End Property
|
||
|
||
''' <summary>
|
||
''' Generiert das SQL aus der URI-Write-Syntax.
|
||
''' UPDATE: "TBDD_USER?USER_ID=4524!STATUS=1" → "UPDATE TBDD_USER SET STATUS=1 WHERE USER_ID=4524"
|
||
''' DELETE: "TBDD_USER?USER_ID=4524!DELETE" → "DELETE FROM TBDD_USER WHERE USER_ID=4524"
|
||
''' </summary>
|
||
Public ReadOnly Property CacheWriteToSql As String
|
||
Get
|
||
If Not IsCacheWriteQuery Then Return String.Empty
|
||
If CacheWriteIsDelete Then
|
||
Return $"DELETE FROM {CacheWriteTable} WHERE {CacheWriteFilter}"
|
||
Else
|
||
Return $"UPDATE {CacheWriteTable} SET {CacheWriteAction} WHERE {CacheWriteFilter}"
|
||
End If
|
||
End Get
|
||
End Property
|
||
''' <summary>
|
||
''' Creates a new options object for GetDatatable options
|
||
''' </summary>
|
||
''' <param name="pFallbackSQL">SQL Command to execute as fallback</param>
|
||
''' <param name="pFallbackType">Named Database to use for the fallback SQL Command</param>
|
||
Public Sub New(pFallbackSQL As String, pFallbackType As Constants.DatabaseType)
|
||
FallbackSQL = pFallbackSQL
|
||
FallbackType = pFallbackType
|
||
End Sub
|
||
''' <summary>
|
||
''' Creates a new options object using URI-Syntax (Cache-Query or URI-Write-Query).
|
||
''' FallbackType defaults to ECM – only relevant if URI detection fails and falls back to SQL.
|
||
''' Beispiel Read: "TBDD_USER?USER_ID=4524"
|
||
''' Beispiel Write: "TBDD_USER?USER_ID=4524!STATUS=1"
|
||
''' Beispiel Write: "TBDD_USER?USER_ID=4524!DELETE"
|
||
''' </summary>
|
||
''' <param name="pURISyntax">URI-Syntax string</param>
|
||
Public Sub New(pURISyntax As String)
|
||
FallbackSQL = pURISyntax
|
||
FallbackType = Constants.DatabaseType.ECM
|
||
End Sub
|
||
End Class
|
||
Public Sub New(LogConfig As LogConfig, Client As Client, DatabaseECM As MSSQLServer, DatabaseIDB As MSSQLServer)
|
||
_Logger = LogConfig.GetLogger()
|
||
_Client = Client
|
||
_DatabaseECM = DatabaseECM
|
||
_DatabaseIDB = DatabaseIDB
|
||
|
||
_Logger.Debug("Client exists: [{0}]", Not IsNothing(Client))
|
||
_Logger.Debug("DatabaseECM exists: [{0}]", Not IsNothing(DatabaseECM))
|
||
_Logger.Debug("DatabaseIDB exists: [{0}]", Not IsNothing(DatabaseIDB))
|
||
|
||
' Load client config, will throw if client is not yet connected to service
|
||
_ClientConfig = Client?.ClientConfig
|
||
End Sub
|
||
|
||
''' <summary>
|
||
''' Set the IDB Database class after initializing the class.
|
||
''' It is now your responsibility to make sure to not use any IDB calls before calling this method.
|
||
''' </summary>
|
||
Public Sub InitializeIDB(pDatabaseIDB As MSSQLServer)
|
||
_DatabaseIDB = pDatabaseIDB
|
||
End Sub
|
||
|
||
Public Function GetConnectionString(pConnectionId As Integer) As String
|
||
Return _DatabaseECM.GetConnectionStringForId(pConnectionId)
|
||
End Function
|
||
|
||
|
||
#Region "GetDatatable"
|
||
''' <summary>URI-Cache-Syntax wird über GetDatatable(pOptions) ausgewertet.</summary>
|
||
Public Function GetDatatableECM(pSQL As String) As DataTable
|
||
Return GetDatatable(New GetDatatableOptions(pSQL, Constants.DatabaseType.ECM))
|
||
End Function
|
||
|
||
Public Async Function GetDatatableECMAsync(pSQL As String) As Task(Of DataTable)
|
||
Return Await GetDatatableFromServiceAsync(pSQL, Constants.DatabaseType.ECM, 0)
|
||
End Function
|
||
|
||
''' <summary>URI-Cache-Syntax wird über GetDatatable(pOptions) ausgewertet.</summary>
|
||
Public Function GetDatatableIDB(pSQL As String) As DataTable
|
||
Return GetDatatable(New GetDatatableOptions(pSQL, Constants.DatabaseType.IDB))
|
||
End Function
|
||
|
||
Public Async Function GetDatatableIDBAsync(pSQL As String) As Task(Of DataTable)
|
||
Return Await GetDatatableFromServiceAsync(pSQL, Constants.DatabaseType.IDB, 0)
|
||
End Function
|
||
|
||
Public Function GetDatatableWithConnection(pSQL As String, pConnectionId As Integer) As DataTable
|
||
' ===== URI-CACHE-SYNTAX: "TABELLENNAME?FILTERAUSDRUCK" =====
|
||
Dim oOptions As New GetDatatableOptions(pSQL, Constants.DatabaseType.None) With {.ConnectionId = pConnectionId}
|
||
If oOptions.IsCacheQuery Then
|
||
Return GetDatatable(oOptions.CacheQueryTable, oOptions)
|
||
End If
|
||
Return GetDatatable(oOptions)
|
||
End Function
|
||
|
||
Public Async Function GetDatatableWithConnectionAsync(pSQL As String, pConnectionId As Integer) As Task(Of DataTable)
|
||
Return Await GetDatatableFromServiceAsync(pSQL, Constants.DatabaseType.None, pConnectionId)
|
||
End Function
|
||
#End Region
|
||
|
||
#Region "GetScalarValue"
|
||
Public Function GetScalarValueECM(pSQL As String) As Object
|
||
Return GetScalarValue(pSQL, Constants.DatabaseType.ECM)
|
||
End Function
|
||
|
||
Public Async Function GetScalarValueECMAsync(pSQL As String) As Task(Of Object)
|
||
Return Await GetScalarValueFromServiceAsync(pSQL, Constants.DatabaseType.ECM, 0)
|
||
End Function
|
||
|
||
Public Function GetScalarValueIDB(pSQL As String) As Object
|
||
Return GetScalarValue(pSQL, Constants.DatabaseType.IDB)
|
||
End Function
|
||
|
||
Public Async Function GetScalarValueIDBAsync(pSQL As String) As Task(Of Object)
|
||
Return Await GetScalarValueFromServiceAsync(pSQL, Constants.DatabaseType.IDB, 0)
|
||
End Function
|
||
|
||
Public Function GetScalarValueWithConnection(pSQL As String, pConnectionId As Integer) As Object
|
||
Return GetScalarValue(pSQL, Constants.DatabaseType.None, pConnectionId:=pConnectionId)
|
||
End Function
|
||
|
||
Public Async Function GetScalarValueWithConnectionAsync(pSQL As String, pConnectionId As Integer) As Task(Of Object)
|
||
Return Await GetScalarValueFromServiceAsync(pSQL, Constants.DatabaseType.None, pConnectionId)
|
||
End Function
|
||
#End Region
|
||
|
||
#Region "ExecuteNonQuery"
|
||
' URI-Cache-Syntax ("TABELLE?FILTER") ist hier nicht anwendbar:
|
||
' ExecuteNonQuery führt schreibende Operationen aus (INSERT/UPDATE/DELETE)
|
||
' und liefert kein Ergebnis, das aus dem AppServer-Cache gelesen werden könnte.
|
||
Public Function ExecuteNonQueryECM(pSQL As String) As Boolean
|
||
Return ExecuteNonQuery(pSQL, Constants.DatabaseType.ECM)
|
||
End Function
|
||
|
||
Public Async Function ExecuteNonQueryECMAsync(pSQL As String) As Task(Of Boolean)
|
||
Return Await ExecuteNonQueryFromServiceAsync(pSQL, Constants.DatabaseType.ECM, 0)
|
||
End Function
|
||
|
||
Public Function ExecuteNonQueryIDB(pSQL As String) As Boolean
|
||
Return ExecuteNonQuery(pSQL, Constants.DatabaseType.IDB)
|
||
End Function
|
||
|
||
Public Async Function ExecuteNonQueryIDBAsync(pSQL As String) As Task(Of Boolean)
|
||
Return Await ExecuteNonQueryFromServiceAsync(pSQL, Constants.DatabaseType.IDB, 0)
|
||
End Function
|
||
|
||
Public Function ExecuteNonQueryWithConnection(pSQL As String, pConnectionId As Integer) As Boolean
|
||
Return ExecuteNonQuery(pSQL, Constants.DatabaseType.None, pConnectionId:=pConnectionId)
|
||
End Function
|
||
|
||
Public Async Function ExecuteNonQueryWithConnectionAsync(pSQL As String, pConnectionId As Integer) As Task(Of Boolean)
|
||
Return Await ExecuteNonQueryFromServiceAsync(pSQL, Constants.DatabaseType.None, pConnectionId)
|
||
End Function
|
||
#End Region
|
||
|
||
|
||
''' <summary>
|
||
''' Returns a Datatable by trying to fetch a cached version from the service, then querying the database through the service and querying the database directly as fallback.
|
||
''' </summary>
|
||
''' <param name="pDataTableName">Name of the Cached Datatable</param>
|
||
''' <param name="pOptions">Options object</param>
|
||
Public Function GetDatatable(pDataTableName As String, pOptions As GetDatatableOptions) As DataTable
|
||
' ===== URI-CACHE-SYNTAX: "TABELLENNAME?FILTERAUSDRUCK" =====
|
||
If pOptions.IsCacheQuery AndAlso Not pOptions.ForceFallback AndAlso
|
||
Not (_ClientConfig IsNot Nothing AndAlso _ClientConfig.ForceDirectDatabaseAccess) Then
|
||
Dim oTable As String = pOptions.CacheQueryTable
|
||
Dim oFilter As String = pOptions.CacheQueryFilter
|
||
|
||
' ===== LOKALER OVERRIDE-CACHE – vorrangig prüfen =====
|
||
Dim oCacheKey As String = GetOverrideCacheKey(oTable, oFilter)
|
||
Dim oOverride As DataTable = Nothing
|
||
If _localOverrideCache.TryGetValue(oCacheKey, oOverride) Then
|
||
_Logger.Debug("✔ GetDatatable: Lokaler Override-Cache trifft für [{0}] Filter [{1}]", oTable, oFilter)
|
||
Return oOverride
|
||
End If
|
||
|
||
_Logger.Debug("GetDatatable: URI-Cache-Query erkannt – Tabelle [{0}] Filter [{1}]", oTable, oFilter)
|
||
|
||
If _Client IsNot Nothing AndAlso IsTableCached(oTable) Then
|
||
Try
|
||
Dim oResult As TableResult = _Client.GetDatatableByName(oTable, oFilter, pOptions.SortByColumn)
|
||
If oResult IsNot Nothing AndAlso oResult.OK Then
|
||
_Logger.Debug("✔ GetDatatable: URI-Cache-Query [{0}] – {1} Zeilen", oTable, oResult.Table?.Rows.Count)
|
||
Return oResult.Table
|
||
End If
|
||
Catch ex As Exception
|
||
_Logger.Error(ex)
|
||
End Try
|
||
_Logger.Warn("⚠ GetDatatable: URI-Cache-Query [{0}] fehlgeschlagen – kein SQL-Fallback verfügbar", oTable)
|
||
Return Nothing
|
||
End If
|
||
|
||
_Logger.Warn("⚠ GetDatatable: URI-Cache-Query – Tabelle [{0}] nicht im AppServer-Cache", oTable)
|
||
Return Nothing
|
||
End If
|
||
Return GetDatatable(pDataTableName, pOptions.FallbackSQL, pOptions.FallbackType, pOptions.FilterExpression, pOptions.SortByColumn, pOptions.ForceFallback, pOptions.ConnectionId)
|
||
End Function
|
||
|
||
Public Function GetDatatable(pSQL As String, pConnectionId As Integer) As DataTable
|
||
' ===== URI-CACHE-SYNTAX: "TABELLENNAME?FILTERAUSDRUCK" =====
|
||
Dim oOptions As New GetDatatableOptions(pSQL, Constants.DatabaseType.ECM) With {.ConnectionId = pConnectionId}
|
||
If oOptions.IsCacheQuery Then
|
||
Return GetDatatable(oOptions.CacheQueryTable, oOptions)
|
||
End If
|
||
Return GetDatatable("FORCE_FALLBACK", pSQL, Constants.DatabaseType.ECM, pSortByColumn:=Nothing, pForceFallback:=False, pConnectionId:=pConnectionId)
|
||
|
||
End Function
|
||
|
||
''' <summary>
|
||
''' Returns a datatable directly from the database – or from AppServer cache if URI-Cache-Syntax is detected.
|
||
''' </summary>
|
||
''' <param name="pOptions">Options object</param>
|
||
Public Function GetDatatable(pOptions As GetDatatableOptions) As DataTable
|
||
' ===== URI-CACHE-SYNTAX: "TABELLENNAME?FILTERAUSDRUCK" =====
|
||
If pOptions.IsCacheQuery Then
|
||
Return GetDatatable(pOptions.CacheQueryTable, pOptions)
|
||
End If
|
||
Dim oForceFallback = True
|
||
Return GetDatatable("FORCE_FALLBACK", pOptions.FallbackSQL, pOptions.FallbackType, pOptions.FilterExpression, pOptions.SortByColumn, oForceFallback, pOptions.ConnectionId)
|
||
End Function
|
||
|
||
''' <summary>
|
||
''' Returns a Datatable by trying to fetch a cached version from the service, then querying the database through the service and querying the database directly as fallback.
|
||
''' </summary>
|
||
''' <param name="pDataTableName">Name of the Cached Datatable</param>
|
||
''' <param name="pFallbackSQL">SQL Command to execute as fallback</param>
|
||
''' <param name="pFallbackType">Named Database to use for the fallback SQL Command</param>
|
||
''' <param name="pFilterExpression">Filter expression for the cached Datatable</param>
|
||
''' <param name="pSortByColumn">Columns to sort the cached Datatable by</param>
|
||
''' <param name="pForceFallback">Force the fallback, skipping the service completely</param>
|
||
''' <param name="pConnectionId">Connection Id to use, references TBDD_CONNECTION</param>
|
||
Public Function GetDatatable(pDataTableName As String, pFallbackSQL As String, pFallbackType As Constants.DatabaseType, Optional pFilterExpression As String = "", Optional pSortByColumn As String = "", Optional pForceFallback As Boolean = False, Optional pConnectionId As Integer = 0) As DataTable
|
||
Try
|
||
Dim oTableResult As TableResult = Nothing
|
||
_Logger.Debug("GetDatatable: Fetching datatable [{0}] , Filter Expression [{1}], Sort By Column [{2}], Force Fallback [{3}]",
|
||
pDataTableName, pFilterExpression, pSortByColumn, pForceFallback)
|
||
' If there is no client, we assume there is no service (configured)
|
||
If _Client Is Nothing Then
|
||
_Logger.Debug("GetDatatable: Client is empty, falling back to direct database access.")
|
||
Return GetDatatableFromDatabase(pFallbackSQL, pFallbackType, pConnectionId)
|
||
End If
|
||
|
||
' If ForceFallback flag is set, we go to database immediately
|
||
If pForceFallback Or _ClientConfig.ForceDirectDatabaseAccess Then
|
||
_Logger.Debug("GetDatatable: ForceFallback is True, falling back to direct database access.")
|
||
Return GetDatatableFromDatabase(pFallbackSQL, pFallbackType, pConnectionId)
|
||
End If
|
||
|
||
' If the table is not cached, we try going through the service
|
||
If Not IsTableCached(pDataTableName) Then
|
||
_Logger.Debug("GetDatatable: Datatable [{0}] is not chached, fetching data from service.", pDataTableName)
|
||
Return GetDatatableFromService(pFallbackSQL, pFallbackType, pConnectionId)
|
||
End If
|
||
|
||
' If there is a proper ConnectionId, we try going through the service
|
||
If pConnectionId > 0 Then
|
||
_Logger.Debug("GetDatatable: ConnectionId is set, fetching data from service.")
|
||
Return GetDatatableFromService(pFallbackSQL, pFallbackType, pConnectionId)
|
||
End If
|
||
|
||
Try
|
||
_Logger.Debug("GetDatatable: Datatable is chached, fetching data from cache.")
|
||
oTableResult = _Client.GetDatatableByName(pDataTableName, pFilterExpression, pSortByColumn)
|
||
Catch ex As Exception
|
||
_Logger.Error(ex)
|
||
oTableResult = Nothing
|
||
End Try
|
||
|
||
If oTableResult Is Nothing OrElse oTableResult.OK = False Then
|
||
_Logger.Warn("⚠ GetDatatable: Datatable [{0}] could not be fetched from AppServer Cache. Falling back to direct Database Access.", pDataTableName)
|
||
Return GetDatatableFromDatabase(pFallbackSQL, pFallbackType, pConnectionId)
|
||
Else
|
||
_Logger.Debug("✔ GetDatatable: Datatable [{0}] fetched from AppServer Cache.", pDataTableName)
|
||
Return oTableResult.Table
|
||
End If
|
||
|
||
Catch ex As Exception
|
||
_Logger.Error(ex)
|
||
Return Nothing
|
||
|
||
End Try
|
||
End Function
|
||
|
||
''' <summary>
|
||
''' Returns a Scalar Value by querying the database through the service and querying the database directly as fallback.
|
||
''' URI-Cache-Syntax wird unterstützt: "TABELLENNAME?FILTERAUSDRUCK" – liefert Wert der ersten Spalte der ersten Zeile aus dem AppServer-Cache.
|
||
''' </summary>
|
||
''' <param name="pSQL">SQL Command to execute as fallback – oder URI-Cache-Syntax "TABELLENNAME?FILTERAUSDRUCK"</param>
|
||
''' <param name="pDatabaseType">Named Database to use for the fallback SQL Command</param>
|
||
''' <param name="pForceFallback">Force the fallback, skipping the service completely</param>
|
||
''' <param name="pConnectionId">Connection Id to use, references TBDD_CONNECTION</param>
|
||
Public Function GetScalarValue(pSQL As String, pDatabaseType As Constants.DatabaseType, Optional pForceFallback As Boolean = False, Optional pConnectionId As Integer = 0) As Object
|
||
Try
|
||
' ===== URI-CACHE-SYNTAX: "TABELLENNAME?FILTERAUSDRUCK" =====
|
||
' ForceFallback übersteuert auch die URI-Cache-Syntax
|
||
Dim oOptions As New GetDatatableOptions(pSQL, pDatabaseType)
|
||
If oOptions.IsCacheQuery AndAlso Not pForceFallback AndAlso
|
||
Not (_ClientConfig IsNot Nothing AndAlso _ClientConfig.ForceDirectDatabaseAccess) Then
|
||
Dim oTable As String = oOptions.CacheQueryTable
|
||
Dim oFilter As String = oOptions.CacheQueryFilter
|
||
_Logger.Debug("GetScalarValue: URI-Cache-Query erkannt – Tabelle [{0}] Filter [{1}]", oTable, oFilter)
|
||
|
||
If _Client IsNot Nothing AndAlso IsTableCached(oTable) Then
|
||
Try
|
||
Dim oResult As TableResult = _Client.GetDatatableByName(oTable, oFilter, "")
|
||
If oResult IsNot Nothing AndAlso oResult.OK AndAlso
|
||
oResult.Table IsNot Nothing AndAlso oResult.Table.Rows.Count > 0 Then
|
||
Dim oScalar As Object = oResult.Table.Rows(0)(0)
|
||
_Logger.Debug("✔ GetScalarValue: URI-Cache-Query [{0}] – Scalar [{1}]", oTable, oScalar)
|
||
Return oScalar
|
||
End If
|
||
Catch ex As Exception
|
||
_Logger.Error(ex)
|
||
End Try
|
||
_Logger.Warn("⚠ GetScalarValue: URI-Cache-Query [{0}] fehlgeschlagen – kein SQL-Fallback verfügbar", oTable)
|
||
Return Nothing
|
||
End If
|
||
|
||
_Logger.Warn("⚠ GetScalarValue: URI-Cache-Query – Tabelle [{0}] nicht im AppServer-Cache", oTable)
|
||
Return Nothing
|
||
End If
|
||
|
||
If _Client Is Nothing Then
|
||
Return GetScalarValueFromDatabase(pSQL, pDatabaseType, pConnectionId)
|
||
End If
|
||
If pForceFallback Or _ClientConfig.ForceDirectDatabaseAccess Then
|
||
Return GetScalarValueFromDatabase(pSQL, pDatabaseType, pConnectionId)
|
||
End If
|
||
Return GetScalarValueFromService(pSQL, pDatabaseType, pConnectionId)
|
||
|
||
Catch ex As Exception
|
||
_Logger.Error(ex)
|
||
Return Nothing
|
||
End Try
|
||
End Function
|
||
|
||
''' <summary>
|
||
''' Executes a non-query SQL command through the service and falls back to direct database access.
|
||
''' </summary>
|
||
''' <param name="pSQL">SQL Command to execute</param>
|
||
''' <param name="pDatabaseType">Named Database to use for the SQL Command</param>
|
||
''' <param name="pForceFallback">Force the fallback, skipping the service completely</param>
|
||
''' <param name="pConnectionId">Connection Id to use, references TBDD_CONNECTION</param>
|
||
Public Function ExecuteNonQuery(pSQL As String, pDatabaseType As Constants.DatabaseType, Optional pForceFallback As Boolean = False, Optional pConnectionId As Integer = 0) As Boolean
|
||
Try
|
||
' If there is no client, we assume there is no service (configured)
|
||
If _Client Is Nothing Then
|
||
_Logger.Debug("ExecuteNonQuery - Client is empty, falling back to direct database access.")
|
||
Return ExecuteNonQueryFromDatabase(pSQL, pDatabaseType, pConnectionId)
|
||
End If
|
||
|
||
' If ForceFallback flag is set, we go to database immediately
|
||
If pForceFallback Or _ClientConfig.ForceDirectDatabaseAccess Then
|
||
_Logger.Debug("ExecuteNonQuery - ForceFallback is set, falling back to direct database access.")
|
||
Return ExecuteNonQueryFromDatabase(pSQL, pDatabaseType, pConnectionId)
|
||
End If
|
||
|
||
Return ExecuteNonQueryFromService(pSQL, pDatabaseType, pConnectionId)
|
||
|
||
Catch ex As Exception
|
||
_Logger.Error(ex)
|
||
Return False
|
||
|
||
End Try
|
||
End Function
|
||
''' <summary>
|
||
''' Führt ein UPDATE oder DELETE über URI-Write-Syntax aus.
|
||
''' Der geänderte Snapshot wird lokal gecacht und bei GetDatatable/GetScalarValue
|
||
''' vorrangig ausgewertet – bis der Server-Refresh den Cache neu befüllt.
|
||
''' UPDATE: "TBDD_USER?USER_ID=4524!STATUS=1"
|
||
''' DELETE: "TBDD_USER?USER_ID=4524!DELETE"
|
||
''' </summary>
|
||
Public Function ExecuteAppServiceWrite(pOptions As GetDatatableOptions) As Boolean
|
||
If Not pOptions.IsCacheWriteQuery Then
|
||
_Logger.Warn("⚠ ExecuteAppServiceWrite: Keine gültige URI-Write-Syntax – [{0}] – Abbruch.", pOptions.FallbackSQL)
|
||
Return False
|
||
End If
|
||
|
||
If _Client Is Nothing OrElse Not IsTableCached(pOptions.CacheWriteTable) Then
|
||
_Logger.Warn("⚠ ExecuteAppServiceWrite: Tabelle [{0}] nicht im AppServer-Cache – Abbruch.", pOptions.CacheWriteTable)
|
||
Return False
|
||
End If
|
||
|
||
_Logger.Debug("ExecuteAppServiceWrite: Tabelle [{0}] | Filter [{1}] | Aktion [{2}] | IsDelete [{3}]",
|
||
pOptions.CacheWriteTable, pOptions.CacheWriteFilter, pOptions.CacheWriteAction, pOptions.CacheWriteIsDelete)
|
||
Try
|
||
' Aktuellen Snapshot aus Cache laden
|
||
Dim oResult As TableResult = _Client.GetDatatableByName(pOptions.CacheWriteTable, pOptions.CacheWriteFilter, "")
|
||
If oResult Is Nothing OrElse Not oResult.OK OrElse oResult.Table Is Nothing Then
|
||
_Logger.Warn("⚠ ExecuteAppServiceWrite: Cache-Abfrage für [{0}] fehlgeschlagen.", pOptions.CacheWriteTable)
|
||
Return False
|
||
End If
|
||
|
||
Dim oMatchingRows As DataRow() = oResult.Table.Select(pOptions.CacheWriteFilter)
|
||
If oMatchingRows.Length = 0 Then
|
||
_Logger.Debug("ExecuteAppServiceWrite: Keine Zeilen für Filter [{0}] gefunden.", pOptions.CacheWriteFilter)
|
||
Return True
|
||
End If
|
||
|
||
If pOptions.CacheWriteIsDelete Then
|
||
For Each oRow As DataRow In oMatchingRows
|
||
oRow.Delete()
|
||
Next
|
||
oResult.Table.AcceptChanges()
|
||
_Logger.Debug("✔ ExecuteAppServiceWrite: DELETE – [{0}] Zeilen entfernt.", oMatchingRows.Length)
|
||
Else
|
||
For Each oPair As String In pOptions.CacheWriteAction.Split(","c)
|
||
Dim oParts = oPair.Split("="c)
|
||
If oParts.Length <> 2 Then Continue For
|
||
Dim oCol As String = oParts(0).Trim()
|
||
Dim oVal As String = oParts(1).Trim().Trim("'"c)
|
||
For Each oRow As DataRow In oMatchingRows
|
||
If oResult.Table.Columns.Contains(oCol) Then
|
||
oRow(oCol) = oVal
|
||
End If
|
||
Next
|
||
Next
|
||
oResult.Table.AcceptChanges()
|
||
_Logger.Debug("✔ ExecuteAppServiceWrite: UPDATE – [{0}] Zeilen aktualisiert.", oMatchingRows.Length)
|
||
End If
|
||
|
||
' ===== OVERRIDE-CACHE BEFÜLLEN =====
|
||
Dim oCacheKey As String = GetOverrideCacheKey(pOptions.CacheWriteTable, pOptions.CacheWriteFilter)
|
||
_localOverrideCache(oCacheKey) = oResult.Table
|
||
_Logger.Debug("✔ ExecuteAppServiceWrite: Override-Cache gesetzt für [{0}]", oCacheKey)
|
||
|
||
Return True
|
||
|
||
Catch ex As Exception
|
||
_Logger.Error(ex)
|
||
Return False
|
||
End Try
|
||
End Function
|
||
Private Function IsTableCached(pName As String) As Boolean
|
||
_Logger.Debug("IsTableCached: Checking if table [{0}] is cached.", pName)
|
||
If _Client Is Nothing Then
|
||
_Logger.Warn("⚠ IsTableCached: Client is nothing, cannot check if table is cached.")
|
||
Return False
|
||
End If
|
||
|
||
Return _Client.CachedTables.Contains(pName.ToUpper)
|
||
End Function
|
||
|
||
Private Function GetDatatableFromService(pSQLCommand As String, DatabaseType As Constants.DatabaseType, pConnectionId As Integer) As DataTable
|
||
Try
|
||
_Logger.Debug("GetDatatableFromService: Fetching data from service [{0}] with Connection Id [{1}] and Command [{2}]", DatabaseType.ToString, pConnectionId, pSQLCommand)
|
||
Dim oResult As GetDatatableResponse = Nothing
|
||
|
||
Select Case DatabaseType
|
||
Case Constants.DatabaseType.ECM
|
||
oResult = _Client.GetDatatableFromECM(pSQLCommand)
|
||
Case Constants.DatabaseType.IDB
|
||
oResult = _Client.GetDatatableFromIDB(pSQLCommand)
|
||
Case Else
|
||
Return GetDatatableFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
|
||
End Select
|
||
|
||
If oResult Is Nothing OrElse oResult.OK = False Then
|
||
_Logger.Warn("⚠ GetDatatableFromService: Service returned invalid response [{0}]. Falling back to direct database access.", If(oResult Is Nothing, "null", oResult.ErrorMessage))
|
||
Return GetDatatableFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
|
||
End If
|
||
|
||
Return oResult.Table
|
||
|
||
Catch ex As InvalidOperationException
|
||
_Logger.Warn("⚠ GetDatatableFromService skipped: [{0}] Falling back to direct database access.", ex.Message)
|
||
Return GetDatatableFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
|
||
|
||
Catch ex As Exception
|
||
_Logger.Warn("⚠ GetDatatableFromService failed. Falling back to direct database access.")
|
||
_Logger.Error(ex)
|
||
Return GetDatatableFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
|
||
|
||
End Try
|
||
End Function
|
||
|
||
Private Async Function GetDatatableFromServiceAsync(pSQLCommand As String, DatabaseType As Constants.DatabaseType, pConnectionId As Integer) As Task(Of DataTable)
|
||
_Logger.Debug("GetDatatableFromServiceAsync called | SQL=[{0}] | DB=[{1}] | ConnId=[{2}]",
|
||
pSQLCommand, DatabaseType.ToString, pConnectionId)
|
||
|
||
If _Client Is Nothing OrElse _ClientConfig?.ForceDirectDatabaseAccess Then
|
||
' ← Kein Task.Run: direkt synchron, Thread ist ohnehin frei
|
||
Return GetDatatableFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
|
||
End If
|
||
|
||
Try
|
||
Dim oResult As GetDatatableResponse = Nothing
|
||
|
||
Select Case DatabaseType
|
||
Case Constants.DatabaseType.ECM
|
||
oResult = Await _Client.GetDatatableFromECMAsync(pSQLCommand)
|
||
Case Constants.DatabaseType.IDB
|
||
oResult = Await _Client.GetDatatableFromIDBAsync(pSQLCommand)
|
||
Case Else
|
||
oResult = Await _Client.GetDatatableFromConnectionAsync(pSQLCommand, pConnectionId)
|
||
End Select
|
||
|
||
If oResult Is Nothing OrElse oResult.OK = False Then
|
||
_Logger.Warn("⚠ GetDatatableFromServiceAsync failed | 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
|
||
_Logger.Debug("GetDatatableFromDatabase: Fetching data from database [{0}] with Connection Id [{1}] and Command [{2}]", pDatabaseType.ToString, pConnectionId, pSQLCommand)
|
||
|
||
Select Case pDatabaseType
|
||
Case Constants.DatabaseType.IDB
|
||
Return _DatabaseIDB.GetDatatable(pSQLCommand)
|
||
|
||
Case Else
|
||
If pConnectionId > 0 Then
|
||
_Logger.Debug("Retrieving Connection String from Connection Id [{0}]", pConnectionId)
|
||
Dim oConnectionString = GetCachedConnectionString(pConnectionId) ' ← NEU: Cache statt direkter DB-Abfrage
|
||
Return _DatabaseECM.GetDatatableWithConnection(pSQLCommand, oConnectionString)
|
||
Else
|
||
Return _DatabaseECM.GetDatatable(pSQLCommand)
|
||
End If
|
||
|
||
End Select
|
||
|
||
Catch ex As Exception
|
||
_Logger.Error(ex)
|
||
Return Nothing
|
||
|
||
End Try
|
||
End Function
|
||
Private Async Function GetScalarValueFromServiceAsync(pSQLCommand As String, DatabaseType As Constants.DatabaseType, pConnectionId As Integer) As Task(Of Object)
|
||
_Logger.Debug("GetScalarValueFromServiceAsync called | SQL=[{0}] | DB=[{1}] | ConnId=[{2}]",
|
||
pSQLCommand, DatabaseType.ToString, pConnectionId)
|
||
|
||
If _Client Is Nothing OrElse _ClientConfig?.ForceDirectDatabaseAccess Then
|
||
Return GetScalarValueFromDatabase(pSQLCommand, DatabaseType, pConnectionId) ' ← kein Task.Run
|
||
End If
|
||
|
||
Try
|
||
Dim oResult As GetScalarValueResponse = Nothing
|
||
|
||
Select Case DatabaseType
|
||
Case Constants.DatabaseType.ECM
|
||
oResult = Await _Client.GetScalarValueFromECMAsync(pSQLCommand)
|
||
Case Constants.DatabaseType.IDB
|
||
oResult = Await _Client.GetScalarValueFromIDBAsync(pSQLCommand)
|
||
Case Else
|
||
oResult = Await _Client.GetScalarValueFromConnectionAsync(pSQLCommand, pConnectionId)
|
||
End Select
|
||
|
||
If oResult Is Nothing OrElse oResult.OK = False Then
|
||
_Logger.Warn("⚠ GetScalarValueFromServiceAsync failed | 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
|
||
Dim oResult As GetScalarValueResponse = Nothing
|
||
_Logger.Debug("GetScalarValueFromService [{0}] with Connection Id [{1}] and Command [{2}]", DatabaseType.ToString, pConnectionId, pSQLCommand)
|
||
Select Case DatabaseType
|
||
Case Constants.DatabaseType.ECM
|
||
oResult = _Client.GetScalarValueFromECM(pSQLCommand)
|
||
Case Constants.DatabaseType.IDB
|
||
oResult = _Client.GetScalarValueFromIDB(pSQLCommand)
|
||
Case Else
|
||
Return GetScalarValueFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
|
||
End Select
|
||
|
||
If oResult Is Nothing OrElse oResult.OK = False Then
|
||
_Logger.Warn("⚠ GetScalarValueFromService: Service returned invalid response [{0}]. Falling back to direct database access.", If(oResult Is Nothing, "null", oResult.ErrorMessage))
|
||
Return GetScalarValueFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
|
||
End If
|
||
|
||
Return oResult.Scalar
|
||
|
||
Catch ex As InvalidOperationException
|
||
_Logger.Warn("⚠ GetScalarValueFromService skipped: [{0}] Falling back to direct database access.", ex.Message)
|
||
Return GetScalarValueFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
|
||
|
||
Catch ex As Exception
|
||
_Logger.Warn("⚠ GetScalarValueFromService failed. Falling back to direct database access.")
|
||
_Logger.Error(ex)
|
||
Return GetScalarValueFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
|
||
|
||
End Try
|
||
End Function
|
||
|
||
|
||
Private Function GetScalarValueFromDatabase(pSQLCommand As String, DatabaseType As Constants.DatabaseType, pConnectionId As Integer) As Object
|
||
Try
|
||
_Logger.Debug("DBwFF GetScalarValueFromDatabase [{0}] with Connection Id [{1}] and Command [{2}]", DatabaseType.ToString, pConnectionId, pSQLCommand)
|
||
Select Case DatabaseType
|
||
Case Constants.DatabaseType.IDB
|
||
Return _DatabaseIDB.GetScalarValue(pSQLCommand)
|
||
|
||
Case Else
|
||
If pConnectionId > 0 Then
|
||
Dim oConnectionString = GetCachedConnectionString(pConnectionId) ' ← NEU: Cache statt direkter DB-Abfrage
|
||
Return _DatabaseECM.GetScalarValueWithConnection(pSQLCommand, oConnectionString)
|
||
Else
|
||
Return _DatabaseECM.GetScalarValue(pSQLCommand)
|
||
End If
|
||
|
||
End Select
|
||
|
||
Catch ex As Exception
|
||
_Logger.Error(ex)
|
||
Return Nothing
|
||
|
||
End Try
|
||
End Function
|
||
Private Async Function ExecuteNonQueryFromServiceAsync(pSQLCommand As String, DatabaseType As Constants.DatabaseType, pConnectionId As Integer) As Task(Of Boolean)
|
||
_Logger.Debug("ExecuteNonQueryFromServiceAsync called | SQL=[{0}] | DB=[{1}] | ConnId=[{2}]",
|
||
pSQLCommand, DatabaseType.ToString, pConnectionId)
|
||
|
||
If _Client Is Nothing OrElse _ClientConfig?.ForceDirectDatabaseAccess Then
|
||
Return ExecuteNonQueryFromDatabase(pSQLCommand, DatabaseType, pConnectionId) ' ← kein Task.Run
|
||
End If
|
||
|
||
Try
|
||
Dim oResult As ExecuteNonQueryResponse = Nothing
|
||
|
||
Select Case DatabaseType
|
||
Case Constants.DatabaseType.ECM
|
||
oResult = Await _Client.ExecuteNonQueryFromECMAsync(pSQLCommand)
|
||
Case Constants.DatabaseType.IDB
|
||
oResult = Await _Client.ExecuteNonQueryFromIDBAsync(pSQLCommand)
|
||
Case Else
|
||
oResult = Await _Client.ExecuteNonQueryFromConnectionAsync(pSQLCommand, pConnectionId)
|
||
End Select
|
||
|
||
If oResult Is Nothing OrElse oResult.OK = False Then
|
||
_Logger.Warn("⚠ ExecuteNonQueryFromServiceAsync failed | 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
|
||
Dim oResult As ExecuteNonQueryResponse = Nothing
|
||
_Logger.Debug("ExecuteNonQueryFromService [{0}] with Connection Id [{1}] and Command [{2}]", DatabaseType.ToString, pConnectionId, pSQLCommand)
|
||
Select Case DatabaseType
|
||
Case Constants.DatabaseType.ECM
|
||
oResult = _Client.ExecuteNonQueryFromECM(pSQLCommand)
|
||
Case Constants.DatabaseType.IDB
|
||
oResult = _Client.ExecuteNonQueryFromIDB(pSQLCommand)
|
||
Case Else
|
||
Return ExecuteNonQueryFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
|
||
End Select
|
||
|
||
If oResult Is Nothing OrElse oResult.OK = False Then
|
||
_Logger.Warn("⚠ ExecuteNonQueryFromService: Service returned invalid response [{0}]. Falling back to direct database access.", If(oResult Is Nothing, "null", oResult.ErrorMessage))
|
||
Return ExecuteNonQueryFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
|
||
End If
|
||
|
||
Return oResult.Result
|
||
|
||
Catch ex As InvalidOperationException
|
||
_Logger.Warn("⚠ ExecuteNonQueryFromService skipped: [{0}] Falling back to direct database access.", ex.Message)
|
||
Return ExecuteNonQueryFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
|
||
|
||
Catch ex As Exception
|
||
_Logger.Warn("⚠ ExecuteNonQueryFromService failed. Falling back to direct database access.")
|
||
_Logger.Error(ex)
|
||
Return ExecuteNonQueryFromDatabase(pSQLCommand, DatabaseType, pConnectionId)
|
||
|
||
End Try
|
||
End Function
|
||
|
||
Private Function ExecuteNonQueryFromDatabase(pSQLCommand As String, DatabaseType As Constants.DatabaseType, pConnectionId As Integer) As Boolean
|
||
Try
|
||
_Logger.Debug("ExecuteNonQueryFromDatabase [{0}] with Connection Id [{1}] and Command [{2}]", DatabaseType.ToString, pConnectionId, pSQLCommand)
|
||
|
||
Select Case DatabaseType
|
||
Case Constants.DatabaseType.IDB
|
||
Return _DatabaseIDB.ExecuteNonQuery(pSQLCommand)
|
||
|
||
Case Else
|
||
If pConnectionId > 0 Then
|
||
Dim oConnectionString = GetCachedConnectionString(pConnectionId) ' ← NEU: Cache statt direkter DB-Abfrage
|
||
Return _DatabaseECM.ExecuteNonQueryWithConnection(pSQLCommand, oConnectionString)
|
||
Else
|
||
Return _DatabaseECM.ExecuteNonQuery(pSQLCommand)
|
||
End If
|
||
|
||
End Select
|
||
|
||
Catch ex As Exception
|
||
_Logger.Error(ex)
|
||
Return False
|
||
|
||
End Try
|
||
End Function
|
||
End Class
|
||
|