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 _DynamicEditorColumns As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase)
|
||||
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 _currencySymbol As String = "€"
|
||||
@@ -82,45 +83,63 @@ Namespace ControlCreator
|
||||
''' </summary>
|
||||
Private Function ResolveSqlTemplate(sqlTemplate As String, pView As GridView, rowHandle As Integer) As String
|
||||
Dim resolvedSql As String = sqlTemplate
|
||||
' *** SCHRITT 1: {#TBCOL#...} Platzhalter ersetzen (BESTEHENDER CODE) ***
|
||||
|
||||
' *** SCHRITT 1: {#TBCOL#...} Platzhalter ersetzen ***
|
||||
Dim pattern As String = "\{#TBCOL#([^}]+)\}"
|
||||
Dim matches = Regex.Matches(sqlTemplate, pattern)
|
||||
For Each match As Match In matches
|
||||
Dim colName = match.Groups(1).Value
|
||||
Dim cellValue = pView.GetRowCellValue(rowHandle, colName)
|
||||
Dim safeValue As String
|
||||
|
||||
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
|
||||
|
||||
Dim safeValue As String = ConvertToSqlValue(cellValue) ' Hilfsfunktion
|
||||
resolvedSql = resolvedSql.Replace(match.Value, safeValue)
|
||||
_Logger.Debug("Resolved SQL placeholder [{0}] with value [{1}] → {2}", match.Value, cellValue, safeValue)
|
||||
Next
|
||||
' *** SCHRITT 2: Weitere Patterns via clsPatterns ersetzen (NEU) ***
|
||||
If _ParentControl IsNot Nothing Then
|
||||
Try
|
||||
_Logger.Debug("Applying clsPatterns.ReplaceAllValues() to SQL: {0}", resolvedSql)
|
||||
resolvedSql = clsPatterns.ReplaceAllValues(resolvedSql, _ParentControl, True)
|
||||
_Logger.Debug("After clsPatterns: {0}", resolvedSql)
|
||||
Catch ex As Exception
|
||||
_Logger.Warn("⚠️ clsPatterns.ReplaceAllValues() failed: {0}", ex.Message)
|
||||
_Logger.Error(ex)
|
||||
End Try
|
||||
Else
|
||||
_Logger.Debug("ParentControl is Nothing – skipping clsPatterns.ReplaceAllValues()")
|
||||
|
||||
' *** SCHRITT 2: {#CTRL#...} via clsPatterns - MIT CACHE ***
|
||||
If _ParentControl IsNot Nothing AndAlso resolvedSql.Contains("{#CTRL#") Then
|
||||
' Cache-Key: Hash aus SQL + Control-Werten
|
||||
Dim cacheKey = GenerateCacheKey(resolvedSql, _ParentControl)
|
||||
|
||||
SyncLock _ResolvedSqlCache
|
||||
If _ResolvedSqlCache.ContainsKey(cacheKey) Then
|
||||
resolvedSql = _ResolvedSqlCache(cacheKey)
|
||||
' Kein Log → spart 200+ Zeilen
|
||||
Else
|
||||
' 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
|
||||
_Logger.Debug("Final resolved SQL: {0}", resolvedSql)
|
||||
|
||||
Return resolvedSql
|
||||
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)
|
||||
_LogConfig = pLogConfig
|
||||
_Logger = pLogConfig.GetLogger()
|
||||
@@ -784,10 +803,9 @@ Namespace ControlCreator
|
||||
If _DynamicEditorCacheShared.ContainsKey(cacheKey) Then
|
||||
' ✅ CACHE HIT: Editor wiederverwenden
|
||||
e.RepositoryItem = _DynamicEditorCacheShared(cacheKey)
|
||||
_Logger.Debug("[CustomRowCellEdit] Using CACHED editor for [{0}] (CacheKey=[{1}])", e.Column.FieldName, cacheKey)
|
||||
Else
|
||||
' ❌ 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)
|
||||
|
||||
@@ -795,7 +813,7 @@ Namespace ControlCreator
|
||||
' *** IN CACHE SPEICHERN ***
|
||||
_DynamicEditorCacheShared(cacheKey) = 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
|
||||
_Logger.Warn("[CustomRowCellEdit] CreateRowSpecificEditor returned Nothing for [{0}]", e.Column.FieldName)
|
||||
End If
|
||||
@@ -817,7 +835,35 @@ Namespace ControlCreator
|
||||
_Logger.Error(ex)
|
||||
End Try
|
||||
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)
|
||||
Dim oRow As DataRowView = pGridView.GetRow(pGridView.FocusedRowHandle)
|
||||
Dim oColumnName = pGridView.FocusedColumn.FieldName
|
||||
@@ -1001,15 +1047,127 @@ CheckNewItemRow:
|
||||
|
||||
AddHandler pGridView.CellValueChanged,
|
||||
Sub(sender As Object, e As CellValueChangedEventArgs)
|
||||
' *** HandleInheritedColumnValue MUSS zuerst aufgerufen werden ***
|
||||
' *** 1. HandleInheritedColumnValue ZUERST ***
|
||||
Try
|
||||
HandleInheritedColumnValue(TryCast(sender, GridView), pColumnTable, e)
|
||||
Catch ex As Exception
|
||||
_Logger.Error(ex)
|
||||
End Try
|
||||
|
||||
' *** FORMULA_EXPRESSION-Refresh via CellValueChanged (FALLBACK) ***
|
||||
' (EditValueChanged macht das normalerweise schon LIVE)
|
||||
' *** 2. Cache-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)
|
||||
_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
|
||||
Dim oView As GridView = TryCast(sender, GridView)
|
||||
If oView Is Nothing OrElse e.Column Is Nothing Then Return
|
||||
@@ -1031,7 +1189,7 @@ CheckNewItemRow:
|
||||
Next
|
||||
|
||||
If oFormulaColumnsToRefresh.Count = 0 Then
|
||||
' Kein FORMULA_EXPRESSION-Refresh nötig – weiter zu FORMULA_SQL
|
||||
' Kein FORMULA_EXPRESSION-Refresh nötig
|
||||
Else
|
||||
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)
|
||||
@@ -1049,7 +1207,7 @@ CheckNewItemRow:
|
||||
_Logger.Debug("[FormulaRefresh] FALLBACK DisplayText for [{0}]: [{1}]",
|
||||
oFormulaColumnName, oView.GetRowCellDisplayText(oRowHandle, oGridColumn))
|
||||
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)
|
||||
Catch ex As Exception
|
||||
_Logger.Error(ex)
|
||||
@@ -1061,8 +1219,7 @@ CheckNewItemRow:
|
||||
_Logger.Error(ex)
|
||||
End Try
|
||||
|
||||
' *** FORMULA_SQL-Refresh via CellValueChanged ***
|
||||
' SQL wird NUR hier ausgeführt (nicht in EditValueChanged) um DB-Roundtrips zu minimieren
|
||||
' *** 4. FORMULA_SQL-Refresh via CellValueChanged ***
|
||||
Try
|
||||
Dim oView As GridView = TryCast(sender, GridView)
|
||||
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}]",
|
||||
e.Column.FieldName, String.Join(", ", oSqlColumnsToRefresh))
|
||||
|
||||
' BeginInvoke: UI nicht blockieren, GridView in stabilem Zustand
|
||||
' BeginInvoke: UI nicht blockieren
|
||||
oView.GridControl.BeginInvoke(New Action(
|
||||
Sub()
|
||||
Try
|
||||
@@ -1110,7 +1267,9 @@ CheckNewItemRow:
|
||||
|
||||
Try
|
||||
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
|
||||
Dim oResult = oResultTable.Rows(0).Item(0)
|
||||
@@ -1141,6 +1300,18 @@ CheckNewItemRow:
|
||||
Catch ex As Exception
|
||||
_Logger.Error(ex)
|
||||
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
|
||||
''' <summary>
|
||||
@@ -1382,6 +1553,11 @@ CheckNewItemRow:
|
||||
End If
|
||||
|
||||
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
|
||||
End If
|
||||
|
||||
@@ -1425,9 +1601,9 @@ CheckNewItemRow:
|
||||
affectedRowsCount)
|
||||
If USER_LANGUAGE <> "de-DE" Then
|
||||
confirmMessage = String.Format(
|
||||
"Do you want to inherit the value '{0}' to {1} subsequent row(s)?",
|
||||
valueToApply,
|
||||
affectedRowsCount)
|
||||
"Do you want to inherit the value '{0}' to {1} subsequent row(s)?",
|
||||
valueToApply,
|
||||
affectedRowsCount)
|
||||
End If
|
||||
Dim confirmTitle As String = "Wertevererbung bestätigen"
|
||||
If USER_LANGUAGE <> "de-DE" Then
|
||||
@@ -1483,9 +1659,14 @@ CheckNewItemRow:
|
||||
_Logger.Info("Skipping confirmation dialog (already confirmed {0} times)", confirmationEntry.Count)
|
||||
End If
|
||||
|
||||
' *** NEU: Cache-Keys sammeln WÄHREND der Vererbung ***
|
||||
Dim oCacheKeysToInvalidate As New List(Of String)
|
||||
|
||||
isApplyingInheritedValue = True
|
||||
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
|
||||
Dim targetHandle = pView.GetRowHandle(dataIndex)
|
||||
If targetHandle = DevExpress.XtraGrid.GridControl.InvalidRowHandle OrElse pView.IsGroupRow(targetHandle) Then
|
||||
@@ -1501,12 +1682,62 @@ CheckNewItemRow:
|
||||
Continue For
|
||||
End If
|
||||
|
||||
' *** SCHRITT 1: Wert setzen ***
|
||||
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
|
||||
|
||||
' *** 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
|
||||
isApplyingInheritedValue = False
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
Private Sub SetInheritanceConfirmationCount(columnName As String, newCount As Integer)
|
||||
Dim entries = UserInheritance_ConfirmationByColumn
|
||||
If entries Is Nothing Then
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user