From fb6d27e53ba49201472755449b4e2355c11a582c Mon Sep 17 00:00:00 2001 From: Developer01 Date: Thu, 22 Oct 2026 18:06:18 +0200 Subject: [PATCH] =?UTF-8?q?Neue=20.resx-Dateien=20und=20Projektkonfigurati?= =?UTF-8?q?onen=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- Database/Adapters/MSSQLServer.vb | 2 +- Database/My Project/AssemblyInfo.vb | 4 +- EDMIAPI/DatabaseWithFallback.vb | 357 ++++++++++++++++++++++++++-- EDMIAPI/My Project/AssemblyInfo.vb | 4 +- Modules.sln | 21 -- Patterns/Patterns.vbproj | 16 +- Patterns/Patterns.vbproj.bak | 164 +++++++++++++ 7 files changed, 516 insertions(+), 52 deletions(-) create mode 100644 Patterns/Patterns.vbproj.bak diff --git a/Database/Adapters/MSSQLServer.vb b/Database/Adapters/MSSQLServer.vb index 922c0bff..f0d99b8a 100644 --- a/Database/Adapters/MSSQLServer.vb +++ b/Database/Adapters/MSSQLServer.vb @@ -178,7 +178,7 @@ Public Class MSSQLServer End If Try - Dim oTable As DataTable = GetDatatable($"SELECT * FROM TBDD_CONNECTION WHERE GUID = {pConnectionId}") + Dim oTable As DataTable = GetDatatable($"SELECT * FROM TBDD_CONNECTION WITH (NOLOCK) WHERE GUID = {pConnectionId}") If oTable.Rows.Count = 1 Then Dim oRow As DataRow = oTable.Rows(0) Dim oProvider = oRow.Item("SQL_PROVIDER").ToString.ToUpper diff --git a/Database/My Project/AssemblyInfo.vb b/Database/My Project/AssemblyInfo.vb index 42d5e5f1..092c81c7 100644 --- a/Database/My Project/AssemblyInfo.vb +++ b/Database/My Project/AssemblyInfo.vb @@ -13,7 +13,7 @@ Imports System.Runtime.InteropServices - + @@ -31,5 +31,5 @@ Imports System.Runtime.InteropServices ' übernehmen, indem Sie "*" eingeben: ' - + diff --git a/EDMIAPI/DatabaseWithFallback.vb b/EDMIAPI/DatabaseWithFallback.vb index a1f8d387..b2116d9f 100644 --- a/EDMIAPI/DatabaseWithFallback.vb +++ b/EDMIAPI/DatabaseWithFallback.vb @@ -1,4 +1,5 @@ -Imports System.Data.SqlClient +Imports System.Collections.Concurrent +Imports System.Data.SqlClient Imports DigitalData.Modules.Database Imports DigitalData.Modules.EDMI.API Imports DigitalData.Modules.EDMI.API.Constants @@ -11,7 +12,70 @@ Public Class DatabaseWithFallback Private ReadOnly _ClientConfig As GlobalStateClientConfiguration Private ReadOnly _DatabaseECM As MSSQLServer Private _DatabaseIDB As MSSQLServer + ''' + ''' 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. + ''' + Private ReadOnly _localOverrideCache As New ConcurrentDictionary(Of String, DataTable)() + ''' Erstellt den Cache-Key aus Tabellenname und Filter. + Private Shared Function GetOverrideCacheKey(pTable As String, pFilter As String) As String + Return $"{pTable.ToUpper}|{pFilter}" + End Function + ''' Leert den lokalen Override-Cache (z.B. nach manuellem Refresh). + Public Sub InvalidateLocalOverrideCache() + _localOverrideCache.Clear() + _Logger.Debug("InvalidateLocalOverrideCache: Lokaler Override-Cache geleert.") + End Sub + + ''' Leert den Override-Cache für eine bestimmte Tabelle. + 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 + ''' + ''' Thread-sicherer Cache für Connection Strings, indexiert nach Connection-ID. + ''' Verhindert wiederholte DB-Roundtrips für SELECT * FROM TBDD_CONNECTION. + ''' + Private ReadOnly _connectionStringCache As New ConcurrentDictionary(Of Integer, String) + + ''' + ''' 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. + ''' + 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 + ''' + ''' Leert den Connection-String-Cache, z.B. nach Konfigurationsänderungen. + ''' + Public Sub InvalidateConnectionCache() + _connectionStringCache.Clear() + _Logger.Debug("InvalidateConnectionCache: Connection-String-Cache geleert") + End Sub + ''' + ''' Leert den Cache für eine bestimmte Connection-ID. + ''' + 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 ''' ''' Options for GetDatatable ''' @@ -35,7 +99,105 @@ Public Class DatabaseWithFallback ''' Connection Id to use, references TBDD_CONNECTION ''' Public Property ConnectionId As Integer = 0 + ''' + ''' Erkennt URI-Cache-Syntax: "TABELLENNAME?FILTERAUSDRUCK" + ''' Beispiel: "TBDD_USER?USER_ID=4524" + ''' + 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 + ''' "TBDD_USER?USER_ID=4524" → "TBDD_USER" + 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 + ''' "TBDD_USER?USER_ID=4524" → "USER_ID=4524" + 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 + ''' "TBDD_USER?USER_ID=4524!STATUS=1" → "TBDD_USER" + 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 + + ''' "TBDD_USER?USER_ID=4524!STATUS=1" → "USER_ID=4524" + 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 + + ''' "TBDD_USER?USER_ID=4524!STATUS=1" → "STATUS=1" | "DELETE" + 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 + + ''' True wenn die Aktion ein DELETE ist + Public ReadOnly Property CacheWriteIsDelete As Boolean + Get + Return IsCacheWriteQuery AndAlso + CacheWriteAction.Equals("DELETE", StringComparison.OrdinalIgnoreCase) + End Get + End Property + + ''' + ''' 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" + ''' + 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 ''' ''' Creates a new options object for GetDatatable options ''' @@ -45,8 +207,19 @@ Public Class DatabaseWithFallback FallbackSQL = pFallbackSQL FallbackType = pFallbackType End Sub + ''' + ''' 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" + ''' + ''' URI-Syntax string + 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 @@ -73,7 +246,9 @@ Public Class DatabaseWithFallback Return _DatabaseECM.GetConnectionStringForId(pConnectionId) End Function + #Region "GetDatatable" + ''' URI-Cache-Syntax wird über GetDatatable(pOptions) ausgewertet. Public Function GetDatatableECM(pSQL As String) As DataTable Return GetDatatable(New GetDatatableOptions(pSQL, Constants.DatabaseType.ECM)) End Function @@ -82,6 +257,7 @@ Public Class DatabaseWithFallback Return Await GetDatatableFromServiceAsync(pSQL, Constants.DatabaseType.ECM, 0) End Function + ''' URI-Cache-Syntax wird über GetDatatable(pOptions) ausgewertet. Public Function GetDatatableIDB(pSQL As String) As DataTable Return GetDatatable(New GetDatatableOptions(pSQL, Constants.DatabaseType.IDB)) End Function @@ -91,7 +267,12 @@ Public Class DatabaseWithFallback End Function Public Function GetDatatableWithConnection(pSQL As String, pConnectionId As Integer) As DataTable - Return GetDatatable(New GetDatatableOptions(pSQL, Constants.DatabaseType.None) With {.ConnectionId = pConnectionId}) + ' ===== 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) @@ -126,6 +307,9 @@ Public Class DatabaseWithFallback #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 @@ -158,18 +342,61 @@ Public Class DatabaseWithFallback ''' Name of the Cached Datatable ''' Options object 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 ''' - ''' Returns a datatable directly from the database. + ''' Returns a datatable directly from the database – or from AppServer cache if URI-Cache-Syntax is detected. ''' ''' Options object 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 @@ -186,7 +413,6 @@ Public Class DatabaseWithFallback ''' Connection Id to use, references TBDD_CONNECTION 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 oResult As DataTable = Nothing Dim oTableResult As TableResult = Nothing _Logger.Debug("GetDatatable: Fetching datatable [{0}] , Filter Expression [{1}], Sort By Column [{2}], Force Fallback [{3}]", pDataTableName, pFilterExpression, pSortByColumn, pForceFallback) @@ -239,44 +465,66 @@ Public Class DatabaseWithFallback ''' ''' 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. ''' - ''' SQL Command to execute as fallback + ''' SQL Command to execute as fallback – oder URI-Cache-Syntax "TABELLENNAME?FILTERAUSDRUCK" ''' Named Database to use for the fallback SQL Command ''' Force the fallback, skipping the service completely ''' Connection Id to use, references TBDD_CONNECTION Public Function GetScalarValue(pSQL As String, pDatabaseType As Constants.DatabaseType, Optional pForceFallback As Boolean = False, Optional pConnectionId As Integer = 0) As Object Try - ' If there is no client, we assume there is no service (configured) + ' ===== 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 ForceFallback flag is set, we go to database immediately 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 ''' - ''' Returns a Scalar Value by querying the database through the service and querying the database directly as fallback. + ''' Executes a non-query SQL command through the service and falls back to direct database access. ''' - ''' SQL Command to execute as fallback - ''' Named Database to use for the fallback SQL Command + ''' SQL Command to execute + ''' Named Database to use for the SQL Command ''' Force the fallback, skipping the service completely ''' Connection Id to use, references TBDD_CONNECTION Public Function ExecuteNonQuery(pSQL As String, pDatabaseType As Constants.DatabaseType, Optional pForceFallback As Boolean = False, Optional pConnectionId As Integer = 0) As Boolean Try - Dim oResult As DataTable = Nothing - Dim oTableResult As TableResult = Nothing - ' 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.") @@ -293,11 +541,78 @@ Public Class DatabaseWithFallback Catch ex As Exception _Logger.Error(ex) - Return Nothing + Return False End Try End Function + ''' + ''' 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" + ''' + 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 @@ -391,7 +706,7 @@ Public Class DatabaseWithFallback Case Else If pConnectionId > 0 Then _Logger.Debug("Retrieving Connection String from Connection Id [{0}]", pConnectionId) - Dim oConnectionString = _DatabaseECM.Get_ConnectionStringforID(pConnectionId) + Dim oConnectionString = GetCachedConnectionString(pConnectionId) ' ← NEU: Cache statt direkter DB-Abfrage Return _DatabaseECM.GetDatatableWithConnection(pSQLCommand, oConnectionString) Else Return _DatabaseECM.GetDatatable(pSQLCommand) @@ -484,7 +799,7 @@ Public Class DatabaseWithFallback Case Else If pConnectionId > 0 Then - Dim oConnectionString = _DatabaseECM.Get_ConnectionStringforID(pConnectionId) + Dim oConnectionString = GetCachedConnectionString(pConnectionId) ' ← NEU: Cache statt direkter DB-Abfrage Return _DatabaseECM.GetScalarValueWithConnection(pSQLCommand, oConnectionString) Else Return _DatabaseECM.GetScalarValue(pSQLCommand) @@ -577,7 +892,7 @@ Public Class DatabaseWithFallback Case Else If pConnectionId > 0 Then - Dim oConnectionString = _DatabaseECM.Get_ConnectionStringforID(pConnectionId) + Dim oConnectionString = GetCachedConnectionString(pConnectionId) ' ← NEU: Cache statt direkter DB-Abfrage Return _DatabaseECM.ExecuteNonQueryWithConnection(pSQLCommand, oConnectionString) Else Return _DatabaseECM.ExecuteNonQuery(pSQLCommand) diff --git a/EDMIAPI/My Project/AssemblyInfo.vb b/EDMIAPI/My Project/AssemblyInfo.vb index 2d7aae46..abc336bd 100644 --- a/EDMIAPI/My Project/AssemblyInfo.vb +++ b/EDMIAPI/My Project/AssemblyInfo.vb @@ -13,7 +13,7 @@ Imports System.Runtime.InteropServices - + @@ -31,5 +31,5 @@ Imports System.Runtime.InteropServices ' übernehmen, indem Sie "*" eingeben: ' - + diff --git a/Modules.sln b/Modules.sln index 7a0e35e3..c9635f29 100644 --- a/Modules.sln +++ b/Modules.sln @@ -24,8 +24,6 @@ Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "License", "License\License. EndProject Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Logging", "Logging\Logging.vbproj", "{903B2D7D-3B80-4BE9-8713-7447B704E1B0}" EndProject -Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Logging.Test", "Logging.Test\Logging.Test.vbproj", "{3207D8E7-36E3-4714-9B03-7B5B3D6D351A}" -EndProject Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Patterns", "Patterns\Patterns.vbproj", "{7C3B0C7E-59FE-4E1A-A655-27AE119F9444}" EndProject Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Windows", "Windows\Windows.vbproj", "{5EFAEF9B-90B9-4F05-9F70-F79AD77FFF86}" @@ -49,63 +47,45 @@ Global EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {6EA0C51F-C2B1-4462-8198-3DE0B32B74F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6EA0C51F-C2B1-4462-8198-3DE0B32B74F8}.Debug|Any CPU.Build.0 = Debug|Any CPU {6EA0C51F-C2B1-4462-8198-3DE0B32B74F8}.Release|Any CPU.ActiveCfg = Release|Any CPU {6EA0C51F-C2B1-4462-8198-3DE0B32B74F8}.Release|Any CPU.Build.0 = Release|Any CPU {44982F9B-6116-44E2-85D0-F39650B1EF99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {44982F9B-6116-44E2-85D0-F39650B1EF99}.Debug|Any CPU.Build.0 = Debug|Any CPU {44982F9B-6116-44E2-85D0-F39650B1EF99}.Release|Any CPU.ActiveCfg = Release|Any CPU {44982F9B-6116-44E2-85D0-F39650B1EF99}.Release|Any CPU.Build.0 = Release|Any CPU {EAF0EA75-5FA7-485D-89C7-B2D843B03A96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EAF0EA75-5FA7-485D-89C7-B2D843B03A96}.Debug|Any CPU.Build.0 = Debug|Any CPU {EAF0EA75-5FA7-485D-89C7-B2D843B03A96}.Release|Any CPU.ActiveCfg = Release|Any CPU {EAF0EA75-5FA7-485D-89C7-B2D843B03A96}.Release|Any CPU.Build.0 = Release|Any CPU {25017513-0D97-49D3-98D7-BA76D9B251B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {25017513-0D97-49D3-98D7-BA76D9B251B0}.Debug|Any CPU.Build.0 = Debug|Any CPU {25017513-0D97-49D3-98D7-BA76D9B251B0}.Release|Any CPU.ActiveCfg = Release|Any CPU {25017513-0D97-49D3-98D7-BA76D9B251B0}.Release|Any CPU.Build.0 = Release|Any CPU {8A8F20FC-C46E-41AC-BEE7-218366CFFF99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8A8F20FC-C46E-41AC-BEE7-218366CFFF99}.Debug|Any CPU.Build.0 = Debug|Any CPU {8A8F20FC-C46E-41AC-BEE7-218366CFFF99}.Release|Any CPU.ActiveCfg = Release|Any CPU {8A8F20FC-C46E-41AC-BEE7-218366CFFF99}.Release|Any CPU.Build.0 = Release|Any CPU {AB6F09BF-E794-4F6A-94BB-C97C0BA84D64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AB6F09BF-E794-4F6A-94BB-C97C0BA84D64}.Debug|Any CPU.Build.0 = Debug|Any CPU {AB6F09BF-E794-4F6A-94BB-C97C0BA84D64}.Release|Any CPU.ActiveCfg = Release|Any CPU {AB6F09BF-E794-4F6A-94BB-C97C0BA84D64}.Release|Any CPU.Build.0 = Release|Any CPU {39EC839A-3C30-4922-A41E-6B09D1DDE5C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {39EC839A-3C30-4922-A41E-6B09D1DDE5C3}.Debug|Any CPU.Build.0 = Debug|Any CPU {39EC839A-3C30-4922-A41E-6B09D1DDE5C3}.Release|Any CPU.ActiveCfg = Release|Any CPU {39EC839A-3C30-4922-A41E-6B09D1DDE5C3}.Release|Any CPU.Build.0 = Release|Any CPU {5EBACBFA-F11A-4BBF-8D02-91461F2293ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5EBACBFA-F11A-4BBF-8D02-91461F2293ED}.Debug|Any CPU.Build.0 = Debug|Any CPU {5EBACBFA-F11A-4BBF-8D02-91461F2293ED}.Release|Any CPU.ActiveCfg = Release|Any CPU {5EBACBFA-F11A-4BBF-8D02-91461F2293ED}.Release|Any CPU.Build.0 = Release|Any CPU {903B2D7D-3B80-4BE9-8713-7447B704E1B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {903B2D7D-3B80-4BE9-8713-7447B704E1B0}.Debug|Any CPU.Build.0 = Debug|Any CPU {903B2D7D-3B80-4BE9-8713-7447B704E1B0}.Release|Any CPU.ActiveCfg = Release|Any CPU {903B2D7D-3B80-4BE9-8713-7447B704E1B0}.Release|Any CPU.Build.0 = Release|Any CPU - {3207D8E7-36E3-4714-9B03-7B5B3D6D351A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3207D8E7-36E3-4714-9B03-7B5B3D6D351A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3207D8E7-36E3-4714-9B03-7B5B3D6D351A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3207D8E7-36E3-4714-9B03-7B5B3D6D351A}.Release|Any CPU.Build.0 = Release|Any CPU {7C3B0C7E-59FE-4E1A-A655-27AE119F9444}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C3B0C7E-59FE-4E1A-A655-27AE119F9444}.Debug|Any CPU.Build.0 = Debug|Any CPU {7C3B0C7E-59FE-4E1A-A655-27AE119F9444}.Release|Any CPU.ActiveCfg = Release|Any CPU {7C3B0C7E-59FE-4E1A-A655-27AE119F9444}.Release|Any CPU.Build.0 = Release|Any CPU {5EFAEF9B-90B9-4F05-9F70-F79AD77FFF86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5EFAEF9B-90B9-4F05-9F70-F79AD77FFF86}.Debug|Any CPU.Build.0 = Debug|Any CPU {5EFAEF9B-90B9-4F05-9F70-F79AD77FFF86}.Release|Any CPU.ActiveCfg = Release|Any CPU {5EFAEF9B-90B9-4F05-9F70-F79AD77FFF86}.Release|Any CPU.Build.0 = Release|Any CPU {4C86DF8F-A280-40D4-85B0-10B1BF66C15C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4C86DF8F-A280-40D4-85B0-10B1BF66C15C}.Debug|Any CPU.Build.0 = Debug|Any CPU {4C86DF8F-A280-40D4-85B0-10B1BF66C15C}.Release|Any CPU.ActiveCfg = Release|Any CPU {4C86DF8F-A280-40D4-85B0-10B1BF66C15C}.Release|Any CPU.Build.0 = Release|Any CPU {81CAC44F-3711-4C8F-AE98-E02A7448782A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {81CAC44F-3711-4C8F-AE98-E02A7448782A}.Debug|Any CPU.Build.0 = Debug|Any CPU {81CAC44F-3711-4C8F-AE98-E02A7448782A}.Release|Any CPU.ActiveCfg = Release|Any CPU {81CAC44F-3711-4C8F-AE98-E02A7448782A}.Release|Any CPU.Build.0 = Release|Any CPU {AF664D85-0A4B-4BAB-A2F8-83110C06553A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AF664D85-0A4B-4BAB-A2F8-83110C06553A}.Debug|Any CPU.Build.0 = Debug|Any CPU {AF664D85-0A4B-4BAB-A2F8-83110C06553A}.Release|Any CPU.ActiveCfg = Release|Any CPU {AF664D85-0A4B-4BAB-A2F8-83110C06553A}.Release|Any CPU.Build.0 = Release|Any CPU {91B4DFC0-543C-43A7-A9E0-6817DCA277EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -117,7 +97,6 @@ Global {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 diff --git a/Patterns/Patterns.vbproj b/Patterns/Patterns.vbproj index 48c0ce5e..c4c3f739 100644 --- a/Patterns/Patterns.vbproj +++ b/Patterns/Patterns.vbproj @@ -44,11 +44,13 @@ On - - - - - + + + + + + + False ..\..\DDMonorepo\Controls.LookupGrid\bin\Debug\DigitalData.Controls.LookupGrid.dll @@ -155,4 +157,8 @@ powershell.exe -command "& { &'$(SolutionDir)copy-binary.ps1' '$(TargetPath)' '$(TargetFileName)' '$(ConfigurationName)' '$(ProjectName)' }" + + + + \ No newline at end of file diff --git a/Patterns/Patterns.vbproj.bak b/Patterns/Patterns.vbproj.bak new file mode 100644 index 00000000..4de7a2c8 --- /dev/null +++ b/Patterns/Patterns.vbproj.bak @@ -0,0 +1,164 @@ + + + + + Debug + AnyCPU + {7C3B0C7E-59FE-4E1A-A655-27AE119F9444} + Library + DigitalData.Modules.Patterns + DigitalData.Modules.Patterns + 512 + Windows + v4.6.2 + + + + true + full + true + true + bin\Debug\ + DigitalData.Modules.Patterns.xml + 42016,41999,42017,42018,42019,42032,42036,42020,42021,42022 + + + pdbonly + false + true + true + bin\Release\ + DigitalData.Modules.Patterns.xml + 42016,41999,42017,42018,42019,42032,42036,42020,42021,42022 + + + On + + + Binary + + + Off + + + On + + + + + + + + + + + False + ..\..\DDMonorepo\Controls.LookupGrid\bin\Debug\DigitalData.Controls.LookupGrid.dll + + + ..\packages\DigitalData.Modules.Logging.2.6.5\lib\net462\DigitalData.Modules.Logging.dll + + + M:\Bibliotheken\3rdParty\windream\Interop.WINDREAMLib.dll + True + + + + ..\packages\NLog.5.0.5\lib\net46\NLog.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + Application.myapp + True + + + True + True + Resources.resx + + + True + Settings.settings + True + + + + + + VbMyResourcesResXFileCodeGenerator + Resources.Designer.vb + My.Resources + Designer + + + + + + MyApplicationCodeGenerator + Application.Designer.vb + + + SettingsSingleFileGenerator + My + Settings.Designer.vb + + + + + + {81cac44f-3711-4c8f-ae98-e02a7448782a} + ZooFlow + + + + + + powershell.exe -command "& { &'$(SolutionDir)copy-binary.ps1' '$(TargetPath)' '$(TargetFileName)' '$(ConfigurationName)' '$(ProjectName)' }" + + + + + + \ No newline at end of file