Neue Features, Optimierungen und Bugfixes
- Neues Projekt `EDMI.API` zur Solution hinzugefügt. - `App.config`: Abhängigkeiten und `bindingRedirect` aktualisiert. - Verbesserte Thread-Sicherheit mit `ReaderWriterLockSlim`. - SQL-Abfragen mit `WITH (NOLOCK)` für bessere Parallelität. - Robuste Verarbeitung von `DataRow`-Werten mit `IIf(IsDBNull(...))`. - Neue Sicherheitsprüfung für `JobListener` initialisiert. - `SaveDataTables` optimiert, Performance-Warnungen hinzugefügt. - Versionsnummer auf `2.5.0.0` aktualisiert, Copyright auf 2026. - Paket `NLog` aus `packages.config` entfernt.
This commit is contained in:
@@ -74,6 +74,8 @@ Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "DBCleaner", "DBCleaner\DBCl
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "EDMIService.Tests", "EDMIService.Tests\EDMIService.Tests.vbproj", "{E3DA65CA-964D-4896-991A-B5426884E215}"
|
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "EDMIService.Tests", "EDMIService.Tests\EDMIService.Tests.vbproj", "{E3DA65CA-964D-4896-991A-B5426884E215}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "EDMI.API", "..\DDModules\EDMIAPI\EDMI.API.vbproj", "{25017513-0D97-49D3-98D7-BA76D9B251B0}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -180,6 +182,10 @@ Global
|
|||||||
{E3DA65CA-964D-4896-991A-B5426884E215}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{E3DA65CA-964D-4896-991A-B5426884E215}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{E3DA65CA-964D-4896-991A-B5426884E215}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{E3DA65CA-964D-4896-991A-B5426884E215}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{E3DA65CA-964D-4896-991A-B5426884E215}.Release|Any CPU.Build.0 = Release|Any CPU
|
{E3DA65CA-964D-4896-991A-B5426884E215}.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
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
</dependentAssembly>
|
</dependentAssembly>
|
||||||
<dependentAssembly>
|
<dependentAssembly>
|
||||||
<assemblyIdentity name="GdPicture.NET.14" publicKeyToken="f52a2e60ad468dbb" culture="neutral" />
|
<assemblyIdentity name="GdPicture.NET.14" publicKeyToken="f52a2e60ad468dbb" culture="neutral" />
|
||||||
<bindingRedirect oldVersion="0.0.0.0-14.2.90.0" newVersion="14.2.90.0" />
|
<bindingRedirect oldVersion="0.0.0.0-14.3.3.0" newVersion="14.3.3.0" />
|
||||||
</dependentAssembly>
|
</dependentAssembly>
|
||||||
<dependentAssembly>
|
<dependentAssembly>
|
||||||
<assemblyIdentity name="protobuf-net" publicKeyToken="257b51d87d2e4d67" culture="neutral" />
|
<assemblyIdentity name="protobuf-net" publicKeyToken="257b51d87d2e4d67" culture="neutral" />
|
||||||
@@ -75,6 +75,14 @@
|
|||||||
<assemblyIdentity name="System.Text.Json" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
<assemblyIdentity name="System.Text.Json" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||||
<bindingRedirect oldVersion="0.0.0.0-8.0.0.5" newVersion="8.0.0.5" />
|
<bindingRedirect oldVersion="0.0.0.0-8.0.0.5" newVersion="8.0.0.5" />
|
||||||
</dependentAssembly>
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="GdPicture.NET.14.Common" publicKeyToken="f52a2e60ad468dbb" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-14.3.3.0" newVersion="14.3.3.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="GdPicture.NET.14.Imaging" publicKeyToken="f52a2e60ad468dbb" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-14.3.3.0" newVersion="14.3.3.0" />
|
||||||
|
</dependentAssembly>
|
||||||
</assemblyBinding>
|
</assemblyBinding>
|
||||||
</runtime>
|
</runtime>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ Imports System.ServiceModel
|
|||||||
Imports System.ServiceModel.Description
|
Imports System.ServiceModel.Description
|
||||||
Imports DigitalData.Modules.Database
|
Imports DigitalData.Modules.Database
|
||||||
Imports DigitalData.Modules.Logging
|
Imports DigitalData.Modules.Logging
|
||||||
Imports DigitalData.Modules.Language
|
'Imports DigitalData.Modules.
|
||||||
Imports DigitalData.Modules
|
|
||||||
Imports DigitalData.Modules.EDMI
|
Imports DigitalData.Modules.EDMI
|
||||||
Imports DigitalData.Modules.EDMI.API.Rights
|
Imports DigitalData.Modules.EDMI.API.Rights
|
||||||
Imports DigitalData.Services.EDMIService.Messages
|
Imports DigitalData.Services.EDMIService.Messages
|
||||||
@@ -13,6 +12,7 @@ Imports DigitalData.Services.EDMIService.Methods
|
|||||||
Imports DigitalData.Services.EDMIService.Methods.Database
|
Imports DigitalData.Services.EDMIService.Methods.Database
|
||||||
Imports DigitalData.Services.EDMIService.Methods.IDB
|
Imports DigitalData.Services.EDMIService.Methods.IDB
|
||||||
|
|
||||||
|
|
||||||
<ServiceBehavior(InstanceContextMode:=InstanceContextMode.PerSession)>
|
<ServiceBehavior(InstanceContextMode:=InstanceContextMode.PerSession)>
|
||||||
Public Class EDMIService
|
Public Class EDMIService
|
||||||
Implements IEDMIService
|
Implements IEDMIService
|
||||||
@@ -22,7 +22,6 @@ Public Class EDMIService
|
|||||||
Public Shared MSSQL_IDB As MSSQLServer
|
Public Shared MSSQL_IDB As MSSQLServer
|
||||||
Public Shared Firebird As Firebird
|
Public Shared Firebird As Firebird
|
||||||
Public Shared AppConfig As Config
|
Public Shared AppConfig As Config
|
||||||
Public Shared Filesystem As Filesystem.File
|
|
||||||
Public Shared GlobalState As GlobalState
|
Public Shared GlobalState As GlobalState
|
||||||
Public Shared Scheduler As Scheduler
|
Public Shared Scheduler As Scheduler
|
||||||
|
|
||||||
@@ -586,7 +585,17 @@ Public Class EDMIService
|
|||||||
Dim oRow As DataRow = oRows.First()
|
Dim oRow As DataRow = oRows.First()
|
||||||
Dim oRightAsInt = oRow.Item("ACCESSRIGHT")
|
Dim oRightAsInt = oRow.Item("ACCESSRIGHT")
|
||||||
|
|
||||||
oRight = Utils.ToEnum(Of AccessRight)(oRightAsInt)
|
Select Case oRight
|
||||||
|
Case 1
|
||||||
|
oRight = AccessRight.VIEW_ONLY
|
||||||
|
Case 2
|
||||||
|
oRight = AccessRight.VIEW_EXPORT
|
||||||
|
Case 4
|
||||||
|
oRight = AccessRight.FULL
|
||||||
|
Case Else
|
||||||
|
_Logger.Warn("GetAccessRightForObjectId: Unknown access right [{0}] for user [{1}] on object [{2}]", oRightAsInt, UserId, ObjectId)
|
||||||
|
oRight = AccessRight.VIEW_ONLY
|
||||||
|
End Select
|
||||||
End If
|
End If
|
||||||
|
|
||||||
_Logger.Debug("Successfully parsed AccessRights for ObjectId [{0}]", ObjectId)
|
_Logger.Debug("Successfully parsed AccessRights for ObjectId [{0}]", ObjectId)
|
||||||
|
|||||||
@@ -79,7 +79,7 @@
|
|||||||
<HintPath>..\..\DDModules\EDMIAPI\bin\Debug\DigitalData.Modules.EDMI.API.dll</HintPath>
|
<HintPath>..\..\DDModules\EDMIAPI\bin\Debug\DigitalData.Modules.EDMI.API.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="DigitalData.Modules.Filesystem">
|
<Reference Include="DigitalData.Modules.Filesystem">
|
||||||
<HintPath>P:\Projekte DIGITAL DATA\DIGITAL DATA - Entwicklung\DLL_Bibliotheken\Digital Data\DigitalData.Modules.Filesystem.dll</HintPath>
|
<HintPath>..\..\DDModules\Filesystem\bin\Debug\DigitalData.Modules.Filesystem.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="DigitalData.Modules.Language">
|
<Reference Include="DigitalData.Modules.Language">
|
||||||
<HintPath>P:\Projekte DIGITAL DATA\DIGITAL DATA - Entwicklung\DLL_Bibliotheken\Digital Data\DigitalData.Modules.Language.dll</HintPath>
|
<HintPath>P:\Projekte DIGITAL DATA\DIGITAL DATA - Entwicklung\DLL_Bibliotheken\Digital Data\DigitalData.Modules.Language.dll</HintPath>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
Imports System.Data.SqlClient
|
Imports System.Data.SqlClient
|
||||||
Imports System.Runtime.Serialization
|
Imports System.Runtime.Serialization
|
||||||
Imports DigitalData.Modules.Database
|
Imports DigitalData.Modules.Database
|
||||||
Imports DigitalData.Modules.Language
|
|
||||||
Imports DigitalData.Modules.Logging
|
Imports DigitalData.Modules.Logging
|
||||||
|
|
||||||
Public Class GlobalState
|
Public Class GlobalState
|
||||||
@@ -13,8 +12,9 @@ Public Class GlobalState
|
|||||||
Public Property ObjectStores As New List(Of ObjectStore)
|
Public Property ObjectStores As New List(Of ObjectStore)
|
||||||
Public Property Connections As New List(Of DatabaseConnection)
|
Public Property Connections As New List(Of DatabaseConnection)
|
||||||
Public Property Doctypes As New List(Of Doctype)
|
Public Property Doctypes As New List(Of Doctype)
|
||||||
|
Public Property ClientConfig As ClientConfiguration = New ClientConfiguration()
|
||||||
|
|
||||||
Public Property ClientConfig As New ClientConfiguration
|
Public Property JobListener As JobListener
|
||||||
|
|
||||||
Public Property TableStore As New DataSet
|
Public Property TableStore As New DataSet
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ Public Class GlobalState
|
|||||||
Public Sub LoadConnections()
|
Public Sub LoadConnections()
|
||||||
_Logger.Info("Loading Database Connections")
|
_Logger.Info("Loading Database Connections")
|
||||||
Try
|
Try
|
||||||
Dim oSQL As String = "SELECT * FROM TBDD_CONNECTION"
|
Dim oSQL As String = "SELECT * FROM TBDD_CONNECTION WITH (NOLOCK)"
|
||||||
Dim oTable As DataTable = _MSSQL_ECM.GetDatatable(oSQL)
|
Dim oTable As DataTable = _MSSQL_ECM.GetDatatable(oSQL)
|
||||||
|
|
||||||
_Logger.Info("Found [{0}] Connections", oTable.Rows.Count)
|
_Logger.Info("Found [{0}] Connections", oTable.Rows.Count)
|
||||||
@@ -132,14 +132,14 @@ Public Class GlobalState
|
|||||||
|
|
||||||
For Each oRow As DataRow In oTable.Rows
|
For Each oRow As DataRow In oTable.Rows
|
||||||
Dim oConnection As New DatabaseConnection() With {
|
Dim oConnection As New DatabaseConnection() With {
|
||||||
.Id = oRow.ItemEx(Of Integer)("GUID"),
|
.Id = IIf(IsDBNull(oRow.Item("GUID")), 0, oRow.Item("GUID")),
|
||||||
.Active = oRow.ItemEx(Of Boolean)("AKTIV"),
|
.Active = IIf(IsDBNull(oRow.Item("AKTIV")), False, oRow.Item("AKTIV")),
|
||||||
.Database = oRow.ItemEx(Of String)("DATENBANK"),
|
.Database = IIf(IsDBNull(oRow.Item("DATENBANK")), String.Empty, oRow.Item("DATENBANK")),
|
||||||
.Title = oRow.ItemEx(Of String)("BEZEICHNUNG"),
|
.Title = IIf(IsDBNull(oRow.Item("BEZEICHNUNG")), String.Empty, oRow.Item("BEZEICHNUNG")),
|
||||||
.Password = oRow.ItemEx(Of String)("PASSWORD"),
|
.Password = IIf(IsDBNull(oRow.Item("PASSWORD")), String.Empty, oRow.Item("PASSWORD")),
|
||||||
.Provider = oRow.ItemEx(Of String)("SQL_PROVIDER"),
|
.Provider = IIf(IsDBNull(oRow.Item("SQL_PROVIDER")), String.Empty, oRow.Item("SQL_PROVIDER")),
|
||||||
.Server = oRow.ItemEx(Of String)("SERVER"),
|
.Server = IIf(IsDBNull(oRow.Item("SERVER")), String.Empty, oRow.Item("SERVER")),
|
||||||
.Username = oRow.ItemEx(Of String)("USERNAME")
|
.Username = IIf(IsDBNull(oRow.Item("USERNAME")), String.Empty, oRow.Item("USERNAME"))
|
||||||
}
|
}
|
||||||
_Logger.Info("New Connection [{0}]", oConnection.Title)
|
_Logger.Info("New Connection [{0}]", oConnection.Title)
|
||||||
oConnections.Add(oConnection)
|
oConnections.Add(oConnection)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
Imports DigitalData.Modules.Database
|
Imports DigitalData.Modules.Database
|
||||||
Imports DigitalData.Modules.Database.MSSQLServer
|
Imports DigitalData.Modules.Database.MSSQLServer
|
||||||
Imports DigitalData.Modules.Language
|
|
||||||
Imports DigitalData.Modules.Logging
|
Imports DigitalData.Modules.Logging
|
||||||
Imports System.Data.SqlClient
|
Imports System.Data.SqlClient
|
||||||
Imports DigitalData.Modules.ZooFlow.State
|
Imports DigitalData.Modules.ZooFlow.State
|
||||||
@@ -59,8 +58,8 @@ Namespace IDB
|
|||||||
End If
|
End If
|
||||||
|
|
||||||
Dim oRow As DataRow = oTable.Rows.Item(0)
|
Dim oRow As DataRow = oTable.Rows.Item(0)
|
||||||
Dim oActive = Utils.NotNull(oRow.Item("ACTIVE"), False)
|
Dim oActive = IIf(IsDBNull(oRow.Item("ACTIVE")), False, oRow.Item("ACTIVE"))
|
||||||
Dim oDeleted = Utils.NotNull(oRow.Item("DELETED"), False)
|
Dim oDeleted = IIf(IsDBNull(oRow.Item("DELETED")), False, oRow.Item("DELETED"))
|
||||||
|
|
||||||
IsDeleted = oDeleted
|
IsDeleted = oDeleted
|
||||||
IsActive = oActive
|
IsActive = oActive
|
||||||
|
|||||||
@@ -14,49 +14,60 @@ Namespace Methods.Database.GetDatatableFromCache
|
|||||||
Try
|
Try
|
||||||
Logger.Debug($"ReturnDatatableFromCache: Datatable: {pData.DataTable}")
|
Logger.Debug($"ReturnDatatableFromCache: Datatable: {pData.DataTable}")
|
||||||
|
|
||||||
Dim oDataTable As DataTable = Nothing
|
' ✅ NEU: Sicherheitsprüfung (sollte nie auftreten, aber defensiv)
|
||||||
|
If GlobalState.JobListener Is Nothing Then
|
||||||
|
Logger.Warn("JobListener not initialized - this should not happen! Using direct TableStore access.")
|
||||||
|
|
||||||
Logger.Debug("ReturnDatatableFromCache: DataSet contains [{0}] datatables", GlobalState.TableStore.Tables.Count)
|
' Fallback: Direkter Zugriff auf TableStore
|
||||||
|
If Not GlobalState.TableStore.Tables.Contains(pData.DataTable) Then
|
||||||
If GlobalState.TableStore.Tables.Contains(pData.DataTable) Then
|
Return New GetDatatableFromCacheResponse(
|
||||||
oDataTable = GlobalState.TableStore.Tables.Item(pData.DataTable).Copy()
|
New KeyNotFoundException($"Datatable {pData.DataTable} does not exist"))
|
||||||
|
|
||||||
' Apply filter and sorting to data
|
|
||||||
Dim oFilterExpression As String = Utils.NotNull(pData.FilterExpression, String.Empty)
|
|
||||||
Dim oSortByColumn As String = Utils.NotNull(pData.SortByColumn, String.Empty)
|
|
||||||
|
|
||||||
Logger.Debug("Applying filter expression: [{0}]", oFilterExpression)
|
|
||||||
Logger.Debug("Applying sort expression: [{0}]", oSortByColumn)
|
|
||||||
|
|
||||||
Dim oFilteredRows = oDataTable.Select(oFilterExpression, oSortByColumn)
|
|
||||||
Dim oFilteredTable As DataTable = Nothing
|
|
||||||
|
|
||||||
If oFilteredRows.Count > 0 Then
|
|
||||||
oFilteredTable = oFilteredRows.CopyToDataTable()
|
|
||||||
oFilteredTable.TableName = pData.DataTable
|
|
||||||
Else
|
|
||||||
' Produce empty table
|
|
||||||
oFilteredTable = oDataTable.Clone()
|
|
||||||
oFilteredTable.TableName = pData.DataTable
|
|
||||||
End If
|
End If
|
||||||
|
|
||||||
Logger.Debug("ReturnDatatableFromCache: Datatable Stats for [{0}]:", pData.DataTable)
|
Dim oDirectTable = GlobalState.TableStore.Tables(pData.DataTable).Copy()
|
||||||
Logger.Debug("Unfiltered: [{0}] rows", oDataTable.Rows.Count)
|
' ... Filter/Sort anwenden wie gehabt
|
||||||
Logger.Debug("Filtered: [{0}] rows", oFilteredTable.Rows.Count)
|
Return New GetDatatableFromCacheResponse(ApplyFilterAndSort(oDirectTable, pData))
|
||||||
|
|
||||||
Return New GetDatatableFromCacheResponse(oFilteredTable)
|
|
||||||
Else
|
|
||||||
Logger.Warn($"ReturnDatatableFromCache: Datatable {pData.DataTable} does not exist")
|
|
||||||
|
|
||||||
Return New GetDatatableFromCacheResponse(New KeyNotFoundException($"Datatable {pData.DataTable} does not exist"))
|
|
||||||
End If
|
End If
|
||||||
|
|
||||||
|
' ✅ Normal: Thread-sicheren Zugriff verwenden
|
||||||
|
Dim oDataTable As DataTable = GlobalState.JobListener.GetTableSafely(pData.DataTable)
|
||||||
|
|
||||||
|
If oDataTable Is Nothing Then
|
||||||
|
Logger.Warn($"Datatable {pData.DataTable} does not exist")
|
||||||
|
Return New GetDatatableFromCacheResponse(
|
||||||
|
New KeyNotFoundException($"Datatable {pData.DataTable} does not exist"))
|
||||||
|
End If
|
||||||
|
|
||||||
|
Return New GetDatatableFromCacheResponse(ApplyFilterAndSort(oDataTable, pData))
|
||||||
|
|
||||||
|
|
||||||
Catch ex As Exception
|
Catch ex As Exception
|
||||||
Logger.Error(ex)
|
Logger.Error(ex)
|
||||||
Return New GetDatatableFromCacheResponse(ex)
|
Return New GetDatatableFromCacheResponse(ex)
|
||||||
|
|
||||||
End Try
|
End Try
|
||||||
End Function
|
End Function
|
||||||
|
' ✅ Hilfsmethode zum Filter/Sort anwenden
|
||||||
|
Private Function ApplyFilterAndSort(table As DataTable, request As GetDatatableFromCacheRequest) As DataTable
|
||||||
|
Dim oFilterExpression = IIf(IsDBNull(request.FilterExpression), String.Empty, request.FilterExpression)
|
||||||
|
Dim oSortByColumn = IIf(IsDBNull(request.SortByColumn), String.Empty, request.SortByColumn)
|
||||||
|
|
||||||
|
Logger.Debug("Filter: [{0}], Sort: [{1}]", oFilterExpression, oSortByColumn)
|
||||||
|
|
||||||
|
Dim oFilteredRows = table.Select(oFilterExpression, oSortByColumn)
|
||||||
|
Dim oResult As DataTable
|
||||||
|
|
||||||
|
If oFilteredRows.Count > 0 Then
|
||||||
|
oResult = oFilteredRows.CopyToDataTable()
|
||||||
|
Else
|
||||||
|
oResult = table.Clone()
|
||||||
|
End If
|
||||||
|
|
||||||
|
oResult.TableName = request.DataTable
|
||||||
|
Logger.Debug("Rows: {0} → {1}", table.Rows.Count, oResult.Rows.Count)
|
||||||
|
|
||||||
|
Return oResult
|
||||||
|
End Function
|
||||||
End Class
|
End Class
|
||||||
|
|
||||||
End Namespace
|
End Namespace
|
||||||
@@ -37,13 +37,13 @@ Namespace Methods.GlobalIndexer
|
|||||||
|
|
||||||
Dim oRow As DataRow = oProfile.Table.Rows.Item(0)
|
Dim oRow As DataRow = oProfile.Table.Rows.Item(0)
|
||||||
Dim oProfileObject As New Profile With {
|
Dim oProfileObject As New Profile With {
|
||||||
.Id = oRow.ItemEx("DOCTYPE_ID", 0),
|
.Id = IIf(IsDBNull(oRow.Item("DOCTYPE_ID")), 0, oRow.Item("DOCTYPE_ID")),
|
||||||
.Name = oRow.ItemEx("DOCTYPE", "Missing Profile Name"),
|
.Name = IIf(IsDBNull(oRow.Item("DOCTYPE")), String.Empty, oRow.Item("DOCTYPE")),
|
||||||
.IsActive = oRow.ItemEx("AKTIV", False),
|
.IsActive = IIf(IsDBNull(oRow.Item("AKTIV")), False, oRow.Item("AKTIV")),
|
||||||
.NameConvention = oRow.ItemEx("NAMENKONVENTION", ""),
|
.NameConvention = IIf(IsDBNull(oRow.Item("NAMENKONVENTION")), String.Empty, oRow.Item("NAMENKONVENTION")),
|
||||||
.ObjectStore = oRow.ItemEx("OBJECT_STORE", "Work"),
|
.ObjectStore = IIf(IsDBNull(oRow.Item("OBJECT_STORE")), "Work", oRow.Item("OBJECT_STORE")),
|
||||||
.ShortName = oRow.ItemEx("KURZNAME", ""),
|
.ShortName = IIf(IsDBNull(oRow.Item("KURZNAME")), String.Empty, oRow.Item("KURZNAME")),
|
||||||
.DynamicPath = oRow.ItemEx("DYNAMIC_FOLDER", "")
|
.DynamicPath = IIf(IsDBNull(oRow.Item("DYNAMIC_FOLDER")), String.Empty, oRow.Item("DYNAMIC_FOLDER"))
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Debug("Name: [{0}]", oProfileObject.Name)
|
Logger.Debug("Name: [{0}]", oProfileObject.Name)
|
||||||
@@ -82,13 +82,13 @@ Namespace Methods.GlobalIndexer
|
|||||||
|
|
||||||
For Each oRow As DataRow In oAutomaticIndexes.Table.Rows
|
For Each oRow As DataRow In oAutomaticIndexes.Table.Rows
|
||||||
Dim oAutomaticIndex As New AutomaticIndex With {
|
Dim oAutomaticIndex As New AutomaticIndex With {
|
||||||
.Id = oRow.ItemEx(Of Integer)("GUID"),
|
.Id = oRow.Item("GUID"),
|
||||||
.Name = oRow.ItemEx(Of String)("INDEXNAME"),
|
.Name = IIf(IsDBNull(oRow.Item("INDEXNAME")), String.Empty, oRow.Item("INDEXNAME")),
|
||||||
.ProfileId = oRow.ItemEx(Of Integer)("DOCTYPE_ID"),
|
.ProfileId = IIf(IsDBNull(oRow.Item("DOCTYPE_ID")), 0, oRow.Item("DOCTYPE_ID")),
|
||||||
.SQLCommand = oRow.ItemEx(Of String)("SQL_RESULT"),
|
.SQLCommand = IIf(IsDBNull(oRow.Item("SQL_RESULT")), String.Empty, oRow.Item("SQL_RESULT")),
|
||||||
.SQLConnectionId = oRow.ItemEx(Of Integer)("CONNECTION_ID"),
|
.SQLConnectionId = IIf(IsDBNull(oRow.Item("CONNECTION_ID")), 0, oRow.Item("CONNECTION_ID")),
|
||||||
.Sequence = oRow.ItemEx(Of String)("SEQUENCE"),
|
.Sequence = IIf(IsDBNull(oRow.Item("SEQUENCE")), String.Empty, oRow.Item("SEQUENCE")),
|
||||||
.Value = oRow.ItemEx(Of String)("VALUE")
|
.Value = IIf(IsDBNull(oRow.Item("VALUE")), String.Empty, oRow.Item("VALUE"))
|
||||||
}
|
}
|
||||||
|
|
||||||
oIndexes.Add(oAutomaticIndex)
|
oIndexes.Add(oAutomaticIndex)
|
||||||
@@ -125,15 +125,15 @@ Namespace Methods.GlobalIndexer
|
|||||||
|
|
||||||
For Each oRow As DataRow In oManualIndexes.Table.Rows
|
For Each oRow As DataRow In oManualIndexes.Table.Rows
|
||||||
Dim oManualIndex As New ManualIndex With {
|
Dim oManualIndex As New ManualIndex With {
|
||||||
.Id = oRow.ItemEx(Of Integer)("GUID"),
|
.Id = oRow.Item("GUID"),
|
||||||
.Name = oRow.ItemEx(Of String)("INDEXNAME"),
|
.Name = oRow.Item("INDEXNAME"),
|
||||||
.ProfileId = oRow.ItemEx(Of Integer)("DOK_ID"),
|
.ProfileId = oRow.Item("DOK_ID"),
|
||||||
.IsOptional = oRow.ItemEx(Of Boolean)("OPTIONAL"),
|
.IsOptional = oRow.Item("OPTIONAL"),
|
||||||
.IsMultiselect = oRow.ItemEx(Of String)("MULTISELECT"),
|
.IsMultiselect = oRow.Item("MULTISELECT"),
|
||||||
.SQLCommand = oRow.ItemEx(Of String)("SQL_RESULT"),
|
.SQLCommand = oRow.Item("SQL_RESULT"),
|
||||||
.SQLConnectionId = oRow.ItemEx(Of Integer)("CONNECTION_ID"),
|
.SQLConnectionId = oRow.Item("CONNECTION_ID"),
|
||||||
.DefaultValue = oRow.ItemEx(Of String)("DEFAULT_VALUE"),
|
.DefaultValue = oRow.Item("DEFAULT_VALUE"),
|
||||||
.DataType = oRow.ItemEx(Of String)("DATATYPE")
|
.DataType = oRow.Item("DATATYPE")
|
||||||
}
|
}
|
||||||
|
|
||||||
oIndexes.Add(oManualIndex)
|
oIndexes.Add(oManualIndex)
|
||||||
@@ -179,16 +179,16 @@ Namespace Methods.GlobalIndexer
|
|||||||
|
|
||||||
For Each oRow As DataRow In oPostProcessingSteps.Table.Rows
|
For Each oRow As DataRow In oPostProcessingSteps.Table.Rows
|
||||||
Dim oStep As New PostProcessingStep With {
|
Dim oStep As New PostProcessingStep With {
|
||||||
.Id = oRow.ItemEx(Of Integer)("GUID"),
|
.Id = oRow.Item("GUID"),
|
||||||
.IndexId = oRow.ItemEx(Of Integer)("IDXMAN_ID"),
|
.IndexId = oRow.Item("IDXMAN_ID"),
|
||||||
.[Variant] = oRow.ItemEx(Of String)("VARIANT"),
|
.[Variant] = oRow.Item("VARIANT"),
|
||||||
.Type = oRow.ItemEx(Of String)("TYPE"),
|
.Type = oRow.Item("TYPE"),
|
||||||
.Function1 = oRow.ItemEx(Of String)("FUNCTION1"),
|
.Function1 = oRow.Item("FUNCTION1"),
|
||||||
.Function2 = oRow.ItemEx(Of String)("FUNCTION2"),
|
.Function2 = oRow.Item("FUNCTION2"),
|
||||||
.Text1 = oRow.ItemEx(Of String)("Text1"),
|
.Text1 = oRow.Item("Text1"),
|
||||||
.Text2 = oRow.ItemEx(Of String)("Text2"),
|
.Text2 = oRow.Item("Text2"),
|
||||||
.Text3 = oRow.ItemEx(Of String)("Text3"),
|
.Text3 = oRow.Item("Text3"),
|
||||||
.Sequence = oRow.ItemEx(Of Integer)("SEQUENCE")
|
.Sequence = oRow.Item("SEQUENCE")
|
||||||
}
|
}
|
||||||
|
|
||||||
oSteps.Add(oStep)
|
oSteps.Add(oStep)
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ Namespace Methods.IDB.GetFileObject
|
|||||||
LogAndThrow("Error while getting FileObject data!")
|
LogAndThrow("Error while getting FileObject data!")
|
||||||
End If
|
End If
|
||||||
|
|
||||||
Dim oRow As DataRow = oTable.First()
|
Dim oRow As DataRow = oTable.Rows(0)
|
||||||
Dim oFileHash As String = oRow.ItemEx("FILE_HASH", "")
|
Dim oFileHash As String = oRow.Item("FILE_HASH")
|
||||||
Dim oFileSize As Long = oRow.ItemEx(Of Long)("FILE_SIZE", 0)
|
Dim oFileSize As Long = oRow.Item("FILE_SIZE")
|
||||||
Dim oFileExtension As String = oRow.ItemEx(Of String)("EXTENSION")
|
Dim oFileExtension As String = oRow.Item("EXTENSION")
|
||||||
Dim oFilePath = oRow.ItemEx("RELPATH", "")
|
Dim oFilePath = oRow.Item("RELPATH")
|
||||||
Dim oFileName = oRow.ItemEx("Filename", "")
|
Dim oFileName = oRow.Item("Filename")
|
||||||
Dim oFullFileName = Path.Combine(oFilePath, oFileName)
|
Dim oFullFileName = Path.Combine(oFilePath, oFileName)
|
||||||
|
|
||||||
Dim oFileObject As New FileObject With {
|
Dim oFileObject As New FileObject With {
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ Imports System.Runtime.InteropServices
|
|||||||
<Assembly: AssemblyDescription("")>
|
<Assembly: AssemblyDescription("")>
|
||||||
<Assembly: AssemblyCompany("Digital Data")>
|
<Assembly: AssemblyCompany("Digital Data")>
|
||||||
<Assembly: AssemblyProduct("EDMIService")>
|
<Assembly: AssemblyProduct("EDMIService")>
|
||||||
<Assembly: AssemblyCopyright("Copyright © 2022")>
|
<Assembly: AssemblyCopyright("2026")>
|
||||||
<Assembly: AssemblyTrademark("2.4.7.0")>
|
<Assembly: AssemblyTrademark("2.5.0")>
|
||||||
|
|
||||||
<Assembly: ComVisible(False)>
|
<Assembly: ComVisible(False)>
|
||||||
|
|
||||||
@@ -31,5 +31,5 @@ Imports System.Runtime.InteropServices
|
|||||||
' übernehmen, indem Sie "*" eingeben:
|
' übernehmen, indem Sie "*" eingeben:
|
||||||
' <Assembly: AssemblyVersion("1.0.*")>
|
' <Assembly: AssemblyVersion("1.0.*")>
|
||||||
|
|
||||||
<Assembly: AssemblyVersion("2.4.7.0")>
|
<Assembly: AssemblyVersion("2.5.0.0")>
|
||||||
<Assembly: AssemblyFileVersion("2.4.7.0")>
|
<Assembly: AssemblyFileVersion("2.5.0.0")>
|
||||||
|
|||||||
@@ -29,17 +29,17 @@ Public Class DatatableJob
|
|||||||
|
|
||||||
Try
|
Try
|
||||||
Dim oMSSQL As MSSQLServer = oJobData.Item("MSSQL")
|
Dim oMSSQL As MSSQLServer = oJobData.Item("MSSQL")
|
||||||
Dim oCronSQL As String = $"SELECT * FROM TBAPPSERV_CRON_DETAIL WHERE CRON_ID = {oCronJobId}"
|
Dim oCronSQL As String = $"SELECT * FROM TBAPPSERV_CRON_DETAIL WITH (NOLOCK) WHERE CRON_ID = {oCronJobId}"
|
||||||
Dim oCronDetails As DataTable = oMSSQL.GetDatatable(oCronSQL)
|
Dim oCronDetails As DataTable = oMSSQL.GetDatatable(oCronSQL)
|
||||||
|
|
||||||
For Each oRow As DataRow In oCronDetails.Rows
|
For Each oRow As DataRow In oCronDetails.Rows
|
||||||
Dim oDatatableName As String = NotNull(oRow.Item("DT_NAME"), String.Empty)
|
Dim oDatatableName As String = IIf(IsDBNull(oRow.Item("DT_NAME")), String.Empty, oRow.Item("DT_NAME"))
|
||||||
Dim oParentColumn As String = NotNull(oRow.Item("DT_COLUMN"), String.Empty)
|
Dim oParentColumn As String = IIf(IsDBNull(oRow.Item("DT_COLUMN")), String.Empty, oRow.Item("DT_COLUMN"))
|
||||||
Dim oChildColumn As String = NotNull(oRow.Item("CHILD_DT_COLUMN"), String.Empty)
|
Dim oChildColumn As String = IIf(IsDBNull(oRow.Item("CHILD_DT_COLUMN")), String.Empty, oRow.Item("CHILD_DT_COLUMN"))
|
||||||
Dim oConnectionId As Integer = NotNull(oRow.Item("CON_ID"), String.Empty)
|
Dim oConnectionId As Integer = IIf(IsDBNull(oRow.Item("CON_ID")), 0, oRow.Item("CON_ID"))
|
||||||
Dim oTitle As String = NotNull(oRow.Item("TITLE"), String.Empty)
|
Dim oTitle As String = IIf(IsDBNull(oRow.Item("TITLE")), String.Empty, oRow.Item("TITLE"))
|
||||||
Dim oSQL As String = NotNull(oRow.Item("COMMAND"), String.Empty)
|
Dim oSQL As String = IIf(IsDBNull(oRow.Item("COMMAND")), String.Empty, oRow.Item("COMMAND"))
|
||||||
Dim oIndexColumns As String = NotNull(oRow.Item("INDEX_COLUMNS"), String.Empty)
|
Dim oIndexColumns As String = IIf(IsDBNull(oRow.Item("INDEX_COLUMNS")), String.Empty, oRow.Item("INDEX_COLUMNS"))
|
||||||
|
|
||||||
Try
|
Try
|
||||||
oLogger.Debug("Running Command-Job [{0}]", oTitle)
|
oLogger.Debug("Running Command-Job [{0}]", oTitle)
|
||||||
@@ -80,7 +80,7 @@ Public Class DatatableJob
|
|||||||
.DetailRow = oRow
|
.DetailRow = oRow
|
||||||
}
|
}
|
||||||
|
|
||||||
Dim oChildTableNAme As String = NotNull(oRow.Item("CHILD_DT_NAME"), String.Empty)
|
Dim oChildTableNAme As String = IIf(IsDBNull(oRow.Item("CHILD_DT_NAME")), String.Empty, oRow.Item("CHILD_DT_NAME"))
|
||||||
|
|
||||||
If oChildTableNAme <> String.Empty Then
|
If oChildTableNAme <> String.Empty Then
|
||||||
oLogger.Debug("Child Datatable [{0}] defined, Relation: Parent [{1}] -> Child [{2}]", oChildTableNAme, oParentColumn, oChildColumn)
|
oLogger.Debug("Child Datatable [{0}] defined, Relation: Parent [{1}] -> Child [{2}]", oChildTableNAme, oParentColumn, oChildColumn)
|
||||||
|
|||||||
@@ -11,7 +11,10 @@ Public Class JobListener
|
|||||||
Inherits JobListenerSupport
|
Inherits JobListenerSupport
|
||||||
|
|
||||||
Public Overrides ReadOnly Property Name As String = "JobListener"
|
Public Overrides ReadOnly Property Name As String = "JobListener"
|
||||||
Public Property Dataset As DataSet
|
|
||||||
|
' ✅ Verwaltung mit ReaderWriterLockSlim statt Shadow-Copy
|
||||||
|
Private ReadOnly _Dataset As DataSet
|
||||||
|
Private Shared ReadOnly _DatasetLock As New ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion)
|
||||||
|
|
||||||
Private ReadOnly _Logger As Logger
|
Private ReadOnly _Logger As Logger
|
||||||
Private ReadOnly _LogConfig As LogConfig
|
Private ReadOnly _LogConfig As LogConfig
|
||||||
@@ -23,9 +26,44 @@ Public Class JobListener
|
|||||||
_LogConfig = LogConfig
|
_LogConfig = LogConfig
|
||||||
_Logger = LogConfig.GetLogger()
|
_Logger = LogConfig.GetLogger()
|
||||||
_MSSQL = MSSQL
|
_MSSQL = MSSQL
|
||||||
Dataset = ResultDataSet
|
_Dataset = ResultDataSet
|
||||||
|
|
||||||
|
_Logger.Debug("JobListener initialized with ReaderWriterLockSlim for thread-safe access")
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
|
' ✅ Thread-sichere Methode zum Abrufen einer Tabelle
|
||||||
|
Public Function GetTableSafely(tableName As String) As DataTable
|
||||||
|
Try
|
||||||
|
_DatasetLock.EnterReadLock()
|
||||||
|
|
||||||
|
If _Dataset.Tables.Contains(tableName) Then
|
||||||
|
' Kopie nur von der einzelnen Tabelle, nicht vom ganzen Dataset!
|
||||||
|
Return _Dataset.Tables(tableName).Copy()
|
||||||
|
Else
|
||||||
|
Return Nothing
|
||||||
|
End If
|
||||||
|
|
||||||
|
Finally
|
||||||
|
_DatasetLock.ExitReadLock()
|
||||||
|
End Try
|
||||||
|
End Function
|
||||||
|
|
||||||
|
' ✅ Thread-sichere Methode zum Prüfen ob Tabelle existiert
|
||||||
|
Public Function TableExists(tableName As String) As Boolean
|
||||||
|
Try
|
||||||
|
_DatasetLock.EnterReadLock()
|
||||||
|
Return _Dataset.Tables.Contains(tableName)
|
||||||
|
Finally
|
||||||
|
_DatasetLock.ExitReadLock()
|
||||||
|
End Try
|
||||||
|
End Function
|
||||||
|
|
||||||
|
' ✅ Thread-sicherer Zugriff auf Dataset (nur für GetCachedTables)
|
||||||
|
Public Function GetCurrentDataset() As DataSet
|
||||||
|
' Keine Kopie - nur für Metadaten-Zugriff (Tabellennamen)
|
||||||
|
Return _Dataset
|
||||||
|
End Function
|
||||||
|
|
||||||
Public Overrides Function JobWasExecuted(context As IJobExecutionContext, jobException As JobExecutionException, Optional cancellationToken As CancellationToken = Nothing) As Task
|
Public Overrides Function JobWasExecuted(context As IJobExecutionContext, jobException As JobExecutionException, Optional cancellationToken As CancellationToken = Nothing) As Task
|
||||||
_Logger.Info("Job [{0}] was executed successfully. Saving Data.", context.JobDetail.Key)
|
_Logger.Info("Job [{0}] was executed successfully. Saving Data.", context.JobDetail.Key)
|
||||||
|
|
||||||
@@ -33,10 +71,11 @@ Public Class JobListener
|
|||||||
|
|
||||||
For Each oTableResult As JobResult.ResultTable In oResult.Tables
|
For Each oTableResult As JobResult.ResultTable In oResult.Tables
|
||||||
Try
|
Try
|
||||||
_Logger.Debug("Saving Datatable [{0}]", oTableResult.Table)
|
_Logger.Debug("Saving Datatable [{0}]", oTableResult.Table.TableName)
|
||||||
SaveDataTables(oTableResult)
|
SaveDataTables(oTableResult)
|
||||||
|
|
||||||
Catch ex As Exception
|
Catch ex As Exception
|
||||||
|
_Logger.Error(ex)
|
||||||
_Logger.Error("Error while executing SaveDataTables for {0}", oTableResult.Table.TableName)
|
_Logger.Error("Error while executing SaveDataTables for {0}", oTableResult.Table.TableName)
|
||||||
End Try
|
End Try
|
||||||
Next
|
Next
|
||||||
@@ -44,6 +83,86 @@ Public Class JobListener
|
|||||||
Return MyBase.JobWasExecuted(context, jobException, cancellationToken)
|
Return MyBase.JobWasExecuted(context, jobException, cancellationToken)
|
||||||
End Function
|
End Function
|
||||||
|
|
||||||
|
' ✅ OPTIMIERT: SaveDataTables mit direktem Write-Lock
|
||||||
|
Public Sub SaveDataTables(Result As JobResult.ResultTable)
|
||||||
|
Dim oStopWatch As New Stopwatch()
|
||||||
|
|
||||||
|
Try
|
||||||
|
Dim oTable As DataTable = Result.Table
|
||||||
|
Dim oName As String = Result.DetailRow.Item("DT_NAME")
|
||||||
|
Dim oDetailId As Integer = Result.DetailRow.Item("GUID")
|
||||||
|
|
||||||
|
_Logger.Debug("Starting thread-safe update for table [{0}]", oName)
|
||||||
|
|
||||||
|
' ✅ SCHRITT 1: Write-Lock anfordern (blockiert Leser während Update)
|
||||||
|
oStopWatch.Start()
|
||||||
|
_DatasetLock.EnterWriteLock()
|
||||||
|
_Logger.Debug("[Lock] Write-Lock acquired for table [{0}]", oName)
|
||||||
|
|
||||||
|
Try
|
||||||
|
' ✅ SCHRITT 2: Direkte Änderungen im Dataset
|
||||||
|
If _Dataset.Tables.Contains(oName) Then
|
||||||
|
_Logger.Debug("Datatable [{0}] already exists, replacing.", oName)
|
||||||
|
|
||||||
|
If Result.ChildTable IsNot Nothing Then
|
||||||
|
ReplaceExistingTable(oName, oTable, _Dataset, Result.ChildTable)
|
||||||
|
AddRelation(oName, Result.TableRelationColumn, Result.ChildTable.TableName, Result.ChildRelationColumn, _Dataset)
|
||||||
|
Else
|
||||||
|
ReplaceExistingTable(oName, oTable, _Dataset)
|
||||||
|
End If
|
||||||
|
Else
|
||||||
|
_Logger.Debug("Datatable [{0}] does not exist yet, adding.", oName)
|
||||||
|
|
||||||
|
AddNewTable(oName, oTable, _Dataset)
|
||||||
|
|
||||||
|
If Result.ChildTable IsNot Nothing Then
|
||||||
|
AddNewTable(Result.ChildTable.TableName, Result.ChildTable, _Dataset)
|
||||||
|
AddRelation(oName, Result.TableRelationColumn, Result.ChildTable.TableName, Result.ChildRelationColumn, _Dataset)
|
||||||
|
End If
|
||||||
|
End If
|
||||||
|
|
||||||
|
_Logger.Info("Dataset successfully updated with table [{0}]", oName)
|
||||||
|
|
||||||
|
Finally
|
||||||
|
' ✅ SCHRITT 3: Write-Lock freigeben (so schnell wie möglich!)
|
||||||
|
_DatasetLock.ExitWriteLock()
|
||||||
|
oStopWatch.Stop()
|
||||||
|
' ✅ Performance-Warning bei langsamen Updates
|
||||||
|
If oStopWatch.ElapsedMilliseconds > 500 Then
|
||||||
|
_Logger.Warn("[Performance] Table update took {0}ms (> 500ms threshold)", oStopWatch.ElapsedMilliseconds)
|
||||||
|
Else
|
||||||
|
_Logger.Debug("[Performance] Table update completed in {0}ms", oStopWatch.ElapsedMilliseconds)
|
||||||
|
End If
|
||||||
|
End Try
|
||||||
|
|
||||||
|
' ✅ SCHRITT 4: Speichern in Historie (außerhalb des Locks)
|
||||||
|
Dim oMemoryUsage = GC.GetTotalMemory(True)
|
||||||
|
_Logger.Debug("Estimated memory usage after Job is [{0}]", FormatBytes(oMemoryUsage))
|
||||||
|
|
||||||
|
_MSSQL.ExecuteNonQuery($"INSERT INTO TBAPPSERV_CRON_DETAIL_HISTORY (DETAIL_ID) VALUES ({oDetailId})")
|
||||||
|
|
||||||
|
Catch ex As Exception
|
||||||
|
_Logger.Warn("Unexpected error in SaveDataTables: {0}", ex.Message)
|
||||||
|
_Logger.Error(ex)
|
||||||
|
End Try
|
||||||
|
End Sub
|
||||||
|
Private Shared Function FormatBytes(bytes As Long) As String
|
||||||
|
Const KB As Long = 1024
|
||||||
|
Const MB As Long = KB * 1024
|
||||||
|
Const GB As Long = MB * 1024
|
||||||
|
|
||||||
|
Select Case bytes
|
||||||
|
Case >= GB
|
||||||
|
Return $"{bytes / GB:N2} GB"
|
||||||
|
Case >= MB
|
||||||
|
Return $"{bytes / MB:N2} MB"
|
||||||
|
Case >= KB
|
||||||
|
Return $"{bytes / KB:N2} KB"
|
||||||
|
Case Else
|
||||||
|
Return $"{bytes} Bytes"
|
||||||
|
End Select
|
||||||
|
End Function
|
||||||
|
|
||||||
Public Sub ReplaceExistingTable(Name As String, Table As DataTable, DataSet As DataSet, Optional ChildTable As DataTable = Nothing)
|
Public Sub ReplaceExistingTable(Name As String, Table As DataTable, DataSet As DataSet, Optional ChildTable As DataTable = Nothing)
|
||||||
Try
|
Try
|
||||||
Dim oDatatableNameTemp As String = Name & "-TEMP"
|
Dim oDatatableNameTemp As String = Name & "-TEMP"
|
||||||
@@ -103,13 +222,12 @@ Public Class JobListener
|
|||||||
DataSet.Tables.Item(oDatatableNameTemp).TableName = Name
|
DataSet.Tables.Item(oDatatableNameTemp).TableName = Name
|
||||||
Catch ex As Exception
|
Catch ex As Exception
|
||||||
_Logger.Error(ex)
|
_Logger.Error(ex)
|
||||||
Throw ex
|
Throw
|
||||||
End Try
|
End Try
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
Public Sub AddNewTable(Name As String, Table As DataTable, DataSet As DataSet)
|
Public Sub AddNewTable(Name As String, Table As DataTable, DataSet As DataSet)
|
||||||
_Logger.Debug("DataTable [{0}] does not exist, adding to DataSet", Name)
|
_Logger.Debug("DataTable [{0}] does not exist, adding to DataSet", Name)
|
||||||
|
|
||||||
DataSet.Tables.Add(Table)
|
DataSet.Tables.Add(Table)
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
@@ -117,9 +235,9 @@ Public Class JobListener
|
|||||||
Return $"{ParentTableName}-{ChildTableName}"
|
Return $"{ParentTableName}-{ChildTableName}"
|
||||||
End Function
|
End Function
|
||||||
|
|
||||||
Public Sub AddRelation(ParentTableName As String, ParentColumnName As String, ChildTableName As String, ChildColumnName As String)
|
Public Sub AddRelation(ParentTableName As String, ParentColumnName As String, ChildTableName As String, ChildColumnName As String, TargetDataSet As DataSet)
|
||||||
Dim oChild As DataTable = Dataset.Tables.Item(ChildTableName)
|
Dim oChild As DataTable = TargetDataSet.Tables.Item(ChildTableName)
|
||||||
Dim oParent As DataTable = Dataset.Tables.Item(ParentTableName)
|
Dim oParent As DataTable = TargetDataSet.Tables.Item(ParentTableName)
|
||||||
|
|
||||||
Dim oRelationName As String = GetRelationName(ParentTableName, ChildTableName)
|
Dim oRelationName As String = GetRelationName(ParentTableName, ChildTableName)
|
||||||
Dim oParentColumn As DataColumn = oParent.Columns.Item(ParentColumnName)
|
Dim oParentColumn As DataColumn = oParent.Columns.Item(ParentColumnName)
|
||||||
@@ -128,12 +246,13 @@ Public Class JobListener
|
|||||||
_Logger.Debug("New Relation: {0}/{1} => {2}/{3}", ParentTableName, ParentColumnName, ChildTableName, ChildColumnName)
|
_Logger.Debug("New Relation: {0}/{1} => {2}/{3}", ParentTableName, ParentColumnName, ChildTableName, ChildColumnName)
|
||||||
Try
|
Try
|
||||||
_Logger.Debug("Creating Relation [{0}]", oRelationName)
|
_Logger.Debug("Creating Relation [{0}]", oRelationName)
|
||||||
_Logger.Debug("ParentColumn exists: {0}", IsNothing(oParentColumn))
|
_Logger.Debug("ParentColumn exists: {0}", Not IsNothing(oParentColumn))
|
||||||
_Logger.Debug("ChildColumn exists: {0}", IsNothing(oChildColumn))
|
_Logger.Debug("ChildColumn exists: {0}", Not IsNothing(oChildColumn))
|
||||||
|
|
||||||
Dim oRelation As New DataRelation(oRelationName, oParentColumn, oChildColumn)
|
Dim oRelation As New DataRelation(oRelationName, oParentColumn, oChildColumn)
|
||||||
|
|
||||||
_Logger.Debug("Adding Relation [{0}]", oRelationName)
|
_Logger.Debug("Adding Relation [{0}]", oRelationName)
|
||||||
Dataset.Relations.Add(oRelation)
|
TargetDataSet.Relations.Add(oRelation)
|
||||||
|
|
||||||
Catch ex As ArgumentNullException
|
Catch ex As ArgumentNullException
|
||||||
_Logger.Error(ex)
|
_Logger.Error(ex)
|
||||||
@@ -145,7 +264,7 @@ Public Class JobListener
|
|||||||
|
|
||||||
Catch ex As InvalidConstraintException
|
Catch ex As InvalidConstraintException
|
||||||
_Logger.Error(ex)
|
_Logger.Error(ex)
|
||||||
_Logger.Warn("DuplicateNameException in AddRelation: {0}", ex.Message)
|
_Logger.Warn("InvalidConstraintException in AddRelation: {0}", ex.Message)
|
||||||
|
|
||||||
Catch ex As ArgumentException
|
Catch ex As ArgumentException
|
||||||
_Logger.Error(ex)
|
_Logger.Error(ex)
|
||||||
@@ -158,55 +277,6 @@ Public Class JobListener
|
|||||||
End Try
|
End Try
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
Public Sub SaveDataTables(Result As JobResult.ResultTable)
|
|
||||||
Try
|
|
||||||
Dim oTable As DataTable = Result.Table
|
|
||||||
|
|
||||||
Dim oName As String = Result.DetailRow.Item("DT_NAME")
|
|
||||||
Dim oDetailId As Integer = Result.DetailRow.Item("GUID")
|
|
||||||
|
|
||||||
' Used for debugging relations and constraints
|
|
||||||
'_Logger.Debug("Dataset BEFORE saving datatables")
|
|
||||||
'ListTables(Dataset)
|
|
||||||
|
|
||||||
If Dataset.Tables.Contains(oName) Then
|
|
||||||
_Logger.Debug("Datatable [{0}] already exists, replacing.", oName)
|
|
||||||
|
|
||||||
' Replace existing table
|
|
||||||
If Result.ChildTable IsNot Nothing Then
|
|
||||||
ReplaceExistingTable(oName, oTable, Dataset, Result.ChildTable)
|
|
||||||
AddRelation(oName, Result.TableRelationColumn, Result.ChildTable.TableName, Result.ChildRelationColumn)
|
|
||||||
Else
|
|
||||||
ReplaceExistingTable(oName, oTable, Dataset)
|
|
||||||
End If
|
|
||||||
Else
|
|
||||||
_Logger.Debug("Datatable [{0}] does not exist yet, adding.", oName)
|
|
||||||
|
|
||||||
AddNewTable(oName, oTable, Dataset)
|
|
||||||
|
|
||||||
If Result.ChildTable IsNot Nothing Then
|
|
||||||
AddNewTable(Result.ChildTable.TableName, Result.ChildTable, Dataset)
|
|
||||||
|
|
||||||
AddRelation(oName, Result.TableRelationColumn, Result.ChildTable.TableName, Result.ChildRelationColumn)
|
|
||||||
End If
|
|
||||||
End If
|
|
||||||
|
|
||||||
' Used for debugging relations and constraints
|
|
||||||
'_Logger.Debug("Dataset AFTER saving datatables")
|
|
||||||
'ListTables(Dataset)
|
|
||||||
|
|
||||||
Dim oMemoryUsage = GC.GetTotalMemory(True)
|
|
||||||
Dim oUsageString = Utils.BytesToString(oMemoryUsage)
|
|
||||||
|
|
||||||
_Logger.Debug("Estimated memory usage after Job is [{0}]", oUsageString)
|
|
||||||
|
|
||||||
_MSSQL.ExecuteNonQuery($"INSERT INTO TBAPPSERV_CRON_DETAIL_HISTORY (DETAIL_ID) VALUES ({oDetailId})")
|
|
||||||
Catch ex As Exception
|
|
||||||
_Logger.Warn("Unexpected error in SaveDataTables: {0}", ex.Message)
|
|
||||||
_Logger.Error(ex)
|
|
||||||
End Try
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
Private Sub ListTables(dataset As DataSet)
|
Private Sub ListTables(dataset As DataSet)
|
||||||
Dim oIndex As Integer = 1
|
Dim oIndex As Integer = 1
|
||||||
|
|
||||||
@@ -226,4 +296,10 @@ Public Class JobListener
|
|||||||
oIndex += 1
|
oIndex += 1
|
||||||
Next
|
Next
|
||||||
End Sub
|
End Sub
|
||||||
End Class
|
|
||||||
|
' ✅ Cleanup beim Dispose
|
||||||
|
Protected Overrides Sub Finalize()
|
||||||
|
_DatasetLock?.Dispose()
|
||||||
|
MyBase.Finalize()
|
||||||
|
End Sub
|
||||||
|
End Class
|
||||||
@@ -13,20 +13,25 @@ Public Class Scheduler
|
|||||||
Private ReadOnly _MSSQL As MSSQLServer
|
Private ReadOnly _MSSQL As MSSQLServer
|
||||||
Private _Scheduler As IScheduler
|
Private _Scheduler As IScheduler
|
||||||
Private ReadOnly _JobListener As JobListener
|
Private ReadOnly _JobListener As JobListener
|
||||||
|
Private ReadOnly _GlobalState As GlobalState ' ✅ NEU
|
||||||
|
|
||||||
Private ReadOnly _Props = New NameValueCollection From {
|
Private ReadOnly _Props = New NameValueCollection From {
|
||||||
{"quartz.serializer.type", "binary"},
|
{"quartz.serializer.type", "binary"},
|
||||||
{"quartz.threadPool.maxConcurrency", 1}
|
{"quartz.threadPool.maxConcurrency", 3}
|
||||||
}
|
}
|
||||||
|
|
||||||
Private Const JOB_GROUP As String = "DatatableJobs"
|
Private Const JOB_GROUP As String = "DatatableJobs"
|
||||||
|
|
||||||
Public Sub New(pLogConfig As LogConfig, pMSSQL_Config As MSSQLServer, pTableStore As DataSet)
|
Public Sub New(pLogConfig As LogConfig, pMSSQL_Config As MSSQLServer, pTableStore As DataSet, pGlobalState As GlobalState)
|
||||||
MyBase.New(pLogConfig)
|
MyBase.New(pLogConfig)
|
||||||
|
|
||||||
_Factory = New StdSchedulerFactory(_Props)
|
_Factory = New StdSchedulerFactory(_Props)
|
||||||
_MSSQL = pMSSQL_Config
|
_MSSQL = pMSSQL_Config
|
||||||
_JobListener = New JobListener(pLogConfig, _MSSQL, pTableStore)
|
_JobListener = New JobListener(pLogConfig, _MSSQL, pTableStore)
|
||||||
|
_GlobalState = pGlobalState
|
||||||
|
|
||||||
|
_GlobalState.JobListener = _JobListener
|
||||||
|
Logger.Debug("JobListener registered in GlobalState")
|
||||||
|
|
||||||
Logging.LogProvider.SetCurrentLogProvider(New LogProvider(Logger))
|
Logging.LogProvider.SetCurrentLogProvider(New LogProvider(Logger))
|
||||||
End Sub
|
End Sub
|
||||||
@@ -108,7 +113,7 @@ Public Class Scheduler
|
|||||||
End Sub
|
End Sub
|
||||||
Public Async Function GetCronJobs() As Task(Of DataTable)
|
Public Async Function GetCronJobs() As Task(Of DataTable)
|
||||||
Try
|
Try
|
||||||
Dim oSQL As String = "SELECT * FROM TBAPPSERV_CRON_JOB WHERE ACTIVE = 1"
|
Dim oSQL As String = "SELECT * FROM TBAPPSERV_CRON_JOB WITH (NOLOCK) WHERE ACTIVE = 1"
|
||||||
Dim oDatatable As DataTable = Await _MSSQL.GetDatatableAsync(oSQL)
|
Dim oDatatable As DataTable = Await _MSSQL.GetDatatableAsync(oSQL)
|
||||||
|
|
||||||
Return oDatatable
|
Return oDatatable
|
||||||
@@ -119,7 +124,7 @@ Public Class Scheduler
|
|||||||
|
|
||||||
Public Async Function GetCronJobDetails(CronJobId As Integer) As Task(Of DataTable)
|
Public Async Function GetCronJobDetails(CronJobId As Integer) As Task(Of DataTable)
|
||||||
Try
|
Try
|
||||||
Dim oSQL As String = $"SELECT * FROM TBAPPSERV_CRON_DETAIL WHERE CRON_ID = {CronJobId}"
|
Dim oSQL As String = $"SELECT * FROM TBAPPSERV_CRON_DETAIL WITH (NOLOCK) WHERE CRON_ID = {CronJobId}"
|
||||||
Dim oDatatable As DataTable = Await _MSSQL.GetDatatableAsync(oSQL)
|
Dim oDatatable As DataTable = Await _MSSQL.GetDatatableAsync(oSQL)
|
||||||
|
|
||||||
Return oDatatable
|
Return oDatatable
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ Public Class WindowsService
|
|||||||
GlobalState = New GlobalState(LogConfig, MSSQL_IDB, MSSQL_ECM)
|
GlobalState = New GlobalState(LogConfig, MSSQL_IDB, MSSQL_ECM)
|
||||||
|
|
||||||
Dim oMSSQLServer = GetMSSQL_Config(LogConfigScheduler)
|
Dim oMSSQLServer = GetMSSQL_Config(LogConfigScheduler)
|
||||||
Scheduler = New Scheduler(LogConfigScheduler, oMSSQLServer, GlobalState.TableStore)
|
Scheduler = New Scheduler(LogConfigScheduler, oMSSQLServer, GlobalState.TableStore, GlobalState)
|
||||||
|
|
||||||
Logger.Info("Loading Global Data")
|
Logger.Info("Loading Global Data")
|
||||||
GlobalState.LoadObjectStores()
|
GlobalState.LoadObjectStores()
|
||||||
@@ -94,7 +94,6 @@ Public Class WindowsService
|
|||||||
EDMIService.Firebird = Firebird
|
EDMIService.Firebird = Firebird
|
||||||
EDMIService.LogConfig = LogConfig
|
EDMIService.LogConfig = LogConfig
|
||||||
EDMIService.AppConfig = Config
|
EDMIService.AppConfig = Config
|
||||||
EDMIService.Filesystem = Filesystem
|
|
||||||
EDMIService.GlobalState = GlobalState
|
EDMIService.GlobalState = GlobalState
|
||||||
EDMIService.Scheduler = Scheduler
|
EDMIService.Scheduler = Scheduler
|
||||||
|
|
||||||
@@ -187,8 +186,13 @@ Public Class WindowsService
|
|||||||
Private Function GetMSSQL_Config(pLogConfig As LogConfig) As MSSQLServer
|
Private Function GetMSSQL_Config(pLogConfig As LogConfig) As MSSQLServer
|
||||||
Logger.Debug("Connecting to Config MSSQL")
|
Logger.Debug("Connecting to Config MSSQL")
|
||||||
If Config.ConnectionString_Config = String.Empty Then
|
If Config.ConnectionString_Config = String.Empty Then
|
||||||
Logger.Warn("Configuration for ConnectionString_Config is missing.")
|
If Config.ConnectionString_ECM <> String.Empty Then
|
||||||
Throw New ArgumentNullException("ConnectionString_Config")
|
Logger.Info("Using ConnectionString_ECM for Config Database connection.")
|
||||||
|
Return GetMSSQL_ECM(pLogConfig)
|
||||||
|
Else
|
||||||
|
Logger.Warn("Configuration for ConnectionString_Config is missing and ConnectionString_ECM is also missing.")
|
||||||
|
Throw New ArgumentNullException("ConnectionString_Config")
|
||||||
|
End If
|
||||||
End If
|
End If
|
||||||
|
|
||||||
Dim oMSSQL = New MSSQLServer(pLogConfig, Config.ConnectionString_Config)
|
Dim oMSSQL = New MSSQLServer(pLogConfig, Config.ConnectionString_Config)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
<packages>
|
<packages>
|
||||||
<package id="FirebirdSql.Data.FirebirdClient" version="7.5.0" targetFramework="net472" />
|
<package id="FirebirdSql.Data.FirebirdClient" version="7.5.0" targetFramework="net472" />
|
||||||
<package id="Microsoft.Extensions.Logging.Abstractions" version="2.1.1" targetFramework="net472" />
|
<package id="Microsoft.Extensions.Logging.Abstractions" version="2.1.1" targetFramework="net472" />
|
||||||
<package id="NLog" version="5.0.5" targetFramework="net472" />
|
|
||||||
<package id="Quartz" version="3.5.0" targetFramework="net462" />
|
<package id="Quartz" version="3.5.0" targetFramework="net462" />
|
||||||
<package id="System.Buffers" version="4.5.1" targetFramework="net472" />
|
<package id="System.Buffers" version="4.5.1" targetFramework="net472" />
|
||||||
<package id="System.Diagnostics.DiagnosticSource" version="4.7.1" targetFramework="net472" />
|
<package id="System.Diagnostics.DiagnosticSource" version="4.7.1" targetFramework="net472" />
|
||||||
|
|||||||
Reference in New Issue
Block a user