diff --git a/DDMonorepo.sln b/DDMonorepo.sln
index a0e54c47..084a7c6d 100644
--- a/DDMonorepo.sln
+++ b/DDMonorepo.sln
@@ -74,6 +74,8 @@ Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "DBCleaner", "DBCleaner\DBCl
EndProject
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "EDMIService.Tests", "EDMIService.Tests\EDMIService.Tests.vbproj", "{E3DA65CA-964D-4896-991A-B5426884E215}"
EndProject
+Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "EDMI.API", "..\DDModules\EDMIAPI\EDMI.API.vbproj", "{25017513-0D97-49D3-98D7-BA76D9B251B0}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
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}.Release|Any CPU.ActiveCfg = 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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/GUIs.Test.TestGUI/App.config b/GUIs.Test.TestGUI/App.config
index 22ec8bdd..7b1c622c 100644
--- a/GUIs.Test.TestGUI/App.config
+++ b/GUIs.Test.TestGUI/App.config
@@ -33,7 +33,7 @@
-
+
@@ -75,6 +75,14 @@
+
+
+
+
+
+
+
+
diff --git a/Service.EDMIService/EDMIService.vb b/Service.EDMIService/EDMIService.vb
index 764852d2..b510a553 100644
--- a/Service.EDMIService/EDMIService.vb
+++ b/Service.EDMIService/EDMIService.vb
@@ -3,8 +3,7 @@ Imports System.ServiceModel
Imports System.ServiceModel.Description
Imports DigitalData.Modules.Database
Imports DigitalData.Modules.Logging
-Imports DigitalData.Modules.Language
-Imports DigitalData.Modules
+'Imports DigitalData.Modules.
Imports DigitalData.Modules.EDMI
Imports DigitalData.Modules.EDMI.API.Rights
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.IDB
+
Public Class EDMIService
Implements IEDMIService
@@ -22,7 +22,6 @@ Public Class EDMIService
Public Shared MSSQL_IDB As MSSQLServer
Public Shared Firebird As Firebird
Public Shared AppConfig As Config
- Public Shared Filesystem As Filesystem.File
Public Shared GlobalState As GlobalState
Public Shared Scheduler As Scheduler
@@ -586,7 +585,17 @@ Public Class EDMIService
Dim oRow As DataRow = oRows.First()
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
_Logger.Debug("Successfully parsed AccessRights for ObjectId [{0}]", ObjectId)
diff --git a/Service.EDMIService/EDMIService.vbproj b/Service.EDMIService/EDMIService.vbproj
index 1c749f52..2347a3b3 100644
--- a/Service.EDMIService/EDMIService.vbproj
+++ b/Service.EDMIService/EDMIService.vbproj
@@ -79,7 +79,7 @@
..\..\DDModules\EDMIAPI\bin\Debug\DigitalData.Modules.EDMI.API.dll
- P:\Projekte DIGITAL DATA\DIGITAL DATA - Entwicklung\DLL_Bibliotheken\Digital Data\DigitalData.Modules.Filesystem.dll
+ ..\..\DDModules\Filesystem\bin\Debug\DigitalData.Modules.Filesystem.dll
P:\Projekte DIGITAL DATA\DIGITAL DATA - Entwicklung\DLL_Bibliotheken\Digital Data\DigitalData.Modules.Language.dll
diff --git a/Service.EDMIService/GlobalState.vb b/Service.EDMIService/GlobalState.vb
index 55a6c6b5..8dbf2988 100644
--- a/Service.EDMIService/GlobalState.vb
+++ b/Service.EDMIService/GlobalState.vb
@@ -1,7 +1,6 @@
Imports System.Data.SqlClient
Imports System.Runtime.Serialization
Imports DigitalData.Modules.Database
-Imports DigitalData.Modules.Language
Imports DigitalData.Modules.Logging
Public Class GlobalState
@@ -13,8 +12,9 @@ Public Class GlobalState
Public Property ObjectStores As New List(Of ObjectStore)
Public Property Connections As New List(Of DatabaseConnection)
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
@@ -123,7 +123,7 @@ Public Class GlobalState
Public Sub LoadConnections()
_Logger.Info("Loading Database Connections")
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)
_Logger.Info("Found [{0}] Connections", oTable.Rows.Count)
@@ -132,14 +132,14 @@ Public Class GlobalState
For Each oRow As DataRow In oTable.Rows
Dim oConnection As New DatabaseConnection() With {
- .Id = oRow.ItemEx(Of Integer)("GUID"),
- .Active = oRow.ItemEx(Of Boolean)("AKTIV"),
- .Database = oRow.ItemEx(Of String)("DATENBANK"),
- .Title = oRow.ItemEx(Of String)("BEZEICHNUNG"),
- .Password = oRow.ItemEx(Of String)("PASSWORD"),
- .Provider = oRow.ItemEx(Of String)("SQL_PROVIDER"),
- .Server = oRow.ItemEx(Of String)("SERVER"),
- .Username = oRow.ItemEx(Of String)("USERNAME")
+ .Id = IIf(IsDBNull(oRow.Item("GUID")), 0, oRow.Item("GUID")),
+ .Active = IIf(IsDBNull(oRow.Item("AKTIV")), False, oRow.Item("AKTIV")),
+ .Database = IIf(IsDBNull(oRow.Item("DATENBANK")), String.Empty, oRow.Item("DATENBANK")),
+ .Title = IIf(IsDBNull(oRow.Item("BEZEICHNUNG")), String.Empty, oRow.Item("BEZEICHNUNG")),
+ .Password = IIf(IsDBNull(oRow.Item("PASSWORD")), String.Empty, oRow.Item("PASSWORD")),
+ .Provider = IIf(IsDBNull(oRow.Item("SQL_PROVIDER")), String.Empty, oRow.Item("SQL_PROVIDER")),
+ .Server = IIf(IsDBNull(oRow.Item("SERVER")), String.Empty, oRow.Item("SERVER")),
+ .Username = IIf(IsDBNull(oRow.Item("USERNAME")), String.Empty, oRow.Item("USERNAME"))
}
_Logger.Info("New Connection [{0}]", oConnection.Title)
oConnections.Add(oConnection)
diff --git a/Service.EDMIService/IDB/Helpers.vb b/Service.EDMIService/IDB/Helpers.vb
index 8cf93b57..90519ba0 100644
--- a/Service.EDMIService/IDB/Helpers.vb
+++ b/Service.EDMIService/IDB/Helpers.vb
@@ -1,6 +1,5 @@
Imports DigitalData.Modules.Database
Imports DigitalData.Modules.Database.MSSQLServer
-Imports DigitalData.Modules.Language
Imports DigitalData.Modules.Logging
Imports System.Data.SqlClient
Imports DigitalData.Modules.ZooFlow.State
@@ -59,8 +58,8 @@ Namespace IDB
End If
Dim oRow As DataRow = oTable.Rows.Item(0)
- Dim oActive = Utils.NotNull(oRow.Item("ACTIVE"), False)
- Dim oDeleted = Utils.NotNull(oRow.Item("DELETED"), False)
+ Dim oActive = IIf(IsDBNull(oRow.Item("ACTIVE")), False, oRow.Item("ACTIVE"))
+ Dim oDeleted = IIf(IsDBNull(oRow.Item("DELETED")), False, oRow.Item("DELETED"))
IsDeleted = oDeleted
IsActive = oActive
diff --git a/Service.EDMIService/Methods/Database/GetDatatableFromCache/GetDatatableFromCacheMethod.vb b/Service.EDMIService/Methods/Database/GetDatatableFromCache/GetDatatableFromCacheMethod.vb
index cd707632..eb88193b 100644
--- a/Service.EDMIService/Methods/Database/GetDatatableFromCache/GetDatatableFromCacheMethod.vb
+++ b/Service.EDMIService/Methods/Database/GetDatatableFromCache/GetDatatableFromCacheMethod.vb
@@ -14,49 +14,60 @@ Namespace Methods.Database.GetDatatableFromCache
Try
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)
-
- If GlobalState.TableStore.Tables.Contains(pData.DataTable) Then
- oDataTable = GlobalState.TableStore.Tables.Item(pData.DataTable).Copy()
-
- ' 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
+ ' Fallback: Direkter Zugriff auf TableStore
+ If Not GlobalState.TableStore.Tables.Contains(pData.DataTable) Then
+ Return New GetDatatableFromCacheResponse(
+ New KeyNotFoundException($"Datatable {pData.DataTable} does not exist"))
End If
- Logger.Debug("ReturnDatatableFromCache: Datatable Stats for [{0}]:", pData.DataTable)
- Logger.Debug("Unfiltered: [{0}] rows", oDataTable.Rows.Count)
- Logger.Debug("Filtered: [{0}] rows", oFilteredTable.Rows.Count)
-
- 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"))
+ Dim oDirectTable = GlobalState.TableStore.Tables(pData.DataTable).Copy()
+ ' ... Filter/Sort anwenden wie gehabt
+ Return New GetDatatableFromCacheResponse(ApplyFilterAndSort(oDirectTable, pData))
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
Logger.Error(ex)
Return New GetDatatableFromCacheResponse(ex)
End Try
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 Namespace
\ No newline at end of file
diff --git a/Service.EDMIService/Methods/GlobalIndexer/Loader.vb b/Service.EDMIService/Methods/GlobalIndexer/Loader.vb
index 52b5ee0c..4f9e750b 100644
--- a/Service.EDMIService/Methods/GlobalIndexer/Loader.vb
+++ b/Service.EDMIService/Methods/GlobalIndexer/Loader.vb
@@ -37,13 +37,13 @@ Namespace Methods.GlobalIndexer
Dim oRow As DataRow = oProfile.Table.Rows.Item(0)
Dim oProfileObject As New Profile With {
- .Id = oRow.ItemEx("DOCTYPE_ID", 0),
- .Name = oRow.ItemEx("DOCTYPE", "Missing Profile Name"),
- .IsActive = oRow.ItemEx("AKTIV", False),
- .NameConvention = oRow.ItemEx("NAMENKONVENTION", ""),
- .ObjectStore = oRow.ItemEx("OBJECT_STORE", "Work"),
- .ShortName = oRow.ItemEx("KURZNAME", ""),
- .DynamicPath = oRow.ItemEx("DYNAMIC_FOLDER", "")
+ .Id = IIf(IsDBNull(oRow.Item("DOCTYPE_ID")), 0, oRow.Item("DOCTYPE_ID")),
+ .Name = IIf(IsDBNull(oRow.Item("DOCTYPE")), String.Empty, oRow.Item("DOCTYPE")),
+ .IsActive = IIf(IsDBNull(oRow.Item("AKTIV")), False, oRow.Item("AKTIV")),
+ .NameConvention = IIf(IsDBNull(oRow.Item("NAMENKONVENTION")), String.Empty, oRow.Item("NAMENKONVENTION")),
+ .ObjectStore = IIf(IsDBNull(oRow.Item("OBJECT_STORE")), "Work", oRow.Item("OBJECT_STORE")),
+ .ShortName = IIf(IsDBNull(oRow.Item("KURZNAME")), String.Empty, oRow.Item("KURZNAME")),
+ .DynamicPath = IIf(IsDBNull(oRow.Item("DYNAMIC_FOLDER")), String.Empty, oRow.Item("DYNAMIC_FOLDER"))
}
Logger.Debug("Name: [{0}]", oProfileObject.Name)
@@ -82,13 +82,13 @@ Namespace Methods.GlobalIndexer
For Each oRow As DataRow In oAutomaticIndexes.Table.Rows
Dim oAutomaticIndex As New AutomaticIndex With {
- .Id = oRow.ItemEx(Of Integer)("GUID"),
- .Name = oRow.ItemEx(Of String)("INDEXNAME"),
- .ProfileId = oRow.ItemEx(Of Integer)("DOCTYPE_ID"),
- .SQLCommand = oRow.ItemEx(Of String)("SQL_RESULT"),
- .SQLConnectionId = oRow.ItemEx(Of Integer)("CONNECTION_ID"),
- .Sequence = oRow.ItemEx(Of String)("SEQUENCE"),
- .Value = oRow.ItemEx(Of String)("VALUE")
+ .Id = oRow.Item("GUID"),
+ .Name = IIf(IsDBNull(oRow.Item("INDEXNAME")), String.Empty, oRow.Item("INDEXNAME")),
+ .ProfileId = IIf(IsDBNull(oRow.Item("DOCTYPE_ID")), 0, oRow.Item("DOCTYPE_ID")),
+ .SQLCommand = IIf(IsDBNull(oRow.Item("SQL_RESULT")), String.Empty, oRow.Item("SQL_RESULT")),
+ .SQLConnectionId = IIf(IsDBNull(oRow.Item("CONNECTION_ID")), 0, oRow.Item("CONNECTION_ID")),
+ .Sequence = IIf(IsDBNull(oRow.Item("SEQUENCE")), String.Empty, oRow.Item("SEQUENCE")),
+ .Value = IIf(IsDBNull(oRow.Item("VALUE")), String.Empty, oRow.Item("VALUE"))
}
oIndexes.Add(oAutomaticIndex)
@@ -125,15 +125,15 @@ Namespace Methods.GlobalIndexer
For Each oRow As DataRow In oManualIndexes.Table.Rows
Dim oManualIndex As New ManualIndex With {
- .Id = oRow.ItemEx(Of Integer)("GUID"),
- .Name = oRow.ItemEx(Of String)("INDEXNAME"),
- .ProfileId = oRow.ItemEx(Of Integer)("DOK_ID"),
- .IsOptional = oRow.ItemEx(Of Boolean)("OPTIONAL"),
- .IsMultiselect = oRow.ItemEx(Of String)("MULTISELECT"),
- .SQLCommand = oRow.ItemEx(Of String)("SQL_RESULT"),
- .SQLConnectionId = oRow.ItemEx(Of Integer)("CONNECTION_ID"),
- .DefaultValue = oRow.ItemEx(Of String)("DEFAULT_VALUE"),
- .DataType = oRow.ItemEx(Of String)("DATATYPE")
+ .Id = oRow.Item("GUID"),
+ .Name = oRow.Item("INDEXNAME"),
+ .ProfileId = oRow.Item("DOK_ID"),
+ .IsOptional = oRow.Item("OPTIONAL"),
+ .IsMultiselect = oRow.Item("MULTISELECT"),
+ .SQLCommand = oRow.Item("SQL_RESULT"),
+ .SQLConnectionId = oRow.Item("CONNECTION_ID"),
+ .DefaultValue = oRow.Item("DEFAULT_VALUE"),
+ .DataType = oRow.Item("DATATYPE")
}
oIndexes.Add(oManualIndex)
@@ -179,16 +179,16 @@ Namespace Methods.GlobalIndexer
For Each oRow As DataRow In oPostProcessingSteps.Table.Rows
Dim oStep As New PostProcessingStep With {
- .Id = oRow.ItemEx(Of Integer)("GUID"),
- .IndexId = oRow.ItemEx(Of Integer)("IDXMAN_ID"),
- .[Variant] = oRow.ItemEx(Of String)("VARIANT"),
- .Type = oRow.ItemEx(Of String)("TYPE"),
- .Function1 = oRow.ItemEx(Of String)("FUNCTION1"),
- .Function2 = oRow.ItemEx(Of String)("FUNCTION2"),
- .Text1 = oRow.ItemEx(Of String)("Text1"),
- .Text2 = oRow.ItemEx(Of String)("Text2"),
- .Text3 = oRow.ItemEx(Of String)("Text3"),
- .Sequence = oRow.ItemEx(Of Integer)("SEQUENCE")
+ .Id = oRow.Item("GUID"),
+ .IndexId = oRow.Item("IDXMAN_ID"),
+ .[Variant] = oRow.Item("VARIANT"),
+ .Type = oRow.Item("TYPE"),
+ .Function1 = oRow.Item("FUNCTION1"),
+ .Function2 = oRow.Item("FUNCTION2"),
+ .Text1 = oRow.Item("Text1"),
+ .Text2 = oRow.Item("Text2"),
+ .Text3 = oRow.Item("Text3"),
+ .Sequence = oRow.Item("SEQUENCE")
}
oSteps.Add(oStep)
diff --git a/Service.EDMIService/Methods/IDB/GetFileObject/GetFileObjectMethod.vb b/Service.EDMIService/Methods/IDB/GetFileObject/GetFileObjectMethod.vb
index 613c68c2..382cafcc 100644
--- a/Service.EDMIService/Methods/IDB/GetFileObject/GetFileObjectMethod.vb
+++ b/Service.EDMIService/Methods/IDB/GetFileObject/GetFileObjectMethod.vb
@@ -25,12 +25,12 @@ Namespace Methods.IDB.GetFileObject
LogAndThrow("Error while getting FileObject data!")
End If
- Dim oRow As DataRow = oTable.First()
- Dim oFileHash As String = oRow.ItemEx("FILE_HASH", "")
- Dim oFileSize As Long = oRow.ItemEx(Of Long)("FILE_SIZE", 0)
- Dim oFileExtension As String = oRow.ItemEx(Of String)("EXTENSION")
- Dim oFilePath = oRow.ItemEx("RELPATH", "")
- Dim oFileName = oRow.ItemEx("Filename", "")
+ Dim oRow As DataRow = oTable.Rows(0)
+ Dim oFileHash As String = oRow.Item("FILE_HASH")
+ Dim oFileSize As Long = oRow.Item("FILE_SIZE")
+ Dim oFileExtension As String = oRow.Item("EXTENSION")
+ Dim oFilePath = oRow.Item("RELPATH")
+ Dim oFileName = oRow.Item("Filename")
Dim oFullFileName = Path.Combine(oFilePath, oFileName)
Dim oFileObject As New FileObject With {
diff --git a/Service.EDMIService/My Project/AssemblyInfo.vb b/Service.EDMIService/My Project/AssemblyInfo.vb
index 6b464f5c..993ffb98 100644
--- a/Service.EDMIService/My Project/AssemblyInfo.vb
+++ b/Service.EDMIService/My Project/AssemblyInfo.vb
@@ -12,8 +12,8 @@ Imports System.Runtime.InteropServices
-
-
+
+
@@ -31,5 +31,5 @@ Imports System.Runtime.InteropServices
' übernehmen, indem Sie "*" eingeben:
'
-
-
+
+
diff --git a/Service.EDMIService/Scheduler/DatatableJob.vb b/Service.EDMIService/Scheduler/DatatableJob.vb
index b1510bd0..7f08e9f9 100644
--- a/Service.EDMIService/Scheduler/DatatableJob.vb
+++ b/Service.EDMIService/Scheduler/DatatableJob.vb
@@ -29,17 +29,17 @@ Public Class DatatableJob
Try
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)
For Each oRow As DataRow In oCronDetails.Rows
- Dim oDatatableName As String = NotNull(oRow.Item("DT_NAME"), String.Empty)
- Dim oParentColumn As String = NotNull(oRow.Item("DT_COLUMN"), String.Empty)
- Dim oChildColumn As String = NotNull(oRow.Item("CHILD_DT_COLUMN"), String.Empty)
- Dim oConnectionId As Integer = NotNull(oRow.Item("CON_ID"), String.Empty)
- Dim oTitle As String = NotNull(oRow.Item("TITLE"), String.Empty)
- Dim oSQL As String = NotNull(oRow.Item("COMMAND"), String.Empty)
- Dim oIndexColumns As String = NotNull(oRow.Item("INDEX_COLUMNS"), String.Empty)
+ Dim oDatatableName As String = IIf(IsDBNull(oRow.Item("DT_NAME")), String.Empty, oRow.Item("DT_NAME"))
+ Dim oParentColumn As String = IIf(IsDBNull(oRow.Item("DT_COLUMN")), String.Empty, oRow.Item("DT_COLUMN"))
+ Dim oChildColumn As String = IIf(IsDBNull(oRow.Item("CHILD_DT_COLUMN")), String.Empty, oRow.Item("CHILD_DT_COLUMN"))
+ Dim oConnectionId As Integer = IIf(IsDBNull(oRow.Item("CON_ID")), 0, oRow.Item("CON_ID"))
+ Dim oTitle As String = IIf(IsDBNull(oRow.Item("TITLE")), String.Empty, oRow.Item("TITLE"))
+ Dim oSQL As String = IIf(IsDBNull(oRow.Item("COMMAND")), String.Empty, oRow.Item("COMMAND"))
+ Dim oIndexColumns As String = IIf(IsDBNull(oRow.Item("INDEX_COLUMNS")), String.Empty, oRow.Item("INDEX_COLUMNS"))
Try
oLogger.Debug("Running Command-Job [{0}]", oTitle)
@@ -80,7 +80,7 @@ Public Class DatatableJob
.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
oLogger.Debug("Child Datatable [{0}] defined, Relation: Parent [{1}] -> Child [{2}]", oChildTableNAme, oParentColumn, oChildColumn)
diff --git a/Service.EDMIService/Scheduler/JobListener.vb b/Service.EDMIService/Scheduler/JobListener.vb
index dab2f9b7..52254915 100644
--- a/Service.EDMIService/Scheduler/JobListener.vb
+++ b/Service.EDMIService/Scheduler/JobListener.vb
@@ -11,7 +11,10 @@ Public Class JobListener
Inherits JobListenerSupport
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 _LogConfig As LogConfig
@@ -23,9 +26,44 @@ Public Class JobListener
_LogConfig = LogConfig
_Logger = LogConfig.GetLogger()
_MSSQL = MSSQL
- Dataset = ResultDataSet
+ _Dataset = ResultDataSet
+
+ _Logger.Debug("JobListener initialized with ReaderWriterLockSlim for thread-safe access")
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
_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
Try
- _Logger.Debug("Saving Datatable [{0}]", oTableResult.Table)
+ _Logger.Debug("Saving Datatable [{0}]", oTableResult.Table.TableName)
SaveDataTables(oTableResult)
Catch ex As Exception
+ _Logger.Error(ex)
_Logger.Error("Error while executing SaveDataTables for {0}", oTableResult.Table.TableName)
End Try
Next
@@ -44,6 +83,86 @@ Public Class JobListener
Return MyBase.JobWasExecuted(context, jobException, cancellationToken)
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)
Try
Dim oDatatableNameTemp As String = Name & "-TEMP"
@@ -103,13 +222,12 @@ Public Class JobListener
DataSet.Tables.Item(oDatatableNameTemp).TableName = Name
Catch ex As Exception
_Logger.Error(ex)
- Throw ex
+ Throw
End Try
End Sub
Public Sub AddNewTable(Name As String, Table As DataTable, DataSet As DataSet)
_Logger.Debug("DataTable [{0}] does not exist, adding to DataSet", Name)
-
DataSet.Tables.Add(Table)
End Sub
@@ -117,9 +235,9 @@ Public Class JobListener
Return $"{ParentTableName}-{ChildTableName}"
End Function
- Public Sub AddRelation(ParentTableName As String, ParentColumnName As String, ChildTableName As String, ChildColumnName As String)
- Dim oChild As DataTable = Dataset.Tables.Item(ChildTableName)
- Dim oParent As DataTable = Dataset.Tables.Item(ParentTableName)
+ Public Sub AddRelation(ParentTableName As String, ParentColumnName As String, ChildTableName As String, ChildColumnName As String, TargetDataSet As DataSet)
+ Dim oChild As DataTable = TargetDataSet.Tables.Item(ChildTableName)
+ Dim oParent As DataTable = TargetDataSet.Tables.Item(ParentTableName)
Dim oRelationName As String = GetRelationName(ParentTableName, ChildTableName)
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)
Try
_Logger.Debug("Creating Relation [{0}]", oRelationName)
- _Logger.Debug("ParentColumn exists: {0}", IsNothing(oParentColumn))
- _Logger.Debug("ChildColumn exists: {0}", IsNothing(oChildColumn))
+ _Logger.Debug("ParentColumn exists: {0}", Not IsNothing(oParentColumn))
+ _Logger.Debug("ChildColumn exists: {0}", Not IsNothing(oChildColumn))
+
Dim oRelation As New DataRelation(oRelationName, oParentColumn, oChildColumn)
_Logger.Debug("Adding Relation [{0}]", oRelationName)
- Dataset.Relations.Add(oRelation)
+ TargetDataSet.Relations.Add(oRelation)
Catch ex As ArgumentNullException
_Logger.Error(ex)
@@ -145,7 +264,7 @@ Public Class JobListener
Catch ex As InvalidConstraintException
_Logger.Error(ex)
- _Logger.Warn("DuplicateNameException in AddRelation: {0}", ex.Message)
+ _Logger.Warn("InvalidConstraintException in AddRelation: {0}", ex.Message)
Catch ex As ArgumentException
_Logger.Error(ex)
@@ -158,55 +277,6 @@ Public Class JobListener
End Try
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)
Dim oIndex As Integer = 1
@@ -226,4 +296,10 @@ Public Class JobListener
oIndex += 1
Next
End Sub
-End Class
+
+ ' ✅ Cleanup beim Dispose
+ Protected Overrides Sub Finalize()
+ _DatasetLock?.Dispose()
+ MyBase.Finalize()
+ End Sub
+End Class
\ No newline at end of file
diff --git a/Service.EDMIService/Scheduler/Scheduler.vb b/Service.EDMIService/Scheduler/Scheduler.vb
index 1a0209fd..aa3c3ee1 100644
--- a/Service.EDMIService/Scheduler/Scheduler.vb
+++ b/Service.EDMIService/Scheduler/Scheduler.vb
@@ -13,20 +13,25 @@ Public Class Scheduler
Private ReadOnly _MSSQL As MSSQLServer
Private _Scheduler As IScheduler
Private ReadOnly _JobListener As JobListener
+ Private ReadOnly _GlobalState As GlobalState ' ✅ NEU
Private ReadOnly _Props = New NameValueCollection From {
{"quartz.serializer.type", "binary"},
- {"quartz.threadPool.maxConcurrency", 1}
+ {"quartz.threadPool.maxConcurrency", 3}
}
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)
_Factory = New StdSchedulerFactory(_Props)
_MSSQL = pMSSQL_Config
_JobListener = New JobListener(pLogConfig, _MSSQL, pTableStore)
+ _GlobalState = pGlobalState
+
+ _GlobalState.JobListener = _JobListener
+ Logger.Debug("JobListener registered in GlobalState")
Logging.LogProvider.SetCurrentLogProvider(New LogProvider(Logger))
End Sub
@@ -108,7 +113,7 @@ Public Class Scheduler
End Sub
Public Async Function GetCronJobs() As Task(Of DataTable)
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)
Return oDatatable
@@ -119,7 +124,7 @@ Public Class Scheduler
Public Async Function GetCronJobDetails(CronJobId As Integer) As Task(Of DataTable)
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)
Return oDatatable
diff --git a/Service.EDMIService/WindowsService.vb b/Service.EDMIService/WindowsService.vb
index 4249c9c5..f5bd3082 100644
--- a/Service.EDMIService/WindowsService.vb
+++ b/Service.EDMIService/WindowsService.vb
@@ -77,7 +77,7 @@ Public Class WindowsService
GlobalState = New GlobalState(LogConfig, MSSQL_IDB, MSSQL_ECM)
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")
GlobalState.LoadObjectStores()
@@ -94,7 +94,6 @@ Public Class WindowsService
EDMIService.Firebird = Firebird
EDMIService.LogConfig = LogConfig
EDMIService.AppConfig = Config
- EDMIService.Filesystem = Filesystem
EDMIService.GlobalState = GlobalState
EDMIService.Scheduler = Scheduler
@@ -187,8 +186,13 @@ Public Class WindowsService
Private Function GetMSSQL_Config(pLogConfig As LogConfig) As MSSQLServer
Logger.Debug("Connecting to Config MSSQL")
If Config.ConnectionString_Config = String.Empty Then
- Logger.Warn("Configuration for ConnectionString_Config is missing.")
- Throw New ArgumentNullException("ConnectionString_Config")
+ If Config.ConnectionString_ECM <> String.Empty Then
+ 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
Dim oMSSQL = New MSSQLServer(pLogConfig, Config.ConnectionString_Config)
diff --git a/Service.EDMIService/packages.config b/Service.EDMIService/packages.config
index b27d6005..b5d64842 100644
--- a/Service.EDMIService/packages.config
+++ b/Service.EDMIService/packages.config
@@ -2,7 +2,6 @@
-