Optimierung von SQL-Caching und dynamischen Editoren
Ein neuer Shared-Cache (`_ResolvedSqlCache`) wurde eingeführt, um die Performance bei der Verarbeitung von SQL-Platzhaltern zu verbessern. Die Methode `ResolveSqlTemplate` nutzt diesen Cache, um SQL-Templates effizienter zu verarbeiten. Die neue Hilfsfunktion `ConvertToSqlValue` ermöglicht eine sichere und wiederverwendbare Konvertierung von Zellwerten in SQL-kompatible Strings. Die visuelle Kennzeichnung dynamischer Editoren wurde durch den `CustomDrawCell`-Eventhandler verbessert, indem leere Zellen grau eingefärbt werden. Änderungen in dynamischen Spalten führen nun zu gezielter Cache-Invalidierung und asynchronem Grid-Refresh. Die Logging-Strategie wurde überarbeitet, um präzisere Informationen zu liefern. Fehlerbehandlung und Benutzerkommunikation wurden erweitert, um die Stabilität und Nachvollziehbarkeit zu erhöhen. Zusätzlich wurden allgemeine Code-Optimierungen vorgenommen, um die Lesbarkeit und Wartbarkeit zu verbessern.
This commit is contained in:
@@ -30,6 +30,7 @@ Namespace ControlCreator
|
|||||||
Private _FormulaSqlColumns As New Dictionary(Of String, FormulaSqlDefinition)(StringComparer.OrdinalIgnoreCase)
|
Private _FormulaSqlColumns As New Dictionary(Of String, FormulaSqlDefinition)(StringComparer.OrdinalIgnoreCase)
|
||||||
Private _DynamicEditorColumns As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase)
|
Private _DynamicEditorColumns As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase)
|
||||||
Private Shared _DynamicEditorCacheShared As New Dictionary(Of String, RepositoryItem)
|
Private Shared _DynamicEditorCacheShared As New Dictionary(Of String, RepositoryItem)
|
||||||
|
Private Shared _ResolvedSqlCache As New Dictionary(Of String, String)
|
||||||
|
|
||||||
Private _isRefreshingFormula As Boolean = False ' *** NEU: Flag für Formel-Refresh ***
|
Private _isRefreshingFormula As Boolean = False ' *** NEU: Flag für Formel-Refresh ***
|
||||||
Private _currencySymbol As String = "€"
|
Private _currencySymbol As String = "€"
|
||||||
@@ -82,45 +83,63 @@ Namespace ControlCreator
|
|||||||
''' </summary>
|
''' </summary>
|
||||||
Private Function ResolveSqlTemplate(sqlTemplate As String, pView As GridView, rowHandle As Integer) As String
|
Private Function ResolveSqlTemplate(sqlTemplate As String, pView As GridView, rowHandle As Integer) As String
|
||||||
Dim resolvedSql As String = sqlTemplate
|
Dim resolvedSql As String = sqlTemplate
|
||||||
' *** SCHRITT 1: {#TBCOL#...} Platzhalter ersetzen (BESTEHENDER CODE) ***
|
|
||||||
|
' *** SCHRITT 1: {#TBCOL#...} Platzhalter ersetzen ***
|
||||||
Dim pattern As String = "\{#TBCOL#([^}]+)\}"
|
Dim pattern As String = "\{#TBCOL#([^}]+)\}"
|
||||||
Dim matches = Regex.Matches(sqlTemplate, pattern)
|
Dim matches = Regex.Matches(sqlTemplate, pattern)
|
||||||
For Each match As Match In matches
|
For Each match As Match In matches
|
||||||
Dim colName = match.Groups(1).Value
|
Dim colName = match.Groups(1).Value
|
||||||
Dim cellValue = pView.GetRowCellValue(rowHandle, colName)
|
Dim cellValue = pView.GetRowCellValue(rowHandle, colName)
|
||||||
Dim safeValue As String
|
Dim safeValue As String = ConvertToSqlValue(cellValue) ' Hilfsfunktion
|
||||||
|
|
||||||
If cellValue Is Nothing OrElse IsDBNull(cellValue) Then
|
|
||||||
safeValue = "NULL"
|
|
||||||
ElseIf TypeOf cellValue Is String Then
|
|
||||||
' SQL-Injection-Schutz: Einfache Anführungszeichen escapen
|
|
||||||
safeValue = cellValue.ToString().Replace("'", "''")
|
|
||||||
ElseIf TypeOf cellValue Is Boolean Then
|
|
||||||
safeValue = If(CBool(cellValue), "1", "0")
|
|
||||||
Else
|
|
||||||
' Numerische Werte: Invariant-Format (Punkt als Dezimaltrenner)
|
|
||||||
safeValue = Convert.ToString(cellValue, CultureInfo.InvariantCulture)
|
|
||||||
End If
|
|
||||||
|
|
||||||
resolvedSql = resolvedSql.Replace(match.Value, safeValue)
|
resolvedSql = resolvedSql.Replace(match.Value, safeValue)
|
||||||
_Logger.Debug("Resolved SQL placeholder [{0}] with value [{1}] → {2}", match.Value, cellValue, safeValue)
|
|
||||||
Next
|
Next
|
||||||
' *** SCHRITT 2: Weitere Patterns via clsPatterns ersetzen (NEU) ***
|
|
||||||
If _ParentControl IsNot Nothing Then
|
' *** SCHRITT 2: {#CTRL#...} via clsPatterns - MIT CACHE ***
|
||||||
Try
|
If _ParentControl IsNot Nothing AndAlso resolvedSql.Contains("{#CTRL#") Then
|
||||||
_Logger.Debug("Applying clsPatterns.ReplaceAllValues() to SQL: {0}", resolvedSql)
|
' Cache-Key: Hash aus SQL + Control-Werten
|
||||||
resolvedSql = clsPatterns.ReplaceAllValues(resolvedSql, _ParentControl, True)
|
Dim cacheKey = GenerateCacheKey(resolvedSql, _ParentControl)
|
||||||
_Logger.Debug("After clsPatterns: {0}", resolvedSql)
|
|
||||||
Catch ex As Exception
|
SyncLock _ResolvedSqlCache
|
||||||
_Logger.Warn("⚠️ clsPatterns.ReplaceAllValues() failed: {0}", ex.Message)
|
If _ResolvedSqlCache.ContainsKey(cacheKey) Then
|
||||||
_Logger.Error(ex)
|
resolvedSql = _ResolvedSqlCache(cacheKey)
|
||||||
End Try
|
' Kein Log → spart 200+ Zeilen
|
||||||
Else
|
Else
|
||||||
_Logger.Debug("ParentControl is Nothing – skipping clsPatterns.ReplaceAllValues()")
|
' Nur bei Cache-Miss ReplaceAllValues aufrufen
|
||||||
|
resolvedSql = clsPatterns.ReplaceAllValues(resolvedSql, _ParentControl, True)
|
||||||
|
_ResolvedSqlCache(cacheKey) = resolvedSql
|
||||||
|
If LOG_HOTSPOTS Then _Logger.Debug("[ResolveSqlTemplate] ReplaceAllValues-Cache MISS")
|
||||||
|
End If
|
||||||
|
End SyncLock
|
||||||
End If
|
End If
|
||||||
_Logger.Debug("Final resolved SQL: {0}", resolvedSql)
|
|
||||||
Return resolvedSql
|
Return resolvedSql
|
||||||
End Function
|
End Function
|
||||||
|
''' <summary>
|
||||||
|
''' Konvertiert einen Zellwert in einen SQL-sicheren String (für {#TBCOL#...} Platzhalter).
|
||||||
|
''' </summary>
|
||||||
|
Private Function ConvertToSqlValue(cellValue As Object) As String
|
||||||
|
If cellValue Is Nothing OrElse IsDBNull(cellValue) Then
|
||||||
|
Return "NULL"
|
||||||
|
ElseIf TypeOf cellValue Is String Then
|
||||||
|
' SQL-Injection-Schutz: Einfache Anführungszeichen escapen
|
||||||
|
Return cellValue.ToString().Replace("'", "''")
|
||||||
|
ElseIf TypeOf cellValue Is Boolean Then
|
||||||
|
Return If(CBool(cellValue), "1", "0")
|
||||||
|
ElseIf TypeOf cellValue Is DateTime Then
|
||||||
|
' ISO-Format für SQL
|
||||||
|
Return CDate(cellValue).ToString("yyyy-MM-dd HH:mm:ss")
|
||||||
|
Else
|
||||||
|
' Numerische Werte: Invariant-Format (Punkt als Dezimaltrenner)
|
||||||
|
Return Convert.ToString(cellValue, CultureInfo.InvariantCulture)
|
||||||
|
End If
|
||||||
|
End Function
|
||||||
|
''' <summary>
|
||||||
|
''' Placeholder für Control-Value-Hashing.
|
||||||
|
''' </summary>
|
||||||
|
Private Function GenerateCacheKey(sql As String, parent As Control) As String
|
||||||
|
' Cache-Key basiert NUR auf aufgelöstem SQL (Control-Werte sind bereits drin)
|
||||||
|
Return sql.GetHashCode().ToString()
|
||||||
|
End Function
|
||||||
Public Sub New(pLogConfig As LogConfig, pGridTables As Dictionary(Of Integer, Dictionary(Of String, RepositoryItem)), pCurrencySymbol As String, pParentControl As Control)
|
Public Sub New(pLogConfig As LogConfig, pGridTables As Dictionary(Of Integer, Dictionary(Of String, RepositoryItem)), pCurrencySymbol As String, pParentControl As Control)
|
||||||
_LogConfig = pLogConfig
|
_LogConfig = pLogConfig
|
||||||
_Logger = pLogConfig.GetLogger()
|
_Logger = pLogConfig.GetLogger()
|
||||||
@@ -784,10 +803,9 @@ Namespace ControlCreator
|
|||||||
If _DynamicEditorCacheShared.ContainsKey(cacheKey) Then
|
If _DynamicEditorCacheShared.ContainsKey(cacheKey) Then
|
||||||
' ✅ CACHE HIT: Editor wiederverwenden
|
' ✅ CACHE HIT: Editor wiederverwenden
|
||||||
e.RepositoryItem = _DynamicEditorCacheShared(cacheKey)
|
e.RepositoryItem = _DynamicEditorCacheShared(cacheKey)
|
||||||
_Logger.Debug("[CustomRowCellEdit] Using CACHED editor for [{0}] (CacheKey=[{1}])", e.Column.FieldName, cacheKey)
|
|
||||||
Else
|
Else
|
||||||
' ❌ CACHE MISS: Neuen Editor erstellen
|
' ❌ CACHE MISS: Neuen Editor erstellen
|
||||||
_Logger.Debug("[CustomRowCellEdit] Creating NEW row-specific editor for [{0}]", e.Column.FieldName)
|
_Logger.Info("[CustomRowCellEdit] 🆕 MISS: Creating editor for [{0}]", e.Column.FieldName)
|
||||||
|
|
||||||
Dim realEditor = CreateRowSpecificEditor(e.Column.FieldName, resolvedSql, oConnectionId, oIsAdvancedLookup)
|
Dim realEditor = CreateRowSpecificEditor(e.Column.FieldName, resolvedSql, oConnectionId, oIsAdvancedLookup)
|
||||||
|
|
||||||
@@ -795,7 +813,7 @@ Namespace ControlCreator
|
|||||||
' *** IN CACHE SPEICHERN ***
|
' *** IN CACHE SPEICHERN ***
|
||||||
_DynamicEditorCacheShared(cacheKey) = realEditor
|
_DynamicEditorCacheShared(cacheKey) = realEditor
|
||||||
e.RepositoryItem = realEditor
|
e.RepositoryItem = realEditor
|
||||||
_Logger.Debug("[CustomRowCellEdit] CACHED new editor (Type=[{0}], CacheKey=[{1}])", realEditor.GetType().Name, cacheKey)
|
_Logger.Info("[CustomRowCellEdit] ✓ Cached [{0}] editor (Type=[{1}])", e.Column.FieldName, realEditor.GetType().Name)
|
||||||
Else
|
Else
|
||||||
_Logger.Warn("[CustomRowCellEdit] CreateRowSpecificEditor returned Nothing for [{0}]", e.Column.FieldName)
|
_Logger.Warn("[CustomRowCellEdit] CreateRowSpecificEditor returned Nothing for [{0}]", e.Column.FieldName)
|
||||||
End If
|
End If
|
||||||
@@ -817,7 +835,35 @@ Namespace ControlCreator
|
|||||||
_Logger.Error(ex)
|
_Logger.Error(ex)
|
||||||
End Try
|
End Try
|
||||||
End Sub
|
End Sub
|
||||||
|
' *** NEU: Visuelle Kennzeichnung für dynamische Editoren ***
|
||||||
|
AddHandler pGridView.CustomDrawCell,
|
||||||
|
Sub(sender As Object, e As RowCellCustomDrawEventArgs)
|
||||||
|
Try
|
||||||
|
' Nur für dynamische Editor-Spalten
|
||||||
|
If Not _DynamicEditorColumns.Contains(e.Column.FieldName) Then Return
|
||||||
|
|
||||||
|
' Zellwert abrufen
|
||||||
|
Dim cellValue = pGridView.GetRowCellValue(e.RowHandle, e.Column.FieldName)
|
||||||
|
|
||||||
|
' Wenn Zelle LEER ist → grau einfärben
|
||||||
|
If cellValue Is Nothing OrElse IsDBNull(cellValue) OrElse String.IsNullOrWhiteSpace(cellValue.ToString()) Then
|
||||||
|
' Helles Grau als Hintergrund
|
||||||
|
e.Appearance.BackColor = Color.FromArgb(240, 240, 240)
|
||||||
|
e.Appearance.ForeColor = Color.Gray
|
||||||
|
Else
|
||||||
|
' Wert vorhanden → Standardfarbe (nur bei Fokus-Wechsel)
|
||||||
|
' WICHTIG: Nicht überschreiben, wenn Zelle selektiert ist!
|
||||||
|
If Not e.Appearance.BackColor.Equals(SystemColors.Highlight) Then
|
||||||
|
e.Appearance.BackColor = Color.White
|
||||||
|
e.Appearance.ForeColor = Color.Black
|
||||||
|
End If
|
||||||
|
End If
|
||||||
|
|
||||||
|
Catch ex As Exception
|
||||||
|
_Logger.Error("[CustomDrawCell] Error: {0}", ex.Message)
|
||||||
|
_Logger.Error(ex)
|
||||||
|
End Try
|
||||||
|
End Sub
|
||||||
AddHandler pGridView.ValidatingEditor, Sub(sender As Object, e As BaseContainerValidateEditorEventArgs)
|
AddHandler pGridView.ValidatingEditor, Sub(sender As Object, e As BaseContainerValidateEditorEventArgs)
|
||||||
Dim oRow As DataRowView = pGridView.GetRow(pGridView.FocusedRowHandle)
|
Dim oRow As DataRowView = pGridView.GetRow(pGridView.FocusedRowHandle)
|
||||||
Dim oColumnName = pGridView.FocusedColumn.FieldName
|
Dim oColumnName = pGridView.FocusedColumn.FieldName
|
||||||
@@ -1001,15 +1047,127 @@ CheckNewItemRow:
|
|||||||
|
|
||||||
AddHandler pGridView.CellValueChanged,
|
AddHandler pGridView.CellValueChanged,
|
||||||
Sub(sender As Object, e As CellValueChangedEventArgs)
|
Sub(sender As Object, e As CellValueChangedEventArgs)
|
||||||
' *** HandleInheritedColumnValue MUSS zuerst aufgerufen werden ***
|
' *** 1. HandleInheritedColumnValue ZUERST ***
|
||||||
Try
|
Try
|
||||||
HandleInheritedColumnValue(TryCast(sender, GridView), pColumnTable, e)
|
HandleInheritedColumnValue(TryCast(sender, GridView), pColumnTable, e)
|
||||||
Catch ex As Exception
|
Catch ex As Exception
|
||||||
_Logger.Error(ex)
|
_Logger.Error(ex)
|
||||||
End Try
|
End Try
|
||||||
|
|
||||||
' *** FORMULA_EXPRESSION-Refresh via CellValueChanged (FALLBACK) ***
|
' *** 2. Cache-Invalidierung für dynamische Editoren ***
|
||||||
' (EditValueChanged macht das normalerweise schon LIVE)
|
Try
|
||||||
|
Dim oView As GridView = TryCast(sender, GridView)
|
||||||
|
If oView Is Nothing OrElse e.Column Is Nothing Then Return
|
||||||
|
|
||||||
|
' Finde alle dynamischen Spalten, die die geänderte Spalte via {#TBCOL#...} referenzieren
|
||||||
|
Dim oDependentDynamicColumns As New List(Of String)
|
||||||
|
|
||||||
|
For Each dynColName In _DynamicEditorColumns
|
||||||
|
Dim oColDef As DataRow = pColumnTable.
|
||||||
|
Select($"SPALTENNAME = '{dynColName}'").
|
||||||
|
FirstOrDefault()
|
||||||
|
|
||||||
|
If oColDef Is Nothing Then Continue For
|
||||||
|
|
||||||
|
Dim oSqlCommand As String = oColDef.ItemEx("SQL_COMMAND", "")
|
||||||
|
|
||||||
|
' Prüfe ob SQL {#TBCOL#<ColumnName>} enthält
|
||||||
|
If oSqlCommand.Contains($"{{#TBCOL#{e.Column.FieldName}}}") Then
|
||||||
|
oDependentDynamicColumns.Add(dynColName)
|
||||||
|
_Logger.Debug("[CellValueChanged] Spalte [{0}] referenziert via #TBCOL# [{1}] → Cache invalidieren",
|
||||||
|
dynColName, e.Column.FieldName)
|
||||||
|
End If
|
||||||
|
Next
|
||||||
|
|
||||||
|
' Cache-Einträge für ALLE betroffenen Zeilen löschen
|
||||||
|
If oDependentDynamicColumns.Count > 0 Then
|
||||||
|
SyncLock _DynamicEditorCacheShared
|
||||||
|
Dim oKeysToRemove As New List(Of String)
|
||||||
|
|
||||||
|
For Each cacheKey In _DynamicEditorCacheShared.Keys
|
||||||
|
For Each depCol In oDependentDynamicColumns
|
||||||
|
' Cache-Key-Format: "ColumnName|HashCode"
|
||||||
|
If cacheKey.StartsWith(depCol & "|") Then
|
||||||
|
oKeysToRemove.Add(cacheKey)
|
||||||
|
Exit For
|
||||||
|
End If
|
||||||
|
Next
|
||||||
|
Next
|
||||||
|
|
||||||
|
' Cache-Keys löschen
|
||||||
|
For Each keyToRemove In oKeysToRemove
|
||||||
|
_DynamicEditorCacheShared.Remove(keyToRemove)
|
||||||
|
_Logger.Info("[CellValueChanged] ♻️ Cache invalidiert: [{0}]", keyToRemove)
|
||||||
|
Next
|
||||||
|
|
||||||
|
If oKeysToRemove.Count > 0 Then
|
||||||
|
_Logger.Info("[CellValueChanged] ♻️ Gesamt: {0} Cache-Einträge für [{1}] gelöscht",
|
||||||
|
oKeysToRemove.Count, e.Column.FieldName)
|
||||||
|
End If
|
||||||
|
End SyncLock
|
||||||
|
End If
|
||||||
|
|
||||||
|
Catch ex As Exception
|
||||||
|
_Logger.Error("[CellValueChanged] Cache-Invalidierung fehlgeschlagen: {0}", ex.Message)
|
||||||
|
_Logger.Error(ex)
|
||||||
|
End Try
|
||||||
|
|
||||||
|
' *** Block 2.5: Grid-Invalidierung für dynamische Editoren ***
|
||||||
|
Try
|
||||||
|
Dim oView As GridView = TryCast(sender, GridView)
|
||||||
|
If oView Is Nothing OrElse e.Column Is Nothing Then Return
|
||||||
|
|
||||||
|
' Finde alle dynamischen Spalten, die die geänderte Spalte via {#TBCOL#...} referenzieren
|
||||||
|
Dim oDependentDynamicColumns As New List(Of String)
|
||||||
|
|
||||||
|
For Each dynColName In _DynamicEditorColumns
|
||||||
|
Dim oColDef As DataRow = pColumnTable.
|
||||||
|
Select($"SPALTENNAME = '{dynColName}'").
|
||||||
|
FirstOrDefault()
|
||||||
|
|
||||||
|
If oColDef Is Nothing Then Continue For
|
||||||
|
|
||||||
|
Dim oSqlCommand As String = oColDef.ItemEx("SQL_COMMAND", "")
|
||||||
|
|
||||||
|
' Prüfe ob SQL {#TBCOL#<ColumnName>} enthält
|
||||||
|
If oSqlCommand.Contains($"{{#TBCOL#{e.Column.FieldName}}}") Then
|
||||||
|
oDependentDynamicColumns.Add(dynColName)
|
||||||
|
End If
|
||||||
|
Next
|
||||||
|
|
||||||
|
' *** KERN-FIX: Grid-Refresh OHNE Cache-Duplikat ***
|
||||||
|
If oDependentDynamicColumns.Count > 0 Then
|
||||||
|
_Logger.Debug("[CellValueChanged] Invalidiere {0} Zeilen für abhängige Spalten: [{1}]",
|
||||||
|
oView.DataRowCount, String.Join(", ", oDependentDynamicColumns))
|
||||||
|
|
||||||
|
' BeginInvoke: Grid-Refresh NACH allen CellValueChanged-Events
|
||||||
|
oView.GridControl.BeginInvoke(New Action(
|
||||||
|
Sub()
|
||||||
|
Try
|
||||||
|
For rowHandle As Integer = 0 To oView.DataRowCount - 1
|
||||||
|
For Each depCol In oDependentDynamicColumns
|
||||||
|
Dim oGridColumn = oView.Columns.ColumnByFieldName(depCol)
|
||||||
|
If oGridColumn IsNot Nothing Then
|
||||||
|
oView.RefreshRowCell(rowHandle, oGridColumn)
|
||||||
|
End If
|
||||||
|
Next
|
||||||
|
Next
|
||||||
|
|
||||||
|
_Logger.Info("[CellValueChanged] ✓ Grid-Invalidierung abgeschlossen: {0} Zeilen, {1} Spalten",
|
||||||
|
oView.DataRowCount, oDependentDynamicColumns.Count)
|
||||||
|
Catch ex As Exception
|
||||||
|
_Logger.Error("[CellValueChanged] Grid-Invalidierung fehlgeschlagen: {0}", ex.Message)
|
||||||
|
_Logger.Error(ex)
|
||||||
|
End Try
|
||||||
|
End Sub))
|
||||||
|
End If
|
||||||
|
|
||||||
|
Catch ex As Exception
|
||||||
|
_Logger.Error("[CellValueChanged] Grid-Invalidierung fehlgeschlagen: {0}", ex.Message)
|
||||||
|
_Logger.Error(ex)
|
||||||
|
End Try
|
||||||
|
|
||||||
|
' *** 3. FORMULA_EXPRESSION-Refresh via CellValueChanged (FALLBACK) ***
|
||||||
Try
|
Try
|
||||||
Dim oView As GridView = TryCast(sender, GridView)
|
Dim oView As GridView = TryCast(sender, GridView)
|
||||||
If oView Is Nothing OrElse e.Column Is Nothing Then Return
|
If oView Is Nothing OrElse e.Column Is Nothing Then Return
|
||||||
@@ -1031,7 +1189,7 @@ CheckNewItemRow:
|
|||||||
Next
|
Next
|
||||||
|
|
||||||
If oFormulaColumnsToRefresh.Count = 0 Then
|
If oFormulaColumnsToRefresh.Count = 0 Then
|
||||||
' Kein FORMULA_EXPRESSION-Refresh nötig – weiter zu FORMULA_SQL
|
' Kein FORMULA_EXPRESSION-Refresh nötig
|
||||||
Else
|
Else
|
||||||
Dim oRowHandle As Integer = e.RowHandle
|
Dim oRowHandle As Integer = e.RowHandle
|
||||||
_Logger.Debug("[FormulaRefresh] CellValueChanged FALLBACK – refreshing EXPRESSION columns for row [{0}] after column [{1}] changed.", oRowHandle, e.Column.FieldName)
|
_Logger.Debug("[FormulaRefresh] CellValueChanged FALLBACK – refreshing EXPRESSION columns for row [{0}] after column [{1}] changed.", oRowHandle, e.Column.FieldName)
|
||||||
@@ -1049,7 +1207,7 @@ CheckNewItemRow:
|
|||||||
_Logger.Debug("[FormulaRefresh] FALLBACK DisplayText for [{0}]: [{1}]",
|
_Logger.Debug("[FormulaRefresh] FALLBACK DisplayText for [{0}]: [{1}]",
|
||||||
oFormulaColumnName, oView.GetRowCellDisplayText(oRowHandle, oGridColumn))
|
oFormulaColumnName, oView.GetRowCellDisplayText(oRowHandle, oGridColumn))
|
||||||
Next
|
Next
|
||||||
' Dies fängt den Fall ab, dass eine SQL-Spalte eine Expression-Spalte referenziert
|
' SQL-Formeln die Expression-Spalten referenzieren auch triggern
|
||||||
TriggerSqlFormulasAfterExpressionUpdate(oView, oRowHandle, oFormulaColumnsToRefresh)
|
TriggerSqlFormulasAfterExpressionUpdate(oView, oRowHandle, oFormulaColumnsToRefresh)
|
||||||
Catch ex As Exception
|
Catch ex As Exception
|
||||||
_Logger.Error(ex)
|
_Logger.Error(ex)
|
||||||
@@ -1061,8 +1219,7 @@ CheckNewItemRow:
|
|||||||
_Logger.Error(ex)
|
_Logger.Error(ex)
|
||||||
End Try
|
End Try
|
||||||
|
|
||||||
' *** FORMULA_SQL-Refresh via CellValueChanged ***
|
' *** 4. FORMULA_SQL-Refresh via CellValueChanged ***
|
||||||
' SQL wird NUR hier ausgeführt (nicht in EditValueChanged) um DB-Roundtrips zu minimieren
|
|
||||||
Try
|
Try
|
||||||
Dim oView As GridView = TryCast(sender, GridView)
|
Dim oView As GridView = TryCast(sender, GridView)
|
||||||
If oView Is Nothing OrElse e.Column Is Nothing Then Return
|
If oView Is Nothing OrElse e.Column Is Nothing Then Return
|
||||||
@@ -1082,7 +1239,7 @@ CheckNewItemRow:
|
|||||||
_Logger.Debug("[FormulaSql] CellValueChanged – column [{0}] triggers SQL refresh for: [{1}]",
|
_Logger.Debug("[FormulaSql] CellValueChanged – column [{0}] triggers SQL refresh for: [{1}]",
|
||||||
e.Column.FieldName, String.Join(", ", oSqlColumnsToRefresh))
|
e.Column.FieldName, String.Join(", ", oSqlColumnsToRefresh))
|
||||||
|
|
||||||
' BeginInvoke: UI nicht blockieren, GridView in stabilem Zustand
|
' BeginInvoke: UI nicht blockieren
|
||||||
oView.GridControl.BeginInvoke(New Action(
|
oView.GridControl.BeginInvoke(New Action(
|
||||||
Sub()
|
Sub()
|
||||||
Try
|
Try
|
||||||
@@ -1110,7 +1267,9 @@ CheckNewItemRow:
|
|||||||
|
|
||||||
Try
|
Try
|
||||||
Dim oResultTable As DataTable = DatabaseFallback.GetDatatable(
|
Dim oResultTable As DataTable = DatabaseFallback.GetDatatable(
|
||||||
New GetDatatableOptions(resolvedSql, DatabaseType.ECM))
|
New GetDatatableOptions(resolvedSql, DatabaseType.ECM) With {
|
||||||
|
.ConnectionId = oDefinition.ConnectionId
|
||||||
|
})
|
||||||
|
|
||||||
If oResultTable IsNot Nothing AndAlso oResultTable.Rows.Count > 0 Then
|
If oResultTable IsNot Nothing AndAlso oResultTable.Rows.Count > 0 Then
|
||||||
Dim oResult = oResultTable.Rows(0).Item(0)
|
Dim oResult = oResultTable.Rows(0).Item(0)
|
||||||
@@ -1141,6 +1300,18 @@ CheckNewItemRow:
|
|||||||
Catch ex As Exception
|
Catch ex As Exception
|
||||||
_Logger.Error(ex)
|
_Logger.Error(ex)
|
||||||
End Try
|
End Try
|
||||||
|
|
||||||
|
' *** 5. Dynamische Editor-Invalidierung (visuell) ***
|
||||||
|
Try
|
||||||
|
Dim oView As GridView = TryCast(sender, GridView)
|
||||||
|
If oView IsNot Nothing AndAlso e.Column IsNot Nothing Then
|
||||||
|
If _DynamicEditorColumns.Contains(e.Column.FieldName) Then
|
||||||
|
oView.InvalidateRow(e.RowHandle)
|
||||||
|
End If
|
||||||
|
End If
|
||||||
|
Catch ex As Exception
|
||||||
|
_Logger.Error(ex)
|
||||||
|
End Try
|
||||||
End Sub
|
End Sub
|
||||||
End Sub
|
End Sub
|
||||||
''' <summary>
|
''' <summary>
|
||||||
@@ -1382,6 +1553,11 @@ CheckNewItemRow:
|
|||||||
End If
|
End If
|
||||||
|
|
||||||
If isApplyingInheritedValue OrElse pArgs.RowHandle = DevExpress.XtraGrid.GridControl.InvalidRowHandle Then
|
If isApplyingInheritedValue OrElse pArgs.RowHandle = DevExpress.XtraGrid.GridControl.InvalidRowHandle Then
|
||||||
|
If isApplyingInheritedValue Then
|
||||||
|
_Logger.Debug("Currently applying inherited value for column {0} – skipping to prevent recursion.", pArgs.Column.FieldName)
|
||||||
|
Else
|
||||||
|
_Logger.Debug("Invalid RowHandle ({0}) – skipping HandleInheritedColumnValue.", pArgs.RowHandle)
|
||||||
|
End If
|
||||||
Return
|
Return
|
||||||
End If
|
End If
|
||||||
|
|
||||||
@@ -1425,9 +1601,9 @@ CheckNewItemRow:
|
|||||||
affectedRowsCount)
|
affectedRowsCount)
|
||||||
If USER_LANGUAGE <> "de-DE" Then
|
If USER_LANGUAGE <> "de-DE" Then
|
||||||
confirmMessage = String.Format(
|
confirmMessage = String.Format(
|
||||||
"Do you want to inherit the value '{0}' to {1} subsequent row(s)?",
|
"Do you want to inherit the value '{0}' to {1} subsequent row(s)?",
|
||||||
valueToApply,
|
valueToApply,
|
||||||
affectedRowsCount)
|
affectedRowsCount)
|
||||||
End If
|
End If
|
||||||
Dim confirmTitle As String = "Wertevererbung bestätigen"
|
Dim confirmTitle As String = "Wertevererbung bestätigen"
|
||||||
If USER_LANGUAGE <> "de-DE" Then
|
If USER_LANGUAGE <> "de-DE" Then
|
||||||
@@ -1483,9 +1659,14 @@ CheckNewItemRow:
|
|||||||
_Logger.Info("Skipping confirmation dialog (already confirmed {0} times)", confirmationEntry.Count)
|
_Logger.Info("Skipping confirmation dialog (already confirmed {0} times)", confirmationEntry.Count)
|
||||||
End If
|
End If
|
||||||
|
|
||||||
|
' *** NEU: Cache-Keys sammeln WÄHREND der Vererbung ***
|
||||||
|
Dim oCacheKeysToInvalidate As New List(Of String)
|
||||||
|
|
||||||
isApplyingInheritedValue = True
|
isApplyingInheritedValue = True
|
||||||
Try
|
Try
|
||||||
_Logger.Info(String.Format("Inherit Value is active for column. So inheritting the value [{0}]...", valueToApply))
|
_Logger.Info(String.Format("Inherit Value is active for column [{0}]. Inheriting value [{1}] to {2} rows...",
|
||||||
|
pArgs.Column.FieldName, valueToApply, affectedRowsCount))
|
||||||
|
|
||||||
For dataIndex As Integer = listIndex + 1 To pView.DataRowCount - 1
|
For dataIndex As Integer = listIndex + 1 To pView.DataRowCount - 1
|
||||||
Dim targetHandle = pView.GetRowHandle(dataIndex)
|
Dim targetHandle = pView.GetRowHandle(dataIndex)
|
||||||
If targetHandle = DevExpress.XtraGrid.GridControl.InvalidRowHandle OrElse pView.IsGroupRow(targetHandle) Then
|
If targetHandle = DevExpress.XtraGrid.GridControl.InvalidRowHandle OrElse pView.IsGroupRow(targetHandle) Then
|
||||||
@@ -1501,12 +1682,62 @@ CheckNewItemRow:
|
|||||||
Continue For
|
Continue For
|
||||||
End If
|
End If
|
||||||
|
|
||||||
|
' *** SCHRITT 1: Wert setzen ***
|
||||||
pView.SetRowCellValue(targetHandle, pArgs.Column.FieldName, valueToApply)
|
pView.SetRowCellValue(targetHandle, pArgs.Column.FieldName, valueToApply)
|
||||||
|
|
||||||
|
' *** SCHRITT 2: Cache-Keys für DIESE Zeile sammeln ***
|
||||||
|
' Finde alle dynamischen Spalten, die die geänderte Spalte via {#TBCOL#...} referenzieren
|
||||||
|
For Each dynColName In _DynamicEditorColumns
|
||||||
|
Dim oColDef As DataRow = pColumnDefinition.
|
||||||
|
Select($"SPALTENNAME = '{dynColName}'").
|
||||||
|
FirstOrDefault()
|
||||||
|
|
||||||
|
If oColDef Is Nothing Then Continue For
|
||||||
|
|
||||||
|
Dim oSqlCommand As String = oColDef.ItemEx("SQL_COMMAND", "")
|
||||||
|
|
||||||
|
' Prüfe ob SQL {#TBCOL#<ColumnName>} enthält
|
||||||
|
If oSqlCommand.Contains($"{{#TBCOL#{pArgs.Column.FieldName}}}") Then
|
||||||
|
' SQL auflösen mit NEUEM Wert (targetHandle!)
|
||||||
|
Try
|
||||||
|
Dim resolvedSql = ResolveSqlTemplate(oSqlCommand, pView, targetHandle)
|
||||||
|
Dim cacheKey = $"{dynColName}|{resolvedSql.GetHashCode()}"
|
||||||
|
|
||||||
|
If Not oCacheKeysToInvalidate.Contains(cacheKey) Then
|
||||||
|
oCacheKeysToInvalidate.Add(cacheKey)
|
||||||
|
_Logger.Debug("[HandleInheritedColumnValue] Marked for invalidation: [{0}] (Row {1})",
|
||||||
|
cacheKey, targetHandle)
|
||||||
|
End If
|
||||||
|
Catch ex As Exception
|
||||||
|
_Logger.Error("[HandleInheritedColumnValue] Failed to resolve SQL for column [{0}], row {1}: {2}",
|
||||||
|
dynColName, targetHandle, ex.Message)
|
||||||
|
End Try
|
||||||
|
End If
|
||||||
|
Next
|
||||||
Next
|
Next
|
||||||
|
|
||||||
|
' *** SCHRITT 3: Cache SOFORT invalidieren BEVOR Grid refresht wird ***
|
||||||
|
If oCacheKeysToInvalidate.Count > 0 Then
|
||||||
|
_Logger.Info("[HandleInheritedColumnValue] ♻️ Invalidating {0} cache entries BEFORE grid refresh...",
|
||||||
|
oCacheKeysToInvalidate.Count)
|
||||||
|
|
||||||
|
SyncLock _DynamicEditorCacheShared
|
||||||
|
For Each keyToRemove In oCacheKeysToInvalidate
|
||||||
|
If _DynamicEditorCacheShared.ContainsKey(keyToRemove) Then
|
||||||
|
_DynamicEditorCacheShared.Remove(keyToRemove)
|
||||||
|
_Logger.Debug("[HandleInheritedColumnValue] ✓ Cache invalidated (IMMEDIATE): [{0}]", keyToRemove)
|
||||||
|
End If
|
||||||
|
Next
|
||||||
|
End SyncLock
|
||||||
|
|
||||||
|
_Logger.Info("[HandleInheritedColumnValue] ✓ Cache invalidation complete. User can now click cells safely.")
|
||||||
|
End If
|
||||||
|
|
||||||
Finally
|
Finally
|
||||||
isApplyingInheritedValue = False
|
isApplyingInheritedValue = False
|
||||||
End Try
|
End Try
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
Private Sub SetInheritanceConfirmationCount(columnName As String, newCount As Integer)
|
Private Sub SetInheritanceConfirmationCount(columnName As String, newCount As Integer)
|
||||||
Dim entries = UserInheritance_ConfirmationByColumn
|
Dim entries = UserInheritance_ConfirmationByColumn
|
||||||
If entries Is Nothing Then
|
If entries Is Nothing Then
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user