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:
Developer01
2026-06-16 08:10:16 +02:00
parent 3e7d700536
commit 0cc7fe45d3
2 changed files with 1231 additions and 4118 deletions

View File

@@ -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