From cf719c6a210b532dd8c80b5662e954b145ee65b5 Mon Sep 17 00:00:00 2001 From: Danny Parsons Date: Mon, 9 Mar 2020 22:26:43 +0300 Subject: [PATCH 1/6] created data structure class and child class --- ClimsoftVer4/ClimsoftVer4/ClimsoftVer4.vbproj | 2 + ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb | 243 ++++++++++++++++++ .../ClimsoftVer4/clsDataStructureStation.vb | 18 ++ 3 files changed, 263 insertions(+) create mode 100644 ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb create mode 100644 ClimsoftVer4/ClimsoftVer4/clsDataStructureStation.vb diff --git a/ClimsoftVer4/ClimsoftVer4/ClimsoftVer4.vbproj b/ClimsoftVer4/ClimsoftVer4/ClimsoftVer4.vbproj index 0ecdd45c..4965cf20 100644 --- a/ClimsoftVer4/ClimsoftVer4/ClimsoftVer4.vbproj +++ b/ClimsoftVer4/ClimsoftVer4/ClimsoftVer4.vbproj @@ -91,6 +91,8 @@ + + diff --git a/ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb b/ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb new file mode 100644 index 00000000..35390bed --- /dev/null +++ b/ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb @@ -0,0 +1,243 @@ +''' +''' +''' +''' The DataStructure will keep data in the same structure/table as in the database. +''' +Public Class DataStructure + ' TODO + ' 1. Do we allow choice of fields to get? If writing then all fields needed, + ' but reading only may not need all fields. + ' Most tables have few fields so might not be needed. + ' 2. Where should SQL statement construction functions live? + + ''' + ''' A string, the name of the table in the database this DataStructure links. + ''' + Private strTableName As String + + ''' + ''' A string, how the "id" column is stored in the database in all tables it appears in. + ''' This could be moved to a separate class of constants. + ''' + Private strId As String = "id" + + ''' + ''' A string, how the "version number" column is stored in the database in all tables it appears in. + ''' This could be moved to a separate class of constants. + ''' + Private strVersionNumber As String = "version_number" + + ''' + ''' A string, how the "current/current best" column is stored in strTableName. + ''' This is "current_best" by default and can either be "current" or "current_best". + ''' + Private strCurrent As String = "current_best" + + ''' + ''' A list of strings, the names of the fields in strTableName which uniquely define a row. + ''' In most tables and by default this is {strId, strVersionNumber} but it is not in all and can be changed. + ''' + Private lstKeyFieldNames As List(Of String) = New List(Of String)({strId, strVersionNumber}) + + ''' + ''' A DataTable storing the data read from the database. + ''' This remains static once fetched from the database. + ''' + Private dtbReadTable As DataTable + + ''' + ''' A DataTable storing the new data to be written to the database. + ''' This builds up as controls change. + ''' + Private dtbWriteTable As DataTable + + ''' + ''' A DataTable storing new comments associated to strTableName. + ''' + Private dtbComments As DataTable + + ''' + ''' A DataTable storing new events associated to strTableName. + ''' + Private dtbEvents As DataTable + + ''' + ''' A list of linked DataStructures that relate to required linked tables in the database. + ''' + ''' + ''' This will included DataStructures for existing comments and events associated to strTableName. + ''' + Private lstLinkedDataStructures As List(Of DataStructure) + + ''' + ''' This event is raised when dtbReadTable changes. + ''' The navigator and page control will listen for this and update their values when this event is raised. + ''' + Public Event DataChanged() + + ''' + ''' Set the name of the table in the database this DataStructure links to. + ''' + ''' A string, the name of the table in the database. + Public Sub SetTableName(strNewTableName As String) + strTableName = strNewTableName + End Sub + + ''' + ''' Set the names of the fields in strTableName which uniquely define a row. + ''' This is needed when updating records. + ''' In most tables and by default this is {strId, strVersionNumber}. + ''' This method should only be used if it is different to the default. + ''' + Public Sub SetKeyFields(iEnumerableNewKeyFields As IEnumerable(Of String)) + lstKeyFieldNames = iEnumerableNewKeyFields.ToList() + End Sub + + ''' + ''' Initialises dtbWriteTable as a clone of dtbReadTable. + ''' This should be called once dtbReadTable has been set. + ''' + ''' + ''' .Clone copies the structure but not the data of a DataTable. + ''' + Private Sub InitialiseWriteTable() + If dtbReadTable IsNot Nothing Then + dtbWriteTable = dtbReadTable.Clone() + End If + End Sub + + ''' + ''' Gets the comments associated with strTableName and stores them in dtbComments. + ''' This may be called when getting the main data, or may only be called on demand. + ''' + ''' Boolean, if True all versions returned, if False only the current comment for each id is returned. + Private Sub SetCommentsTable(Optional bAllVersions As Boolean = False) + ' SQL command, all versions: + ' SELECT * FROM c5_comments WHERE table_id=@strTableName + + ' From here: https://stackoverflow.com/questions/612231/how-can-i-select-rows-with-maxcolumn-value-distinct-by-another-column-in-sql + ' SQL command, current only: + ' Select Case m.* From c5_comment m + ' INNER Join(SELECT id, MAX(current) As max_current FROM c5_comment GROUP BY id) m_group + ' ON m.id=m_group.id + ' AND m.current=m_group.max_current AND m.table_id=@strTableName + + ' alternative....... + + ' SELECT m.* + ' FROM c5_comment m + ' LEFT JOIN c5_comment b + ' ON m.id = b.id + ' AND m.current < b.current + ' WHERE b.current Is NULL AND m.table_id=@strTableName + End Sub + + ''' + ''' Gets the events associated with strTableName and stores them in dtbEvents. + ''' This may be called when getting the main data, or may only be called on demand. + ''' + Private Sub SetEventsTable() + ' SQL command, current only: + ' + ' SELECT * FROM (SELECT m.* + ' FROM c5_event_effect m + ' LEFT JOIN c5_event_effect b + ' ON m.id = b.id + ' AND m.current < b.current + ' WHERE b.current IS NULL AND m.table_id=@strTableName) e_e + ' INNER JOIN (SELECT m.* + ' FROM c5_event m + ' LEFT JOIN c5_event b + ' ON m.id = b.id + ' AND m.current < b.current + ' WHERE b.current IS NULL) e + ' ON e_e.event_id=e.id + End Sub + + ''' + ''' Constructs an SQL query to get only the current versions from a table. + ''' (Could be moved to a separate class for query construction) + ''' + ''' A string, the name of the table to query. + ''' + Private Function GetCurrentVersionsQuery(strTable As String) As String + ' From here: https://stackoverflow.com/questions/612231/how-can-i-select-rows-with-maxcolumn-value-distinct-by-another-column-in-sql + + ' Option 1: + ' ######### + ' Select Case m.* From @strTable m + ' INNER Join(SELECT id, MAX(@strCurrent) As max_current FROM @strTable GROUP BY id) m_group + ' ON m.id=m_group.id + ' AND m.@strCurrent=m_group.max_current + + ' Option 2: + ' ######### + ' SELECT m.* + ' FROM @strTable m + ' LEFT JOIN @strTable b + ' ON m.id = b.id + ' AND m.@strCurrent < b.@strCurrent + ' WHERE b.@strCurrent Is NULL + End Function + + ''' + ''' Constructs an SQL query to get only the versions equal to iVersion from a table. + ''' (Could be moved to a separate class for query construction) + ''' + ''' A string, the name of the table to query. + ''' An integer, the version numbers to extract for each id. + ''' + Private Function GetVersionNumberQuery(strTable As String, iVersion As Integer) As String + + End Function + + Private Function NewAction(iActionTypeID As Integer, strOperatorID As String) As Integer + Dim iActionID As Integer + ' create new entry in action table using iActionTypeID, strOperatorID and current date-time + ' get back action ID for new entry created + Return iActionID + End Function + + Public Sub DoAction(iActionTypeID As Integer, strOperatorID As String, DataStruct As DataStructure, Optional strActionComment As String = "") + Dim iActionID As Integer + + ' create new entry in action table using iActionTypeID, strOperatorID and current date-time + ' get back action ID for new entry created + iActionID = NewAction(iActionTypeID, strOperatorID) + ' create comment for action comment and add to comment table + + DataStruct.UpdateTable(iActionTypeID) + End Sub + + Public Sub UpdateTable(iActionID As Integer) + ' produce table from the read and write table that will corresponds to the 'add's and 'update's of that table + ' STEPS + ' 1. Make the update table as a Clone() of the read table + ' 2. Make a blank audit table to add entries to + ' 3. Loop through the write table and: + ' a) read the update type from the write table + ' b) add a row for each row to the update table, (unless update type = delete) + ' c) add a row for an edit to the read table if neccessary + ' d) produce the audit record to be added to the audit write table + ' 4. Write: update table, audit table, comments table, events table + ' 5. Call UpdateTable for each lstLinkedDataStructures + End Sub + + + ' Need function which takes these two tables and generates the 'add' and 'update' tables/commands + ' When adding, only need to update current field of existing records + + ' produce table from the read and write table that will corresponds to the 'add's and 'update's of that table + ' produce the audit records to be added + + ''' + ''' Takes dtbReadTable and dtbWriteTable and produces a DataTable with the correct updates and inserts to be added to the database. + ''' + ''' A DataTable with + Private Function GenerateTableForUpdate() As DataTable + + End Function + + ' UpdateData() adds, updates and deletes data + +End Class diff --git a/ClimsoftVer4/ClimsoftVer4/clsDataStructureStation.vb b/ClimsoftVer4/ClimsoftVer4/clsDataStructureStation.vb new file mode 100644 index 00000000..4c1fbd70 --- /dev/null +++ b/ClimsoftVer4/ClimsoftVer4/clsDataStructureStation.vb @@ -0,0 +1,18 @@ +Public Class DataStructureStation + Inherits DataStructure + + ' Will be able to view/get station data as a single table + + ' Properties of station to get: all station properties and all features (all features relate to stations) + + + ' Top level table: feature_type + ' Second level: feature (get list of stations), feature_type_property (get ID to link to station) + + ' strPropertyName is a string/enumerated value of known properties + ' Method knows how to get a know property in v4 or v5 database + ' if strPropertyName is not recognised then have generic way to search database + Public Function GetPropertyValue(strPropertyName As String) As Object + + End Function +End Class From be0b276b7b9a70a555c81286b986f5bdeeb7542a Mon Sep 17 00:00:00 2001 From: Danny Parsons Date: Tue, 10 Mar 2020 18:54:17 +0300 Subject: [PATCH 2/6] added code to produce update table and test on login, added enumerations class --- ClimsoftVer4/ClimsoftVer4/ClimsoftVer4.vbproj | 1 + ClimsoftVer4/ClimsoftVer4/Enumerations.vb | 9 +++ ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb | 81 +++++++++++++++++-- ClimsoftVer4/ClimsoftVer4/frmLogin.vb | 4 + 4 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 ClimsoftVer4/ClimsoftVer4/Enumerations.vb diff --git a/ClimsoftVer4/ClimsoftVer4/ClimsoftVer4.vbproj b/ClimsoftVer4/ClimsoftVer4/ClimsoftVer4.vbproj index 4965cf20..13cf724d 100644 --- a/ClimsoftVer4/ClimsoftVer4/ClimsoftVer4.vbproj +++ b/ClimsoftVer4/ClimsoftVer4/ClimsoftVer4.vbproj @@ -99,6 +99,7 @@ + formAgro1.vb diff --git a/ClimsoftVer4/ClimsoftVer4/Enumerations.vb b/ClimsoftVer4/ClimsoftVer4/Enumerations.vb new file mode 100644 index 00000000..24ddfa94 --- /dev/null +++ b/ClimsoftVer4/ClimsoftVer4/Enumerations.vb @@ -0,0 +1,9 @@ +Public Class Enumerations + Public Enum UpdateType + NewRecord + Correction + EventChange + Delete + End Enum + +End Class diff --git a/ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb b/ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb index 35390bed..a76585f3 100644 --- a/ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb +++ b/ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb @@ -75,6 +75,9 @@ Public Class DataStructure ''' Public Event DataChanged() + Private strVOld As String = ".v_old" + Private strUpdateType As String = ".update_type" + ''' ''' Set the name of the table in the database this DataStructure links to. ''' @@ -103,6 +106,8 @@ Public Class DataStructure Private Sub InitialiseWriteTable() If dtbReadTable IsNot Nothing Then dtbWriteTable = dtbReadTable.Clone() + dtbWriteTable.Columns.Add(strVOld, GetType(Integer)) + dtbWriteTable.Columns.Add(strUpdateType, GetType(Integer)) End If End Sub @@ -209,21 +214,87 @@ Public Class DataStructure DataStruct.UpdateTable(iActionTypeID) End Sub + Public Sub TestUpdateTable() + dtbReadTable = New DataTable + dtbReadTable.Columns.Add(strId, GetType(Integer)) + dtbReadTable.Columns.Add(strVersionNumber, GetType(Integer)) + dtbReadTable.Columns.Add("value", GetType(String)) + dtbReadTable.Columns.Add(strCurrent, GetType(Integer)) + + dtbReadTable.Rows.Add(1, 1, "a", 1) + dtbReadTable.Rows.Add(2, 1, "b", 1) + dtbReadTable.Rows.Add(3, 1, "c", 1) + dtbReadTable.Rows.Add(4, 1, "d", 1) + + dtbWriteTable = dtbReadTable.Clone() + dtbWriteTable.Columns.Add(strVOld, GetType(Integer)) + dtbWriteTable.Columns.Add(strUpdateType, GetType(Integer)) + + dtbWriteTable.Rows.Add(1, 2, "A", 1, 1, Enumerations.UpdateType.Correction) + dtbWriteTable.Rows.Add(3, 1, "c", DBNull.Value, 1, Enumerations.UpdateType.Delete) + dtbWriteTable.Rows.Add(4, 2, "dd", 2, 1, Enumerations.UpdateType.EventChange) + UpdateTable(1) + End Sub + Public Sub UpdateTable(iActionID As Integer) + Dim dtbUpdateTable As DataTable + Dim dtbAuditTable As DataTable + Dim iUpdateType As Integer + Dim dtbWriteTableCopy As DataTable + Dim rowNewWrite As DataRow + Dim strSelectExp As String + Dim drowsReadRows() As DataRow + Dim rowReadRow As DataRow + Dim rowUpdateReadRow As DataRow + ' produce table from the read and write table that will corresponds to the 'add's and 'update's of that table ' STEPS ' 1. Make the update table as a Clone() of the read table + dtbUpdateTable = dtbReadTable.Clone() + + dtbWriteTableCopy = dtbWriteTable.Copy() + dtbWriteTableCopy.Columns.Remove(strVOld) + dtbWriteTableCopy.Columns.Remove(strUpdateType) ' 2. Make a blank audit table to add entries to + ' dtbAuditTable = ' 3. Loop through the write table and: - ' a) read the update type from the write table - ' b) add a row for each row to the update table, (unless update type = delete) - ' c) add a row for an edit to the read table if neccessary - ' d) produce the audit record to be added to the audit write table + For i As Integer = 0 To dtbWriteTable.Rows.Count - 1 + ' a) read the update type from the write table + iUpdateType = dtbWriteTable.Rows(i).Field(Of Integer)(strUpdateType) + ' b) add a row for each row to the update table + ' (current value should be correct in dtbWriteTable at this point) + ' for a delete, current value will be NULL so the "new" row is actually an update to an existing row + dtbUpdateTable.ImportRow(dtbWriteTableCopy.Rows(i)) + rowNewWrite = dtbUpdateTable.Rows(dtbUpdateTable.Rows.Count - 1) + ' c) add a row for an edit to the read table if neccessary + ' Only a correction needs an edit to the read data (a change for a delete is in the write table already) + If iUpdateType = Enumerations.UpdateType.Correction Then + strSelectExp = strId & " = " & dtbWriteTable.Rows(i).Field(Of Integer)(strId) & " and " & strVersionNumber & " = " & dtbWriteTable.Rows(i).Field(Of Integer)(strVOld) + drowsReadRows = dtbReadTable.Select(strSelectExp) + If drowsReadRows.Count = 1 Then + rowReadRow = drowsReadRows(0) + dtbUpdateTable.ImportRow(rowReadRow) + rowUpdateReadRow = dtbUpdateTable.Rows(dtbUpdateTable.Rows.Count - 1) + If iUpdateType = Enumerations.UpdateType.Correction Then + rowUpdateReadRow.Item(strCurrent) = DBNull.Value + ElseIf iUpdateType = Enumerations.UpdateType.EventChange Then + rowUpdateReadRow.Item(strCurrent) = rowUpdateReadRow.Field(Of Integer)(strCurrent) + 1 + End If + Else + ' Error? Should always be 1 row because id and version number form a key + End If + End If + ' d) produce the audit record to be added to the audit write table + Next + Console.Write(dtbUpdateTable) + ' 4. Write: update table, audit table, comments table, events table ' 5. Call UpdateTable for each lstLinkedDataStructures + For Each clsLinkedStruc As DataStructure In lstLinkedDataStructures + clsLinkedStruc.UpdateTable(iActionID) + Next End Sub - ' Need function which takes these two tables and generates the 'add' and 'update' tables/commands ' When adding, only need to update current field of existing records diff --git a/ClimsoftVer4/ClimsoftVer4/frmLogin.vb b/ClimsoftVer4/ClimsoftVer4/frmLogin.vb index 7a8adf27..7100051b 100644 --- a/ClimsoftVer4/ClimsoftVer4/frmLogin.vb +++ b/ClimsoftVer4/ClimsoftVer4/frmLogin.vb @@ -209,6 +209,10 @@ Public Class frmLogin 'Translate text for controls on login form. 'Other Translation after successful login will come from language translation table stored in database + Dim datstruct As New DataStructure + + datstruct.TestUpdateTable() + msgKeyentryFormsListUpdated = "List of key-entry forms updated!" msgStationInformationNotFound = "Station information Not found. Please add station information And try again!" From 76553f71b839136845299882a6c773fd9b90ab3c Mon Sep 17 00:00:00 2001 From: Danny Parsons Date: Thu, 12 Mar 2020 18:12:26 +0300 Subject: [PATCH 3/6] added XML comments to DataStructure, added method to add record to DataStructure, added to GlobalVariables --- ClimsoftVer4/ClimsoftVer4/ClimsoftVer4.vbproj | 2 +- ClimsoftVer4/ClimsoftVer4/Enumerations.vb | 9 - ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb | 223 +++++++++++++----- .../ClimsoftVer4/clsGlobalVariables.vb | 93 ++++++++ 4 files changed, 256 insertions(+), 71 deletions(-) delete mode 100644 ClimsoftVer4/ClimsoftVer4/Enumerations.vb create mode 100644 ClimsoftVer4/ClimsoftVer4/clsGlobalVariables.vb diff --git a/ClimsoftVer4/ClimsoftVer4/ClimsoftVer4.vbproj b/ClimsoftVer4/ClimsoftVer4/ClimsoftVer4.vbproj index 13cf724d..8b781a73 100644 --- a/ClimsoftVer4/ClimsoftVer4/ClimsoftVer4.vbproj +++ b/ClimsoftVer4/ClimsoftVer4/ClimsoftVer4.vbproj @@ -99,7 +99,7 @@ - + formAgro1.vb diff --git a/ClimsoftVer4/ClimsoftVer4/Enumerations.vb b/ClimsoftVer4/ClimsoftVer4/Enumerations.vb deleted file mode 100644 index 24ddfa94..00000000 --- a/ClimsoftVer4/ClimsoftVer4/Enumerations.vb +++ /dev/null @@ -1,9 +0,0 @@ -Public Class Enumerations - Public Enum UpdateType - NewRecord - Correction - EventChange - Delete - End Enum - -End Class diff --git a/ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb b/ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb index a76585f3..0e2cbf72 100644 --- a/ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb +++ b/ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb @@ -21,12 +21,6 @@ Public Class DataStructure ''' Private strId As String = "id" - ''' - ''' A string, how the "version number" column is stored in the database in all tables it appears in. - ''' This could be moved to a separate class of constants. - ''' - Private strVersionNumber As String = "version_number" - ''' ''' A string, how the "current/current best" column is stored in strTableName. ''' This is "current_best" by default and can either be "current" or "current_best". @@ -37,7 +31,15 @@ Public Class DataStructure ''' A list of strings, the names of the fields in strTableName which uniquely define a row. ''' In most tables and by default this is {strId, strVersionNumber} but it is not in all and can be changed. ''' - Private lstKeyFieldNames As List(Of String) = New List(Of String)({strId, strVersionNumber}) + Private lstKeyFieldNames As List(Of String) = New List(Of String)({strId, GlobalVariables.strVersionNumber}) + + ''' + ''' A list of strings, the names of the fields in strTableName which do not relate to primary key of auditting i.e. all columns apart from: strID, GlobalVariables.strVersionNumber and strCurrent. + ''' + ''' + ''' This is used when adding a new record. To add a new record, a value must be specified for each of lstValueFields. + ''' + Private lstValueFields As List(Of String) ''' ''' A DataTable storing the data read from the database. @@ -75,34 +77,54 @@ Public Class DataStructure ''' Public Event DataChanged() + ''' Field name for "version old" when constructing dtbUpdateTable. Private strVOld As String = ".v_old" + + ''' Field name for "update type" when constructing dtbUpdateTable. Private strUpdateType As String = ".update_type" - ''' - ''' Set the name of the table in the database this DataStructure links to. - ''' - ''' A string, the name of the table in the database. + '''//////////////////////////////////////////////////////////////////////////////////////////////////// + ''' Set the name of the table in the database this DataStructure links to. + ''' + ''' A string, the name of the table in the database. + '''//////////////////////////////////////////////////////////////////////////////////////////////////// Public Sub SetTableName(strNewTableName As String) strTableName = strNewTableName End Sub + '''//////////////////////////////////////////////////////////////////////////////////////////////////// ''' - ''' Set the names of the fields in strTableName which uniquely define a row. - ''' This is needed when updating records. - ''' In most tables and by default this is {strId, strVersionNumber}. - ''' This method should only be used if it is different to the default. + ''' Set the names of the fields in strTableName which do not relate to primary key of auditting + ''' i.e. all columns apart from: strID, GlobalVariables.strVersionNumber and strCurrent. ''' + ''' + ''' The list of names of the key fields for strTableName. + '''//////////////////////////////////////////////////////////////////////////////////////////////////// Public Sub SetKeyFields(iEnumerableNewKeyFields As IEnumerable(Of String)) lstKeyFieldNames = iEnumerableNewKeyFields.ToList() End Sub + '''//////////////////////////////////////////////////////////////////////////////////////////////////// ''' - ''' Initialises dtbWriteTable as a clone of dtbReadTable. - ''' This should be called once dtbReadTable has been set. + ''' Set the names of the fields in strTableName which uniquely define a row. This is + ''' needed when updating records. In most tables and by default this is {strId, + ''' strVersionNumber}. This method should only be used if it is different to the default. ''' - ''' - ''' .Clone copies the structure but not the data of a DataTable. - ''' + ''' + ''' The list of names of the value fields for strTableName. + '''//////////////////////////////////////////////////////////////////////////////////////////////////// + Public Sub SetValueFields(iEnumerableNewValueFields As IEnumerable(Of String)) + lstValueFields = iEnumerableNewValueFields.ToList() + End Sub + + '''//////////////////////////////////////////////////////////////////////////////////////////////////// + ''' + ''' Initialises dtbWriteTable as a clone of dtbReadTable. This should be called + ''' once dtbReadTable has been set. + ''' + ''' + ''' .Clone copies the structure but not the data of a DataTable. + '''//////////////////////////////////////////////////////////////////////////////////////////////////// Private Sub InitialiseWriteTable() If dtbReadTable IsNot Nothing Then dtbWriteTable = dtbReadTable.Clone() @@ -111,11 +133,15 @@ Public Class DataStructure End If End Sub + '''//////////////////////////////////////////////////////////////////////////////////////////////////// ''' - ''' Gets the comments associated with strTableName and stores them in dtbComments. + ''' Sets the comments associated with strTableName and stores them in dtbComments. ''' This may be called when getting the main data, or may only be called on demand. ''' - ''' Boolean, if True all versions returned, if False only the current comment for each id is returned. + ''' + ''' (Optional) Boolean, if True all versions returned, if + ''' False only the current comment for each id is returned. + '''//////////////////////////////////////////////////////////////////////////////////////////////////// Private Sub SetCommentsTable(Optional bAllVersions As Boolean = False) ' SQL command, all versions: ' SELECT * FROM c5_comments WHERE table_id=@strTableName @@ -137,10 +163,12 @@ Public Class DataStructure ' WHERE b.current Is NULL AND m.table_id=@strTableName End Sub + '''//////////////////////////////////////////////////////////////////////////////////////////////////// ''' - ''' Gets the events associated with strTableName and stores them in dtbEvents. - ''' This may be called when getting the main data, or may only be called on demand. + ''' Gets the events associated with strTableName and stores them in dtbEvents. This + ''' may be called when getting the main data, or may only be called on demand. ''' + '''//////////////////////////////////////////////////////////////////////////////////////////////////// Private Sub SetEventsTable() ' SQL command, current only: ' @@ -159,12 +187,16 @@ Public Class DataStructure ' ON e_e.event_id=e.id End Sub + '''//////////////////////////////////////////////////////////////////////////////////////////////////// ''' - ''' Constructs an SQL query to get only the current versions from a table. - ''' (Could be moved to a separate class for query construction) + ''' Constructs an SQL query to get only the current versions from a table. (Could be moved to a + ''' separate class for query construction) ''' - ''' A string, the name of the table to query. - ''' + ''' + ''' A string, the name of the table to query. + ''' + ''' The current versions query. + '''//////////////////////////////////////////////////////////////////////////////////////////////////// Private Function GetCurrentVersionsQuery(strTable As String) As String ' From here: https://stackoverflow.com/questions/612231/how-can-i-select-rows-with-maxcolumn-value-distinct-by-another-column-in-sql @@ -185,17 +217,31 @@ Public Class DataStructure ' WHERE b.@strCurrent Is NULL End Function + '''//////////////////////////////////////////////////////////////////////////////////////////////////// ''' ''' Constructs an SQL query to get only the versions equal to iVersion from a table. ''' (Could be moved to a separate class for query construction) ''' - ''' A string, the name of the table to query. - ''' An integer, the version numbers to extract for each id. - ''' + ''' + ''' Danny, 12/03/2020. + ''' + ''' A string, the name of the table to query. + ''' An integer, the version numbers to extract for each id. + ''' + ''' An SQL query string. + '''//////////////////////////////////////////////////////////////////////////////////////////////////// Private Function GetVersionNumberQuery(strTable As String, iVersion As Integer) As String End Function + '''//////////////////////////////////////////////////////////////////////////////////////////////////// + ''' Creates a new action in the action table and returns its Action ID. + ''' + ''' Identifier for the action type. + ''' Identifier for the operator. + ''' + ''' The Action ID for the new action added. + '''//////////////////////////////////////////////////////////////////////////////////////////////////// Private Function NewAction(iActionTypeID As Integer, strOperatorID As String) As Integer Dim iActionID As Integer ' create new entry in action table using iActionTypeID, strOperatorID and current date-time @@ -203,6 +249,43 @@ Public Class DataStructure Return iActionID End Function + '''//////////////////////////////////////////////////////////////////////////////////////////////////// + ''' + ''' Updates dtbUpdateTable to add a new record to strTableName. + ''' This is only sent to the database when DoAction is called. + ''' + '''//////////////////////////////////////////////////////////////////////////////////////////////////// + Public Sub DoAddRecord(dctValues As Dictionary(Of String, Object)) + Dim lstValues As New List(Of Object) + Dim objTemp As Object = Nothing + + For Each strField As String In lstValueFields + If dctValues.TryGetValue(strField, objTemp) Then + lstValues.Add(objTemp) + Else + MsgBox("Developer error in clsDataStructure:DoAddRecord. No value specified for field: " & strField & " when attempting to add a record.") + Exit Sub + End If + Next + + ' This adds a row for the fields named: + ' strId, strVersionNumber, lstValueFields, strCurrent, strVOld, strUpdateType + ' The value for strId is DBNull.Value since this will be auto incremented in the database. + ' The order is important. + ' TODO: If audit table will also use this then it will be to be adapted as it does not have the same id, version number, current + dtbWriteTable.Rows.Add(DBNull.Value, 1, lstValueFields.ToArray, 1, DBNull.Value, GlobalVariables.UpdateType.NewRecord) + 'TODO Need to get back ID added for audit table then correct it in dtbWriteTable + ' Possible using IDENTITY https://www.sqlservertutorial.net/sql-server-basics/sql-server-identity/ + End Sub + + '''//////////////////////////////////////////////////////////////////////////////////////////////////// + ''' Executes the action operation. + ''' + ''' The ID of the action type by run. + ''' The ID of the operator performing the action. + ''' The DataStructure containing changes that form the action. + ''' (Optional) An comment on the action. + '''//////////////////////////////////////////////////////////////////////////////////////////////////// Public Sub DoAction(iActionTypeID As Integer, strOperatorID As String, DataStruct As DataStructure, Optional strActionComment As String = "") Dim iActionID As Integer @@ -214,28 +297,42 @@ Public Class DataStructure DataStruct.UpdateTable(iActionTypeID) End Sub + '''//////////////////////////////////////////////////////////////////////////////////////////////////// + ''' Test for update table. + '''//////////////////////////////////////////////////////////////////////////////////////////////////// Public Sub TestUpdateTable() dtbReadTable = New DataTable dtbReadTable.Columns.Add(strId, GetType(Integer)) - dtbReadTable.Columns.Add(strVersionNumber, GetType(Integer)) + dtbReadTable.Columns.Add(GlobalVariables.strVersionNumber, GetType(Integer)) dtbReadTable.Columns.Add("value", GetType(String)) dtbReadTable.Columns.Add(strCurrent, GetType(Integer)) dtbReadTable.Rows.Add(1, 1, "a", 1) dtbReadTable.Rows.Add(2, 1, "b", 1) dtbReadTable.Rows.Add(3, 1, "c", 1) - dtbReadTable.Rows.Add(4, 1, "d", 1) + dtbReadTable.Rows.Add(4, 1, "dd", 1) + dtbReadTable.Rows.Add(4, 2, "ddd", 2) + dtbReadTable.Rows.Add(5, 1, "e", 1) dtbWriteTable = dtbReadTable.Clone() dtbWriteTable.Columns.Add(strVOld, GetType(Integer)) dtbWriteTable.Columns.Add(strUpdateType, GetType(Integer)) - dtbWriteTable.Rows.Add(1, 2, "A", 1, 1, Enumerations.UpdateType.Correction) - dtbWriteTable.Rows.Add(3, 1, "c", DBNull.Value, 1, Enumerations.UpdateType.Delete) - dtbWriteTable.Rows.Add(4, 2, "dd", 2, 1, Enumerations.UpdateType.EventChange) + dtbWriteTable.Rows.Add(1, 1, "a", DBNull.Value, 1, GlobalVariables.UpdateType.Correction) + dtbWriteTable.Rows.Add(1, 2, "A", 1, 1, GlobalVariables.UpdateType.Correction) + dtbWriteTable.Rows.Add(2, 2, "bb", 2, 1, GlobalVariables.UpdateType.EventChange) + dtbWriteTable.Rows.Add(3, 1, "c", DBNull.Value, 1, GlobalVariables.UpdateType.Delete) + dtbWriteTable.Rows.Add(4, 3, "d", 1, DBNull.Value, GlobalVariables.UpdateType.EventChange) + dtbWriteTable.Rows.Add(4, 1, "dd", 2, 1, GlobalVariables.UpdateType.EventChange) + dtbWriteTable.Rows.Add(4, 2, "ddd", 3, 1, GlobalVariables.UpdateType.EventChange) UpdateTable(1) End Sub + '''//////////////////////////////////////////////////////////////////////////////////////////////////// + ''' Performs the updates to the tables in this DataStructure associated with the action with id iActionID. + ''' + ''' The ID of the action being performed. + '''//////////////////////////////////////////////////////////////////////////////////////////////////// Public Sub UpdateTable(iActionID As Integer) Dim dtbUpdateTable As DataTable Dim dtbAuditTable As DataTable @@ -256,7 +353,7 @@ Public Class DataStructure dtbWriteTableCopy.Columns.Remove(strVOld) dtbWriteTableCopy.Columns.Remove(strUpdateType) ' 2. Make a blank audit table to add entries to - ' dtbAuditTable = + dtbAuditTable = GlobalVariables.dtbEmptyAuditTable ' 3. Loop through the write table and: For i As Integer = 0 To dtbWriteTable.Rows.Count - 1 ' a) read the update type from the write table @@ -267,26 +364,32 @@ Public Class DataStructure dtbUpdateTable.ImportRow(dtbWriteTableCopy.Rows(i)) rowNewWrite = dtbUpdateTable.Rows(dtbUpdateTable.Rows.Count - 1) ' c) add a row for an edit to the read table if neccessary - ' Only a correction needs an edit to the read data (a change for a delete is in the write table already) - If iUpdateType = Enumerations.UpdateType.Correction Then - strSelectExp = strId & " = " & dtbWriteTable.Rows(i).Field(Of Integer)(strId) & " and " & strVersionNumber & " = " & dtbWriteTable.Rows(i).Field(Of Integer)(strVOld) - drowsReadRows = dtbReadTable.Select(strSelectExp) - If drowsReadRows.Count = 1 Then - rowReadRow = drowsReadRows(0) - dtbUpdateTable.ImportRow(rowReadRow) - rowUpdateReadRow = dtbUpdateTable.Rows(dtbUpdateTable.Rows.Count - 1) - If iUpdateType = Enumerations.UpdateType.Correction Then - rowUpdateReadRow.Item(strCurrent) = DBNull.Value - ElseIf iUpdateType = Enumerations.UpdateType.EventChange Then - rowUpdateReadRow.Item(strCurrent) = rowUpdateReadRow.Field(Of Integer)(strCurrent) + 1 + ' (a change for a delete is in the write table already) + Select Case iUpdateType + Case GlobalVariables.UpdateType.NewRecord + 'TODO How will we know the "id" of a new record + GlobalVariables.AddToAuditTable(dtbAuditTable, iActionID, strTableName, , Nothing, 1) + Case GlobalVariables.UpdateType.Correction, GlobalVariables.UpdateType.EventChange + strSelectExp = strId & " = " & dtbWriteTable.Rows(i).Field(Of Integer)(strId) & " and " & GlobalVariables.strVersionNumber & " = " & dtbWriteTable.Rows(i).Field(Of Integer)(strVOld) + drowsReadRows = dtbReadTable.Select(strSelectExp) + If drowsReadRows.Count = 1 Then + rowReadRow = drowsReadRows(0) + dtbUpdateTable.ImportRow(rowReadRow) + rowUpdateReadRow = dtbUpdateTable.Rows(dtbUpdateTable.Rows.Count - 1) + If iUpdateType = GlobalVariables.UpdateType.Correction Then + rowUpdateReadRow.Item(strCurrent) = DBNull.Value + ElseIf iUpdateType = GlobalVariables.UpdateType.EventChange Then + rowUpdateReadRow.Item(strCurrent) = rowUpdateReadRow.Field(Of Integer)(strCurrent) + 1 + End If + GlobalVariables.AddToAuditTable(dtbAuditTable, iActionID, strTableName, dtbWriteTable.Rows(i).Field(Of Integer)(strId), dtbWriteTable.Rows(i).Field(Of Integer)(strVOld), dtbWriteTable.Rows(i).Field(Of Integer)(GlobalVariables.strVersionNumber)) + Else + ' Error? Should always be 1 row because id and version number form a key End If - Else - ' Error? Should always be 1 row because id and version number form a key - End If + End Select + If iUpdateType = GlobalVariables.UpdateType.Correction Then End If ' d) produce the audit record to be added to the audit write table Next - Console.Write(dtbUpdateTable) ' 4. Write: update table, audit table, comments table, events table ' 5. Call UpdateTable for each lstLinkedDataStructures @@ -295,16 +398,14 @@ Public Class DataStructure Next End Sub - ' Need function which takes these two tables and generates the 'add' and 'update' tables/commands - ' When adding, only need to update current field of existing records - - ' produce table from the read and write table that will corresponds to the 'add's and 'update's of that table - ' produce the audit records to be added - + '''//////////////////////////////////////////////////////////////////////////////////////////////////// ''' - ''' Takes dtbReadTable and dtbWriteTable and produces a DataTable with the correct updates and inserts to be added to the database. + ''' Takes dtbReadTable and dtbWriteTable and produces a DataTable with the + ''' correct updates and inserts to be added to the database. ''' - ''' A DataTable with + ''' + ''' A DataTable with. + '''//////////////////////////////////////////////////////////////////////////////////////////////////// Private Function GenerateTableForUpdate() As DataTable End Function diff --git a/ClimsoftVer4/ClimsoftVer4/clsGlobalVariables.vb b/ClimsoftVer4/ClimsoftVer4/clsGlobalVariables.vb new file mode 100644 index 00000000..616e12cf --- /dev/null +++ b/ClimsoftVer4/ClimsoftVer4/clsGlobalVariables.vb @@ -0,0 +1,93 @@ +'--------------------------------------------------------------------------------------------------- +' file: clsGlobalVariables.vb +' +' summary: GlobalVariables class +'--------------------------------------------------------------------------------------------------- + +'''//////////////////////////////////////////////////////////////////////////////////////////////////// +''' Global Variables. +'''//////////////////////////////////////////////////////////////////////////////////////////////////// + +Public Class GlobalVariables + + ''' + ''' A string, how the "version number" column is stored in the database in all tables it appears in. + ''' This could be moved to a separate class of constants. + ''' + Public Shared ReadOnly strVersionNumber As String = "version_number" + + '''//////////////////////////////////////////////////////////////////////////////////////////////////// + ''' Values that represent update types. + '''//////////////////////////////////////////////////////////////////////////////////////////////////// + + Public Enum UpdateType + ''' An enum constant representing the new record option. When a new record is added. + NewRecord + ''' An enum constant representing the correction option. When a correction is made to an existing record. + Correction + ''' An enum constant representing the event change option. When a change is made to an event that is not a correction e.g. another event is added before this resulting in a change to its current. + EventChange + ''' An enum constant representing the event new option. When a new value is added to an existing record corresponding to a new event. + EventNew + ''' An enum constant representing the delete option. When a record is deleted. + Delete + End Enum + + '''//////////////////////////////////////////////////////////////////////////////////////////////////// + ''' Gets the dtb empty audit table. + ''' + ''' The dtb empty audit table. + '''//////////////////////////////////////////////////////////////////////////////////////////////////// + + Public Shared ReadOnly Property dtbEmptyAuditTable As DataTable + Get + Dim dtbAudit As New DataTable + + dtbAudit.Columns.Add("id", GetType(Integer)) + dtbAudit.Columns.Add("action_id", GetType(Integer)) + dtbAudit.Columns.Add("table", GetType(String)) + dtbAudit.Columns.Add("entry_id", GetType(Integer)) + dtbAudit.Columns.Add("version_old", GetType(Integer)) + dtbAudit.Columns.Add("version_new", GetType(Integer)) + Return dtbAudit + End Get + End Property + + '''//////////////////////////////////////////////////////////////////////////////////////////////////// + ''' Adds to audit table. + ''' + ''' Danny, 12/03/2020. + ''' + ''' The dtb audit. + ''' Identifier for the action. + ''' The table. + ''' Identifier for the entry. + ''' Zero-based index of the version old. + ''' Zero-based index of the version new. + ''' (Optional) Zero-based index of the identifier. + '''//////////////////////////////////////////////////////////////////////////////////////////////////// + + Public Shared Sub AddToAuditTable(dtbAudit As DataTable, iActionId As Integer, strTable As String, iEntryId As Integer, iVersionOld As Nullable(Of Integer), iVersionNew As Nullable(Of Integer), Optional iId As Nullable(Of Integer) = Nothing) + Dim dtbAuditSchema As DataTable + Dim objParams As New List(Of Object) + + dtbAuditSchema = dtbEmptyAuditTable + If dtbAudit.Columns.Count <> dtbAuditSchema.Columns.Count Then + MsgBox("Developer error in GlobalVariables.AddToAuditTable: Cannot add records to audit table. Audit table provided does not match audit table schema (incorrect number of columns).", MsgBoxStyle.Information, Title:="Developer error") + Exit Sub + End If + For i As Integer = 0 To dtbAudit.Columns.Count - 1 + If dtbAudit.Columns(i).ColumnName <> dtbAuditSchema.Columns(i).ColumnName OrElse dtbAudit.Columns(i).DataType <> dtbAuditSchema.Columns(i).DataType Then + MsgBox("Developer error in GlobalVariables.AddToAuditTable: Cannot add records to audit table. Audit table provided does not match audit table schema (incorrect column name/type).", MsgBoxStyle.Information, Title:="Developer error") + Exit Sub + End If + Next + objParams.Add(If(iId Is Nothing, DBNull.Value, iId)) + objParams.Add(iActionId) + objParams.Add(strTable) + objParams.Add(iEntryId) + objParams.Add(If(iVersionOld Is Nothing, DBNull.Value, iVersionOld)) + objParams.Add(If(iVersionNew Is Nothing, DBNull.Value, iVersionNew)) + dtbAudit.Rows.Add(objParams.ToArray) + End Sub +End Class \ No newline at end of file From f072bc14990a392c8a02903c13831e28771b8d3c Mon Sep 17 00:00:00 2001 From: Danny Parsons Date: Thu, 19 Mar 2020 17:38:51 +0000 Subject: [PATCH 4/6] organised methods, completed doevent method --- ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb | 211 ++++++++++-------- .../ClimsoftVer4/clsGlobalVariables.vb | 58 ++++- ClimsoftVer4/ClimsoftVer4/frmLogin.vb | 2 - 3 files changed, 171 insertions(+), 100 deletions(-) diff --git a/ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb b/ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb index 0e2cbf72..95532a6c 100644 --- a/ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb +++ b/ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb @@ -187,53 +187,6 @@ Public Class DataStructure ' ON e_e.event_id=e.id End Sub - '''//////////////////////////////////////////////////////////////////////////////////////////////////// - ''' - ''' Constructs an SQL query to get only the current versions from a table. (Could be moved to a - ''' separate class for query construction) - ''' - ''' - ''' A string, the name of the table to query. - ''' - ''' The current versions query. - '''//////////////////////////////////////////////////////////////////////////////////////////////////// - Private Function GetCurrentVersionsQuery(strTable As String) As String - ' From here: https://stackoverflow.com/questions/612231/how-can-i-select-rows-with-maxcolumn-value-distinct-by-another-column-in-sql - - ' Option 1: - ' ######### - ' Select Case m.* From @strTable m - ' INNER Join(SELECT id, MAX(@strCurrent) As max_current FROM @strTable GROUP BY id) m_group - ' ON m.id=m_group.id - ' AND m.@strCurrent=m_group.max_current - - ' Option 2: - ' ######### - ' SELECT m.* - ' FROM @strTable m - ' LEFT JOIN @strTable b - ' ON m.id = b.id - ' AND m.@strCurrent < b.@strCurrent - ' WHERE b.@strCurrent Is NULL - End Function - - '''//////////////////////////////////////////////////////////////////////////////////////////////////// - ''' - ''' Constructs an SQL query to get only the versions equal to iVersion from a table. - ''' (Could be moved to a separate class for query construction) - ''' - ''' - ''' Danny, 12/03/2020. - ''' - ''' A string, the name of the table to query. - ''' An integer, the version numbers to extract for each id. - ''' - ''' An SQL query string. - '''//////////////////////////////////////////////////////////////////////////////////////////////////// - Private Function GetVersionNumberQuery(strTable As String, iVersion As Integer) As String - - End Function - '''//////////////////////////////////////////////////////////////////////////////////////////////////// ''' Creates a new action in the action table and returns its Action ID. ''' @@ -246,6 +199,7 @@ Public Class DataStructure Dim iActionID As Integer ' create new entry in action table using iActionTypeID, strOperatorID and current date-time ' get back action ID for new entry created + ' This will be done manually initially: find max ActionId in the action table and increment. Return iActionID End Function @@ -254,6 +208,11 @@ Public Class DataStructure ''' Updates dtbUpdateTable to add a new record to strTableName. ''' This is only sent to the database when DoAction is called. ''' + ''' + ''' A dictionary of values for the new record. The keys are the field names. + ''' This does not include the indentifiers and current fields. Their values are + ''' calculated automatically. + ''' '''//////////////////////////////////////////////////////////////////////////////////////////////////// Public Sub DoAddRecord(dctValues As Dictionary(Of String, Object)) Dim lstValues As New List(Of Object) @@ -274,8 +233,93 @@ Public Class DataStructure ' The order is important. ' TODO: If audit table will also use this then it will be to be adapted as it does not have the same id, version number, current dtbWriteTable.Rows.Add(DBNull.Value, 1, lstValueFields.ToArray, 1, DBNull.Value, GlobalVariables.UpdateType.NewRecord) - 'TODO Need to get back ID added for audit table then correct it in dtbWriteTable - ' Possible using IDENTITY https://www.sqlservertutorial.net/sql-server-basics/sql-server-identity/ + End Sub + + '''//////////////////////////////////////////////////////////////////////////////////////////////////// + ''' + ''' Adds two rows to dtbUpdateTable to indicate a correction to a record. + ''' One row is to modify the current of the existing row. The other is the new row for the + ''' correction. + ''' This is only sent to the database when DoAction is called. + ''' + ''' A collection of DataRows for all event changes. These rows should + ''' have the same structure as dtbReadTable. + ''' This may be nothing if there are no event changes. + ''' + ''' A DataRow for a new event. This row should have the same structure + ''' as dtbReadTable.This may be nothing if there is no new event. + ''' + '''//////////////////////////////////////////////////////////////////////////////////////////////////// + Public Sub DoEvent(Optional rcEventChanges As DataRowCollection = Nothing, Optional rowEventNew As DataRow = Nothing) + Dim rowNew As DataRow + + ' Add the rows for the event changes. + If rcEventChanges IsNot Nothing Then + For Each rowCurrent As DataRow In rcEventChanges + rowNew = dtbWriteTable.NewRow() + For i = 0 To rowCurrent.ItemArray.Count - 1 + rowNew.Item(i) = rowCurrent.ItemArray(i) + Next + rowNew.Item(strVOld) = DBNull.Value + rowNew.Item(strUpdateType) = GlobalVariables.UpdateType.EventChange + Next + End If + + ' Add a row for the new event + If rowEventNew IsNot Nothing Then + rowNew = dtbWriteTable.NewRow() + For i = 0 To rowEventNew.ItemArray.Count - 1 + rowNew.Item(i) = rowEventNew.ItemArray(i) + Next + rowNew.Item(strVOld) = DBNull.Value + rowNew.Item(strUpdateType) = GlobalVariables.UpdateType.EventNew + End If + End Sub + + '''//////////////////////////////////////////////////////////////////////////////////////////////////// + ''' + ''' Adds two rows to dtbUpdateTable to indicate a correction to a record. + ''' One row is to modify the current of the existing row. The other is the new row for the + ''' correction. + ''' This is only sent to the database when DoAction is called. + ''' + ''' The DataRow to be corrected. + ''' A dictionary of values for the correction. The keys are the field names. + '''//////////////////////////////////////////////////////////////////////////////////////////////////// + Public Sub DoCorrectionToRecord(rowCurrent As DataRow, dctValues As Dictionary(Of String, Object)) + Dim lstValues As New List(Of Object) + Dim objTemp As Object = Nothing + Dim rowCurrentUpdate As DataRow + + ' Add the row for the correction to the existing row. The only change is current set to NULL. + rowCurrentUpdate = dtbWriteTable.NewRow() + For i = 0 To rowCurrent.ItemArray.Count - 1 + rowCurrentUpdate.Item(i) = rowCurrent.ItemArray(i) + Next + rowCurrentUpdate.Item(strCurrent) = DBNull.Value + rowCurrentUpdate.Item(strVOld) = rowCurrentUpdate.Item(GlobalVariables.strVersionNumber) + rowCurrentUpdate.Item(strUpdateType) = GlobalVariables.UpdateType.Correction + + ' TODO Should be moved to separate function as duplicated. + For Each strField As String In lstValueFields + If dctValues.TryGetValue(strField, objTemp) Then + lstValues.Add(objTemp) + Else + MsgBox("Developer error in clsDataStructure:DoAddRecord. No value specified for field: " & strField & " when attempting to add a record.") + Exit Sub + End If + Next + + ' This adds a row for the correction. + ' The fields are named: + ' strId, strVersionNumber, lstValueFields, strCurrent, strVOld, strUpdateType + ' strId is the same as in rowCurrent. + ' strVersionNumber is the existing version number + 1 + ' strCurrent is the existing current + ' strVOld is the existing version number + ' strUpdateType is Correction + ' The order is important. + dtbWriteTable.Rows.Add(rowCurrent.Field(Of Integer)(strId), rowCurrent.Field(Of Integer)(GlobalVariables.strVersionNumber) + 1, lstValueFields.ToArray, rowCurrent.Field(Of Integer)(strCurrent), rowCurrent.Field(Of Integer)(GlobalVariables.strVersionNumber), GlobalVariables.UpdateType.Correction) End Sub '''//////////////////////////////////////////////////////////////////////////////////////////////////// @@ -297,37 +341,6 @@ Public Class DataStructure DataStruct.UpdateTable(iActionTypeID) End Sub - '''//////////////////////////////////////////////////////////////////////////////////////////////////// - ''' Test for update table. - '''//////////////////////////////////////////////////////////////////////////////////////////////////// - Public Sub TestUpdateTable() - dtbReadTable = New DataTable - dtbReadTable.Columns.Add(strId, GetType(Integer)) - dtbReadTable.Columns.Add(GlobalVariables.strVersionNumber, GetType(Integer)) - dtbReadTable.Columns.Add("value", GetType(String)) - dtbReadTable.Columns.Add(strCurrent, GetType(Integer)) - - dtbReadTable.Rows.Add(1, 1, "a", 1) - dtbReadTable.Rows.Add(2, 1, "b", 1) - dtbReadTable.Rows.Add(3, 1, "c", 1) - dtbReadTable.Rows.Add(4, 1, "dd", 1) - dtbReadTable.Rows.Add(4, 2, "ddd", 2) - dtbReadTable.Rows.Add(5, 1, "e", 1) - - dtbWriteTable = dtbReadTable.Clone() - dtbWriteTable.Columns.Add(strVOld, GetType(Integer)) - dtbWriteTable.Columns.Add(strUpdateType, GetType(Integer)) - - dtbWriteTable.Rows.Add(1, 1, "a", DBNull.Value, 1, GlobalVariables.UpdateType.Correction) - dtbWriteTable.Rows.Add(1, 2, "A", 1, 1, GlobalVariables.UpdateType.Correction) - dtbWriteTable.Rows.Add(2, 2, "bb", 2, 1, GlobalVariables.UpdateType.EventChange) - dtbWriteTable.Rows.Add(3, 1, "c", DBNull.Value, 1, GlobalVariables.UpdateType.Delete) - dtbWriteTable.Rows.Add(4, 3, "d", 1, DBNull.Value, GlobalVariables.UpdateType.EventChange) - dtbWriteTable.Rows.Add(4, 1, "dd", 2, 1, GlobalVariables.UpdateType.EventChange) - dtbWriteTable.Rows.Add(4, 2, "ddd", 3, 1, GlobalVariables.UpdateType.EventChange) - UpdateTable(1) - End Sub - '''//////////////////////////////////////////////////////////////////////////////////////////////////// ''' Performs the updates to the tables in this DataStructure associated with the action with id iActionID. ''' @@ -367,8 +380,8 @@ Public Class DataStructure ' (a change for a delete is in the write table already) Select Case iUpdateType Case GlobalVariables.UpdateType.NewRecord - 'TODO How will we know the "id" of a new record - GlobalVariables.AddToAuditTable(dtbAuditTable, iActionID, strTableName, , Nothing, 1) + 'TODO Need to get the entry "id" of the new record to be added + 'GlobalVariables.AddToAuditTable(dtbAuditTable, iActionID, strTableName, , Nothing, 1) Case GlobalVariables.UpdateType.Correction, GlobalVariables.UpdateType.EventChange strSelectExp = strId & " = " & dtbWriteTable.Rows(i).Field(Of Integer)(strId) & " and " & GlobalVariables.strVersionNumber & " = " & dtbWriteTable.Rows(i).Field(Of Integer)(strVOld) drowsReadRows = dtbReadTable.Select(strSelectExp) @@ -399,17 +412,33 @@ Public Class DataStructure End Sub '''//////////////////////////////////////////////////////////////////////////////////////////////////// - ''' - ''' Takes dtbReadTable and dtbWriteTable and produces a DataTable with the - ''' correct updates and inserts to be added to the database. - ''' - ''' - ''' A DataTable with. + ''' Test for update table. '''//////////////////////////////////////////////////////////////////////////////////////////////////// - Private Function GenerateTableForUpdate() As DataTable + Public Sub TestUpdateTable() + dtbReadTable = New DataTable + dtbReadTable.Columns.Add(strId, GetType(Integer)) + dtbReadTable.Columns.Add(GlobalVariables.strVersionNumber, GetType(Integer)) + dtbReadTable.Columns.Add("value", GetType(String)) + dtbReadTable.Columns.Add(strCurrent, GetType(Integer)) - End Function + dtbReadTable.Rows.Add(1, 1, "a", 1) + dtbReadTable.Rows.Add(2, 1, "b", 1) + dtbReadTable.Rows.Add(3, 1, "c", 1) + dtbReadTable.Rows.Add(4, 1, "dd", 1) + dtbReadTable.Rows.Add(4, 2, "ddd", 2) + dtbReadTable.Rows.Add(5, 1, "e", 1) - ' UpdateData() adds, updates and deletes data + dtbWriteTable = dtbReadTable.Clone() + dtbWriteTable.Columns.Add(strVOld, GetType(Integer)) + dtbWriteTable.Columns.Add(strUpdateType, GetType(Integer)) + dtbWriteTable.Rows.Add(1, 1, "a", DBNull.Value, 1, GlobalVariables.UpdateType.Correction) + dtbWriteTable.Rows.Add(1, 2, "A", 1, 1, GlobalVariables.UpdateType.Correction) + dtbWriteTable.Rows.Add(2, 2, "bb", 2, 1, GlobalVariables.UpdateType.EventChange) + dtbWriteTable.Rows.Add(3, 1, "c", DBNull.Value, 1, GlobalVariables.UpdateType.Delete) + dtbWriteTable.Rows.Add(4, 3, "d", 1, DBNull.Value, GlobalVariables.UpdateType.EventChange) + dtbWriteTable.Rows.Add(4, 1, "dd", 2, 1, GlobalVariables.UpdateType.EventChange) + dtbWriteTable.Rows.Add(4, 2, "ddd", 3, 1, GlobalVariables.UpdateType.EventChange) + UpdateTable(1) + End Sub End Class diff --git a/ClimsoftVer4/ClimsoftVer4/clsGlobalVariables.vb b/ClimsoftVer4/ClimsoftVer4/clsGlobalVariables.vb index 616e12cf..b56c6fb7 100644 --- a/ClimsoftVer4/ClimsoftVer4/clsGlobalVariables.vb +++ b/ClimsoftVer4/ClimsoftVer4/clsGlobalVariables.vb @@ -56,18 +56,13 @@ Public Class GlobalVariables '''//////////////////////////////////////////////////////////////////////////////////////////////////// ''' Adds to audit table. ''' - ''' Danny, 12/03/2020. - ''' ''' The dtb audit. - ''' Identifier for the action. ''' The table. ''' Identifier for the entry. ''' Zero-based index of the version old. ''' Zero-based index of the version new. - ''' (Optional) Zero-based index of the identifier. '''//////////////////////////////////////////////////////////////////////////////////////////////////// - - Public Shared Sub AddToAuditTable(dtbAudit As DataTable, iActionId As Integer, strTable As String, iEntryId As Integer, iVersionOld As Nullable(Of Integer), iVersionNew As Nullable(Of Integer), Optional iId As Nullable(Of Integer) = Nothing) + Public Shared Sub AddToAuditTable(dtbAudit As DataTable, iActionID As Integer, strTable As String, iEntryId As Integer, iVersionOld As Nullable(Of Integer), iVersionNew As Nullable(Of Integer)) Dim dtbAuditSchema As DataTable Dim objParams As New List(Of Object) @@ -82,7 +77,8 @@ Public Class GlobalVariables Exit Sub End If Next - objParams.Add(If(iId Is Nothing, DBNull.Value, iId)) + ' id can be blank since it is unique and can be set to auto increment in the database + objParams.Add(DBNull.Value) objParams.Add(iActionId) objParams.Add(strTable) objParams.Add(iEntryId) @@ -90,4 +86,52 @@ Public Class GlobalVariables objParams.Add(If(iVersionNew Is Nothing, DBNull.Value, iVersionNew)) dtbAudit.Rows.Add(objParams.ToArray) End Sub + + '''//////////////////////////////////////////////////////////////////////////////////////////////////// + ''' + ''' Constructs an SQL query to get only the current versions from a table. (Could be moved to a + ''' separate class for query construction) + ''' + ''' + ''' A string, the name of the table to query. + ''' + ''' The current versions query. + '''//////////////////////////////////////////////////////////////////////////////////////////////////// + Private Function GetCurrentVersionsQuery(strTable As String) As String + ' From here: https://stackoverflow.com/questions/612231/how-can-i-select-rows-with-maxcolumn-value-distinct-by-another-column-in-sql + + ' Option 1: + ' ######### + ' Select Case m.* From @strTable m + ' INNER Join(SELECT id, MAX(@strCurrent) As max_current FROM @strTable GROUP BY id) m_group + ' ON m.id=m_group.id + ' AND m.@strCurrent=m_group.max_current + + ' Option 2: + ' ######### + ' SELECT m.* + ' FROM @strTable m + ' LEFT JOIN @strTable b + ' ON m.id = b.id + ' AND m.@strCurrent < b.@strCurrent + ' WHERE b.@strCurrent Is NULL + End Function + + '''//////////////////////////////////////////////////////////////////////////////////////////////////// + ''' + ''' Constructs an SQL query to get only the versions equal to iVersion from a table. + ''' (Could be moved to a separate class for query construction) + ''' + ''' + ''' Danny, 12/03/2020. + ''' + ''' A string, the name of the table to query. + ''' An integer, the version numbers to extract for each id. + ''' + ''' An SQL query string. + '''//////////////////////////////////////////////////////////////////////////////////////////////////// + Private Function GetVersionNumberQuery(strTable As String, iVersion As Integer) As String + + End Function + End Class \ No newline at end of file diff --git a/ClimsoftVer4/ClimsoftVer4/frmLogin.vb b/ClimsoftVer4/ClimsoftVer4/frmLogin.vb index 7100051b..68a244ae 100644 --- a/ClimsoftVer4/ClimsoftVer4/frmLogin.vb +++ b/ClimsoftVer4/ClimsoftVer4/frmLogin.vb @@ -211,8 +211,6 @@ Public Class frmLogin Dim datstruct As New DataStructure - datstruct.TestUpdateTable() - msgKeyentryFormsListUpdated = "List of key-entry forms updated!" msgStationInformationNotFound = "Station information Not found. Please add station information And try again!" From 346357033ce3b103bfc7c2fe7c6f56f63992a0f3 Mon Sep 17 00:00:00 2001 From: Danny Parsons Date: Thu, 19 Mar 2020 21:50:02 +0000 Subject: [PATCH 5/6] completed updatetable, enhanced test method --- ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb | 226 +++++++++++------- .../ClimsoftVer4/clsGlobalVariables.vb | 6 +- ClimsoftVer4/ClimsoftVer4/frmLogin.vb | 2 - 3 files changed, 146 insertions(+), 88 deletions(-) diff --git a/ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb b/ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb index 95532a6c..683b5b61 100644 --- a/ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb +++ b/ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb @@ -49,9 +49,10 @@ Public Class DataStructure ''' ''' A DataTable storing the new data to be written to the database. - ''' This builds up as controls change. + ''' This builds up as controls change. It is a clone of dtbReadTable + ''' with two extra columns: v_old and update_type ''' - Private dtbWriteTable As DataTable + Private dtbUpdateTable As DataTable ''' ''' A DataTable storing new comments associated to strTableName. @@ -69,7 +70,7 @@ Public Class DataStructure ''' ''' This will included DataStructures for existing comments and events associated to strTableName. ''' - Private lstLinkedDataStructures As List(Of DataStructure) + Private lstLinkedDataStructures As New List(Of DataStructure) ''' ''' This event is raised when dtbReadTable changes. @@ -119,17 +120,17 @@ Public Class DataStructure '''//////////////////////////////////////////////////////////////////////////////////////////////////// ''' - ''' Initialises dtbWriteTable as a clone of dtbReadTable. This should be called - ''' once dtbReadTable has been set. + ''' Initialises dtbUpdateTable as a clone of dtbReadTable with two extra columns. + ''' This should be called once dtbReadTable has been set. ''' ''' ''' .Clone copies the structure but not the data of a DataTable. '''//////////////////////////////////////////////////////////////////////////////////////////////////// - Private Sub InitialiseWriteTable() + Private Sub InitialiseUpdateTable() If dtbReadTable IsNot Nothing Then - dtbWriteTable = dtbReadTable.Clone() - dtbWriteTable.Columns.Add(strVOld, GetType(Integer)) - dtbWriteTable.Columns.Add(strUpdateType, GetType(Integer)) + dtbUpdateTable = dtbReadTable.Clone() + dtbUpdateTable.Columns.Add(strVOld, GetType(Integer)) + dtbUpdateTable.Columns.Add(strUpdateType, GetType(Integer)) End If End Sub @@ -232,14 +233,13 @@ Public Class DataStructure ' The value for strId is DBNull.Value since this will be auto incremented in the database. ' The order is important. ' TODO: If audit table will also use this then it will be to be adapted as it does not have the same id, version number, current - dtbWriteTable.Rows.Add(DBNull.Value, 1, lstValueFields.ToArray, 1, DBNull.Value, GlobalVariables.UpdateType.NewRecord) + dtbUpdateTable.Rows.Add(DBNull.Value, 1, lstValueFields.ToArray, 1, DBNull.Value, GlobalVariables.UpdateType.NewRecord) End Sub '''//////////////////////////////////////////////////////////////////////////////////////////////////// ''' - ''' Adds two rows to dtbUpdateTable to indicate a correction to a record. - ''' One row is to modify the current of the existing row. The other is the new row for the - ''' correction. + ''' Adds rows to dtbUpdateTable to indicate a new and/or change to an event which + ''' causes records to change. ''' This is only sent to the database when DoAction is called. ''' ''' A collection of DataRows for all event changes. These rows should @@ -250,29 +250,31 @@ Public Class DataStructure ''' as dtbReadTable.This may be nothing if there is no new event. ''' '''//////////////////////////////////////////////////////////////////////////////////////////////////// - Public Sub DoEvent(Optional rcEventChanges As DataRowCollection = Nothing, Optional rowEventNew As DataRow = Nothing) + Public Sub DoEvent(Optional rcEventChanges() As DataRow = Nothing, Optional rowEventNew As DataRow = Nothing) Dim rowNew As DataRow ' Add the rows for the event changes. If rcEventChanges IsNot Nothing Then For Each rowCurrent As DataRow In rcEventChanges - rowNew = dtbWriteTable.NewRow() + rowNew = dtbUpdateTable.NewRow() For i = 0 To rowCurrent.ItemArray.Count - 1 rowNew.Item(i) = rowCurrent.ItemArray(i) Next rowNew.Item(strVOld) = DBNull.Value rowNew.Item(strUpdateType) = GlobalVariables.UpdateType.EventChange + dtbUpdateTable.Rows.Add(rowNew) Next End If ' Add a row for the new event If rowEventNew IsNot Nothing Then - rowNew = dtbWriteTable.NewRow() + rowNew = dtbUpdateTable.NewRow() For i = 0 To rowEventNew.ItemArray.Count - 1 rowNew.Item(i) = rowEventNew.ItemArray(i) Next rowNew.Item(strVOld) = DBNull.Value rowNew.Item(strUpdateType) = GlobalVariables.UpdateType.EventNew + dtbUpdateTable.Rows.Add(rowNew) End If End Sub @@ -290,15 +292,17 @@ Public Class DataStructure Dim lstValues As New List(Of Object) Dim objTemp As Object = Nothing Dim rowCurrentUpdate As DataRow + Dim lstNewRecordItems As New List(Of Object) ' Add the row for the correction to the existing row. The only change is current set to NULL. - rowCurrentUpdate = dtbWriteTable.NewRow() + rowCurrentUpdate = dtbUpdateTable.NewRow() For i = 0 To rowCurrent.ItemArray.Count - 1 rowCurrentUpdate.Item(i) = rowCurrent.ItemArray(i) Next rowCurrentUpdate.Item(strCurrent) = DBNull.Value rowCurrentUpdate.Item(strVOld) = rowCurrentUpdate.Item(GlobalVariables.strVersionNumber) - rowCurrentUpdate.Item(strUpdateType) = GlobalVariables.UpdateType.Correction + rowCurrentUpdate.Item(strUpdateType) = GlobalVariables.UpdateType.CorrectionOld + dtbUpdateTable.Rows.Add(rowCurrentUpdate) ' TODO Should be moved to separate function as duplicated. For Each strField As String In lstValueFields @@ -319,11 +323,41 @@ Public Class DataStructure ' strVOld is the existing version number ' strUpdateType is Correction ' The order is important. - dtbWriteTable.Rows.Add(rowCurrent.Field(Of Integer)(strId), rowCurrent.Field(Of Integer)(GlobalVariables.strVersionNumber) + 1, lstValueFields.ToArray, rowCurrent.Field(Of Integer)(strCurrent), rowCurrent.Field(Of Integer)(GlobalVariables.strVersionNumber), GlobalVariables.UpdateType.Correction) + lstNewRecordItems.Add(rowCurrent.Field(Of Integer)(strId)) + lstNewRecordItems.Add(rowCurrent.Field(Of Integer)(GlobalVariables.strVersionNumber) + 1) + lstNewRecordItems.AddRange(lstValues.ToArray) + lstNewRecordItems.Add(rowCurrent.Field(Of Integer)(strCurrent)) + lstNewRecordItems.Add(rowCurrent.Field(Of Integer)(GlobalVariables.strVersionNumber)) + lstNewRecordItems.Add(GlobalVariables.UpdateType.CorrectionNew) + dtbUpdateTable.Rows.Add(lstNewRecordItems.ToArray) + End Sub + + '''//////////////////////////////////////////////////////////////////////////////////////////////////// + ''' + ''' Adds a rows to dtbUpdateTable to indicate a deleted record. + ''' This is only sent to the database when DoAction is called. + ''' + ''' The DataRow to be deleted. + '''//////////////////////////////////////////////////////////////////////////////////////////////////// + Public Sub DoDeleteRecord(rowDelete As DataRow) + Dim lstValues As New List(Of Object) + Dim rowDeleteUpdate As DataRow + + ' Add the row for the deletion. + rowDeleteUpdate = dtbUpdateTable.NewRow() + For i = 0 To rowDelete.ItemArray.Count - 1 + rowDeleteUpdate.Item(i) = rowDelete.ItemArray(i) + Next + rowDeleteUpdate.Item(strCurrent) = DBNull.Value + rowDeleteUpdate.Item(strVOld) = rowDeleteUpdate.Item(GlobalVariables.strVersionNumber) + rowDeleteUpdate.Item(strUpdateType) = GlobalVariables.UpdateType.Delete + dtbUpdateTable.Rows.Add(rowDeleteUpdate) End Sub '''//////////////////////////////////////////////////////////////////////////////////////////////////// ''' Executes the action operation. + ''' + ''' This could be moved to a separate class? ''' ''' The ID of the action type by run. ''' The ID of the operator performing the action. @@ -336,91 +370,82 @@ Public Class DataStructure ' create new entry in action table using iActionTypeID, strOperatorID and current date-time ' get back action ID for new entry created iActionID = NewAction(iActionTypeID, strOperatorID) - ' create comment for action comment and add to comment table + ' TODO Create comment for action comment and add to comment table + ' Sends changes from the update table to the database for DataStructures DataStruct.UpdateTable(iActionTypeID) End Sub '''//////////////////////////////////////////////////////////////////////////////////////////////////// - ''' Performs the updates to the tables in this DataStructure associated with the action with id iActionID. + ''' Performs the updates to the tables in this DataStructure associated with + ''' the action with id iActionID. This including writing the audit, comments, + ''' and events. + ''' ''' ''' The ID of the action being performed. '''//////////////////////////////////////////////////////////////////////////////////////////////////// Public Sub UpdateTable(iActionID As Integer) - Dim dtbUpdateTable As DataTable Dim dtbAuditTable As DataTable Dim iUpdateType As Integer - Dim dtbWriteTableCopy As DataTable - Dim rowNewWrite As DataRow - Dim strSelectExp As String - Dim drowsReadRows() As DataRow - Dim rowReadRow As DataRow - Dim rowUpdateReadRow As DataRow - - ' produce table from the read and write table that will corresponds to the 'add's and 'update's of that table - ' STEPS - ' 1. Make the update table as a Clone() of the read table - dtbUpdateTable = dtbReadTable.Clone() - - dtbWriteTableCopy = dtbWriteTable.Copy() - dtbWriteTableCopy.Columns.Remove(strVOld) - dtbWriteTableCopy.Columns.Remove(strUpdateType) - ' 2. Make a blank audit table to add entries to + Dim dtbWriteTable As DataTable + + ' dtbWriteTable will write changes to the database. A copy of dtbUpdateTable with last two columns removed. + dtbWriteTable = Me.dtbUpdateTable.Copy() + dtbWriteTable.Columns.Remove(strVOld) + dtbWriteTable.Columns.Remove(strUpdateType) + + ' Make a blank audit table to add entries to dtbAuditTable = GlobalVariables.dtbEmptyAuditTable - ' 3. Loop through the write table and: - For i As Integer = 0 To dtbWriteTable.Rows.Count - 1 - ' a) read the update type from the write table - iUpdateType = dtbWriteTable.Rows(i).Field(Of Integer)(strUpdateType) - ' b) add a row for each row to the update table - ' (current value should be correct in dtbWriteTable at this point) - ' for a delete, current value will be NULL so the "new" row is actually an update to an existing row - dtbUpdateTable.ImportRow(dtbWriteTableCopy.Rows(i)) - rowNewWrite = dtbUpdateTable.Rows(dtbUpdateTable.Rows.Count - 1) - ' c) add a row for an edit to the read table if neccessary - ' (a change for a delete is in the write table already) + + For i As Integer = 0 To Me.dtbUpdateTable.Rows.Count - 1 + iUpdateType = dtbUpdateTable.Rows(i).Field(Of Integer)(strUpdateType) Select Case iUpdateType Case GlobalVariables.UpdateType.NewRecord - 'TODO Need to get the entry "id" of the new record to be added - 'GlobalVariables.AddToAuditTable(dtbAuditTable, iActionID, strTableName, , Nothing, 1) - Case GlobalVariables.UpdateType.Correction, GlobalVariables.UpdateType.EventChange - strSelectExp = strId & " = " & dtbWriteTable.Rows(i).Field(Of Integer)(strId) & " and " & GlobalVariables.strVersionNumber & " = " & dtbWriteTable.Rows(i).Field(Of Integer)(strVOld) - drowsReadRows = dtbReadTable.Select(strSelectExp) - If drowsReadRows.Count = 1 Then - rowReadRow = drowsReadRows(0) - dtbUpdateTable.ImportRow(rowReadRow) - rowUpdateReadRow = dtbUpdateTable.Rows(dtbUpdateTable.Rows.Count - 1) - If iUpdateType = GlobalVariables.UpdateType.Correction Then - rowUpdateReadRow.Item(strCurrent) = DBNull.Value - ElseIf iUpdateType = GlobalVariables.UpdateType.EventChange Then - rowUpdateReadRow.Item(strCurrent) = rowUpdateReadRow.Field(Of Integer)(strCurrent) + 1 - End If - GlobalVariables.AddToAuditTable(dtbAuditTable, iActionID, strTableName, dtbWriteTable.Rows(i).Field(Of Integer)(strId), dtbWriteTable.Rows(i).Field(Of Integer)(strVOld), dtbWriteTable.Rows(i).Field(Of Integer)(GlobalVariables.strVersionNumber)) - Else - ' Error? Should always be 1 row because id and version number form a key - End If + 'TODO Need to get the entry "id" of the new record to be added - replace 9999 + GlobalVariables.AddToAuditTable(dtbAuditTable, iActionID, strTableName, 9999, Nothing, 1) + Case GlobalVariables.UpdateType.CorrectionOld + ' No audit entry to add + Case GlobalVariables.UpdateType.CorrectionNew + GlobalVariables.AddToAuditTable(dtbAuditTable, iActionID, strTableName, dtbUpdateTable.Rows(i).Field(Of Integer)(strId), dtbUpdateTable.Rows(i).Field(Of Integer)(strVOld), dtbUpdateTable.Rows(i).Field(Of Integer)(GlobalVariables.strVersionNumber)) + Case GlobalVariables.UpdateType.EventChange + ' No audit entry to add + Case GlobalVariables.UpdateType.EventNew + GlobalVariables.AddToAuditTable(dtbAuditTable, iActionID, strTableName, dtbUpdateTable.Rows(i).Field(Of Integer)(strId), Nothing, dtbUpdateTable.Rows(i).Field(Of Integer)(GlobalVariables.strVersionNumber)) + Case GlobalVariables.UpdateType.Delete + GlobalVariables.AddToAuditTable(dtbAuditTable, iActionID, strTableName, dtbUpdateTable.Rows(i).Field(Of Integer)(strId), dtbUpdateTable.Rows(i).Field(Of Integer)(GlobalVariables.strVersionNumber), Nothing) End Select - If iUpdateType = GlobalVariables.UpdateType.Correction Then - End If - ' d) produce the audit record to be added to the audit write table Next - ' 4. Write: update table, audit table, comments table, events table - ' 5. Call UpdateTable for each lstLinkedDataStructures + ' Write: update table, audit table, comments table, events table + ' TODO call methods to write to the database using the data adapter + + ' Call UpdateTable for each lstLinkedDataStructures For Each clsLinkedStruc As DataStructure In lstLinkedDataStructures clsLinkedStruc.UpdateTable(iActionID) Next End Sub '''//////////////////////////////////////////////////////////////////////////////////////////////////// - ''' Test for update table. + ''' Test of updating values. + ''' '''//////////////////////////////////////////////////////////////////////////////////////////////////// Public Sub TestUpdateTable() + Dim dctCorrectionValues As New Dictionary(Of String, Object) + Dim dtbExpectedUpdateTable As DataTable + Dim dtbTempReadTable As DataTable + Dim rowTempEvent1 As DataRow + Dim rowTempEvent2 As DataRow + Dim rcTempEventChanges() As DataRow + dtbReadTable = New DataTable dtbReadTable.Columns.Add(strId, GetType(Integer)) dtbReadTable.Columns.Add(GlobalVariables.strVersionNumber, GetType(Integer)) dtbReadTable.Columns.Add("value", GetType(String)) dtbReadTable.Columns.Add(strCurrent, GetType(Integer)) + ' Temporary read table used to create new rows that are passed in to Do methods + dtbTempReadTable = dtbReadTable.Clone() + dtbReadTable.Rows.Add(1, 1, "a", 1) dtbReadTable.Rows.Add(2, 1, "b", 1) dtbReadTable.Rows.Add(3, 1, "c", 1) @@ -428,17 +453,50 @@ Public Class DataStructure dtbReadTable.Rows.Add(4, 2, "ddd", 2) dtbReadTable.Rows.Add(5, 1, "e", 1) - dtbWriteTable = dtbReadTable.Clone() - dtbWriteTable.Columns.Add(strVOld, GetType(Integer)) - dtbWriteTable.Columns.Add(strUpdateType, GetType(Integer)) - - dtbWriteTable.Rows.Add(1, 1, "a", DBNull.Value, 1, GlobalVariables.UpdateType.Correction) - dtbWriteTable.Rows.Add(1, 2, "A", 1, 1, GlobalVariables.UpdateType.Correction) - dtbWriteTable.Rows.Add(2, 2, "bb", 2, 1, GlobalVariables.UpdateType.EventChange) - dtbWriteTable.Rows.Add(3, 1, "c", DBNull.Value, 1, GlobalVariables.UpdateType.Delete) - dtbWriteTable.Rows.Add(4, 3, "d", 1, DBNull.Value, GlobalVariables.UpdateType.EventChange) - dtbWriteTable.Rows.Add(4, 1, "dd", 2, 1, GlobalVariables.UpdateType.EventChange) - dtbWriteTable.Rows.Add(4, 2, "ddd", 3, 1, GlobalVariables.UpdateType.EventChange) + InitialiseUpdateTable() + + SetValueFields({"value"}) + + '' CHANGES + ' Correction in id=1 from "a" to "A" + dctCorrectionValues.Add("value", "A") + DoCorrectionToRecord(dtbReadTable.Rows(0), dctCorrectionValues) + + ' Event change in id=2 "b" to "bb" + ' New event added after existing event - no change to existing event + dtbTempReadTable.ImportRow(dtbReadTable.Rows(1)) + rowTempEvent1 = dtbTempReadTable.Rows(dtbTempReadTable.Rows.Count - 1) + rowTempEvent1.Item("value") = "bb" + rowTempEvent1.Item(GlobalVariables.strVersionNumber) = rowTempEvent1.Field(Of Integer)(GlobalVariables.strVersionNumber) + 1 + rowTempEvent1.Item(strCurrent) = rowTempEvent1.Field(Of Integer)(strCurrent) + 1 + DoEvent(rowEventNew:=rowTempEvent1) + + ' Delete in id=3 + DoDeleteRecord(dtbReadTable.Rows(2)) + + ' Event change in id=4 + ' New event added before existing two events, changes required to both existing events + dtbTempReadTable.ImportRow(dtbReadTable.Rows(3)) + rowTempEvent2 = dtbTempReadTable.Rows(dtbTempReadTable.Rows.Count - 1) + rowTempEvent2.Item("value") = "d" + rowTempEvent2.Item(GlobalVariables.strVersionNumber) = 3 + rowTempEvent2.Item(strCurrent) = 1 + rcTempEventChanges = dtbReadTable.Select("id = 4") + rcTempEventChanges(0).Item(strCurrent) = 2 + rcTempEventChanges(1).Item(strCurrent) = 3 + DoEvent(rcTempEventChanges, rowTempEvent2) + + dtbExpectedUpdateTable = dtbReadTable.Clone() + dtbExpectedUpdateTable.Columns.Add(strVOld, GetType(Integer)) + dtbExpectedUpdateTable.Columns.Add(strUpdateType, GetType(Integer)) + dtbExpectedUpdateTable.Rows.Add(1, 1, "a", DBNull.Value, 1, GlobalVariables.UpdateType.CorrectionOld) + dtbExpectedUpdateTable.Rows.Add(1, 2, "A", 1, 1, GlobalVariables.UpdateType.CorrectionNew) + dtbExpectedUpdateTable.Rows.Add(2, 2, "bb", 2, DBNull.Value, GlobalVariables.UpdateType.EventNew) + dtbExpectedUpdateTable.Rows.Add(3, 1, "c", DBNull.Value, 1, GlobalVariables.UpdateType.Delete) + dtbExpectedUpdateTable.Rows.Add(4, 3, "d", 1, DBNull.Value, GlobalVariables.UpdateType.EventNew) + dtbExpectedUpdateTable.Rows.Add(4, 1, "dd", 2, DBNull.Value, GlobalVariables.UpdateType.EventChange) + dtbExpectedUpdateTable.Rows.Add(4, 2, "ddd", 3, DBNull.Value, GlobalVariables.UpdateType.EventChange) + UpdateTable(1) End Sub End Class diff --git a/ClimsoftVer4/ClimsoftVer4/clsGlobalVariables.vb b/ClimsoftVer4/ClimsoftVer4/clsGlobalVariables.vb index b56c6fb7..55e4ff1f 100644 --- a/ClimsoftVer4/ClimsoftVer4/clsGlobalVariables.vb +++ b/ClimsoftVer4/ClimsoftVer4/clsGlobalVariables.vb @@ -23,8 +23,10 @@ Public Class GlobalVariables Public Enum UpdateType ''' An enum constant representing the new record option. When a new record is added. NewRecord - ''' An enum constant representing the correction option. When a correction is made to an existing record. - Correction + ''' An enum constant representing the correction old option. When a correction is made to an existing record, this is the existing version. + CorrectionOld + ''' An enum constant representing the correction option. When a correction is made to an existing record, this is the new version. + CorrectionNew ''' An enum constant representing the event change option. When a change is made to an event that is not a correction e.g. another event is added before this resulting in a change to its current. EventChange ''' An enum constant representing the event new option. When a new value is added to an existing record corresponding to a new event. diff --git a/ClimsoftVer4/ClimsoftVer4/frmLogin.vb b/ClimsoftVer4/ClimsoftVer4/frmLogin.vb index 68a244ae..7a8adf27 100644 --- a/ClimsoftVer4/ClimsoftVer4/frmLogin.vb +++ b/ClimsoftVer4/ClimsoftVer4/frmLogin.vb @@ -209,8 +209,6 @@ Public Class frmLogin 'Translate text for controls on login form. 'Other Translation after successful login will come from language translation table stored in database - Dim datstruct As New DataStructure - msgKeyentryFormsListUpdated = "List of key-entry forms updated!" msgStationInformationNotFound = "Station information Not found. Please add station information And try again!" From 9b8393ab6607fa9e75574f06748137c0e793c1a5 Mon Sep 17 00:00:00 2001 From: Danny Parsons Date: Sat, 15 Aug 2020 22:05:26 +0100 Subject: [PATCH 6/6] Added filter methods to DataStructure and other functions, added initial functions for Station, created Test class --- ClimsoftVer4/ClimsoftVer4/ClimsoftVer4.vbproj | 1 + ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb | 86 ++++++++++++---- .../ClimsoftVer4/clsDataStructureStation.vb | 97 ++++++++++++++++++- .../ClimsoftVer4/clsGlobalVariables.vb | 9 +- ClimsoftVer4/ClimsoftVer4/clsTests.vb | 56 +++++++++++ 5 files changed, 220 insertions(+), 29 deletions(-) create mode 100644 ClimsoftVer4/ClimsoftVer4/clsTests.vb diff --git a/ClimsoftVer4/ClimsoftVer4/ClimsoftVer4.vbproj b/ClimsoftVer4/ClimsoftVer4/ClimsoftVer4.vbproj index 8b781a73..690e9bad 100644 --- a/ClimsoftVer4/ClimsoftVer4/ClimsoftVer4.vbproj +++ b/ClimsoftVer4/ClimsoftVer4/ClimsoftVer4.vbproj @@ -98,6 +98,7 @@ + diff --git a/ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb b/ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb index 683b5b61..482dcfe2 100644 --- a/ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb +++ b/ClimsoftVer4/ClimsoftVer4/clsDataStructure.vb @@ -8,7 +8,6 @@ Public Class DataStructure ' 1. Do we allow choice of fields to get? If writing then all fields needed, ' but reading only may not need all fields. ' Most tables have few fields so might not be needed. - ' 2. Where should SQL statement construction functions live? ''' ''' A string, the name of the table in the database this DataStructure links. @@ -19,28 +18,32 @@ Public Class DataStructure ''' A string, how the "id" column is stored in the database in all tables it appears in. ''' This could be moved to a separate class of constants. ''' - Private strId As String = "id" + Protected strId As String = "id" ''' - ''' A string, how the "current/current best" column is stored in strTableName. - ''' This is "current_best" by default and can either be "current" or "current_best". + ''' A string, how the "current"/"current best" column is stored in strTableName. + ''' This is "current_best" by default and expected to be either be "current" or "current_best". ''' Private strCurrent As String = "current_best" ''' ''' A list of strings, the names of the fields in strTableName which uniquely define a row. - ''' In most tables and by default this is {strId, strVersionNumber} but it is not in all and can be changed. + ''' In most tables and by default this is {strId, GlobalVariables.strVersionNumber} but it is not in all and can be changed. ''' Private lstKeyFieldNames As List(Of String) = New List(Of String)({strId, GlobalVariables.strVersionNumber}) ''' - ''' A list of strings, the names of the fields in strTableName which do not relate to primary key of auditting i.e. all columns apart from: strID, GlobalVariables.strVersionNumber and strCurrent. + ''' A list of strings, the names of the fields in strTableName which do not relate to primary key or auditting + ''' i.e. in most cases all columns apart from: strID, GlobalVariables.strVersionNumber and strCurrent. ''' ''' ''' This is used when adding a new record. To add a new record, a value must be specified for each of lstValueFields. ''' Private lstValueFields As List(Of String) + ' A TableFilter object which defines the rows in the table the values will be from + Private clsFilter As TableFilter + ''' ''' A DataTable storing the data read from the database. ''' This remains static once fetched from the database. @@ -84,6 +87,14 @@ Public Class DataStructure ''' Field name for "update type" when constructing dtbUpdateTable. Private strUpdateType As String = ".update_type" + Public Sub New() + + End Sub + + Public Sub New(strNewTableName As String) + SetTableName(strTableName) + End Sub + '''//////////////////////////////////////////////////////////////////////////////////////////////////// ''' Set the name of the table in the database this DataStructure links to. ''' @@ -95,8 +106,9 @@ Public Class DataStructure '''//////////////////////////////////////////////////////////////////////////////////////////////////// ''' - ''' Set the names of the fields in strTableName which do not relate to primary key of auditting - ''' i.e. all columns apart from: strID, GlobalVariables.strVersionNumber and strCurrent. + ''' Set the names of the fields in strTableName which uniquely define a row. This is + ''' needed when updating records. In most tables and by default this is {strId, + ''' strVersionNumber}. This method should only be used if it is different to the default. ''' ''' ''' The list of names of the key fields for strTableName. @@ -107,9 +119,8 @@ Public Class DataStructure '''//////////////////////////////////////////////////////////////////////////////////////////////////// ''' - ''' Set the names of the fields in strTableName which uniquely define a row. This is - ''' needed when updating records. In most tables and by default this is {strId, - ''' strVersionNumber}. This method should only be used if it is different to the default. + ''' Set the names of the fields in strTableName which do not relate to primary key of auditting + ''' i.e. in most cases all columns apart from: strID, GlobalVariables.strVersionNumber and strCurrent. ''' ''' ''' The list of names of the value fields for strTableName. @@ -118,10 +129,40 @@ Public Class DataStructure lstValueFields = iEnumerableNewValueFields.ToList() End Sub + ' Set the TableFilter + Public Sub SetFilter(clsNewFilter As TableFilter) + clsFilter = clsNewFilter + End Sub + + ' Alternative method to set the TableFilter + Public Sub SetFilter(strField As String, strOperator As String, strValue As String, Optional bIsPositiveCondition As Boolean = True, Optional bForceValuesAsString As Boolean = False) + Dim clsNewFilter As New TableFilter + + clsNewFilter.SetFieldCondition(strNewField:=strField, strNewOperator:=strOperator, objNewValue:=strValue, bNewIsPositiveCondition:=bIsPositiveCondition, bForceValuesAsString:=bForceValuesAsString) + SetFilter(clsNewFilter:=clsNewFilter) + End Sub + + ' Gets the data from database for strTableName and fills in dtbReadTable + ' bIncludedLinked - should linked DataStructure's also update their dtbReadTable? + Public Sub UpdateReadTable(Optional bIncludedLinked As Boolean = False) + 'TODO suspect this will be similar to DataCall.GetDataTable() + + If bIncludedLinked Then + For Each clsLinkedStruc As DataStructure In lstLinkedDataStructures + clsLinkedStruc.UpdateReadTable(bIncludedLinked) + Next + End If + End Sub + + ' Returns dtbReadTable + Public Function GetReadTable() As DataTable + Return dtbReadTable + End Function + '''//////////////////////////////////////////////////////////////////////////////////////////////////// ''' ''' Initialises dtbUpdateTable as a clone of dtbReadTable with two extra columns. - ''' This should be called once dtbReadTable has been set. + ''' This should be called after UpdateReadTable() has been called. ''' ''' ''' .Clone copies the structure but not the data of a DataTable. @@ -140,7 +181,7 @@ Public Class DataStructure ''' This may be called when getting the main data, or may only be called on demand. ''' ''' - ''' (Optional) Boolean, if True all versions returned, if + ''' (Optional) Boolean, if True all versions returned, if ''' False only the current comment for each id is returned. '''//////////////////////////////////////////////////////////////////////////////////////////////////// Private Sub SetCommentsTable(Optional bAllVersions As Boolean = False) @@ -188,6 +229,12 @@ Public Class DataStructure ' ON e_e.event_id=e.id End Sub + ' Sets the lstLinkedDataStructures list + ' TODO Are there more useful ways to set this instead of passing in a list directly? + Public Sub SetLinkedDataStructures(lstNewListedDataStructures As List(Of DataStructure)) + + End Sub + '''//////////////////////////////////////////////////////////////////////////////////////////////////// ''' Creates a new action in the action table and returns its Action ID. ''' @@ -228,11 +275,11 @@ Public Class DataStructure End If Next - ' This adds a row for the fields named: - ' strId, strVersionNumber, lstValueFields, strCurrent, strVOld, strUpdateType + ' This adds a row for the fields: + ' strId, strVersionNumber, lstValueFields(), strCurrent, strVOld, strUpdateType ' The value for strId is DBNull.Value since this will be auto incremented in the database. ' The order is important. - ' TODO: If audit table will also use this then it will be to be adapted as it does not have the same id, version number, current + ' TODO: If this method will also be used for adding to the audit table then it will need to be adapted as it does not have the same id, version number, current structure dtbUpdateTable.Rows.Add(DBNull.Value, 1, lstValueFields.ToArray, 1, DBNull.Value, GlobalVariables.UpdateType.NewRecord) End Sub @@ -247,7 +294,7 @@ Public Class DataStructure ''' This may be nothing if there are no event changes. ''' ''' A DataRow for a new event. This row should have the same structure - ''' as dtbReadTable.This may be nothing if there is no new event. + ''' as dtbReadTable. This may be nothing if there is no new event. ''' '''//////////////////////////////////////////////////////////////////////////////////////////////////// Public Sub DoEvent(Optional rcEventChanges() As DataRow = Nothing, Optional rowEventNew As DataRow = Nothing) @@ -304,7 +351,7 @@ Public Class DataStructure rowCurrentUpdate.Item(strUpdateType) = GlobalVariables.UpdateType.CorrectionOld dtbUpdateTable.Rows.Add(rowCurrentUpdate) - ' TODO Should be moved to separate function as duplicated. + ' TODO Should be moved to separate function as duplicated elsewhere. For Each strField As String In lstValueFields If dctValues.TryGetValue(strField, objTemp) Then lstValues.Add(objTemp) @@ -355,7 +402,7 @@ Public Class DataStructure End Sub '''//////////////////////////////////////////////////////////////////////////////////////////////////// - ''' Executes the action operation. + ''' Executes the action operation. ''' ''' This could be moved to a separate class? ''' @@ -397,6 +444,7 @@ Public Class DataStructure ' Make a blank audit table to add entries to dtbAuditTable = GlobalVariables.dtbEmptyAuditTable + ' Construct the audit table For i As Integer = 0 To Me.dtbUpdateTable.Rows.Count - 1 iUpdateType = dtbUpdateTable.Rows(i).Field(Of Integer)(strUpdateType) Select Case iUpdateType diff --git a/ClimsoftVer4/ClimsoftVer4/clsDataStructureStation.vb b/ClimsoftVer4/ClimsoftVer4/clsDataStructureStation.vb index 4c1fbd70..7317614a 100644 --- a/ClimsoftVer4/ClimsoftVer4/clsDataStructureStation.vb +++ b/ClimsoftVer4/ClimsoftVer4/clsDataStructureStation.vb @@ -7,12 +7,103 @@ ' Top level table: feature_type - ' Second level: feature (get list of stations), feature_type_property (get ID to link to station) + ' Linked tables: + ' 1. feature (get list of stations) + ' 1a. feature_geometry (get list of latitude and longitude for all stations) + ' 2. feature_type_property (get list of properties defined for stations) + ' 2a. feature_property_value (get list of property values for all properties for all stations) - ' strPropertyName is a string/enumerated value of known properties + ' Everything that is fixed for this station class + ' e.g. the top level table name and the list of sub DataStructures + + ' Define these here so they can be accessible in multiple functions + Private clsFeature As New DataStructure("c5_feature") + Private clsFeatureGeometry As New DataStructure("c5_feature_geometry") + Private clsFeatureTypeProperty As New DataStructure("c5_feature_type_property") + Private clsFeaturePropertyValue As New DataStructure("c5_feature_property_value") + + ' This is the ID of the station entry in the feature_type table + ' This needs to be retrieved from the database + ' This is needed to get linked data from all other feature tables + Private iFeatureTypeID As Integer + + Private strFeatureTypeIdField As String = "feature_type_id" + + Public Sub New() + SetTableName("c5_feature_type") + End Sub + + ' Gets the ID of the station entry in the feature_type table and sets to iFeatureTypeID + ' This only needs to be called once per session + Private Sub SetFeatureTypeID() + Dim iStationID As Integer + + ' TODO get this from the database and set to iFeatureTypeID + ' How do we get this from the database? Do we assume that the name field will be "station"? What about in other languages? + ' Or can we assume it is a fixed ID across databases? + iFeatureTypeID = iStationID + End Sub + + ' Linked tables: + ' 1. feature (get list of stations) + ' 1a. feature_geometry (get list of latitude and longitude for all stations) + ' 2. feature_type_property (get list of properties defined for stations) + ' 2a. feature_property_value (get list of property values for all properties for all stations) + + ' This should be called after calling SetFeatureTypeID() + Private Sub SetStationLinkedDataStructures() + clsFeature.SetFilter(strFeatureTypeIdField, "=", iFeatureTypeID) + clsFeatureTypeProperty.SetFilter(strFeatureTypeIdField, "=", iFeatureTypeID) + + clsFeature.SetLinkedDataStructures(New List(Of DataStructure)(clsFeatureGeometry)) + clsFeatureTypeProperty.SetLinkedDataStructures(New List(Of DataStructure)(clsFeaturePropertyValue)) + SetLinkedDataStructures(New List(Of DataStructure)({clsFeature, clsFeatureTypeProperty})) + End Sub + + ' strPropertyName is a string/enumerated value of known station properties ' Method knows how to get a know property in v4 or v5 database ' if strPropertyName is not recognised then have generic way to search database Public Function GetPropertyValue(strPropertyName As String) As Object End Function -End Class + + ' Returns all station related data in a single table + Public Function GetStationDataTable() As DataTable + Dim dtbStation As DataTable + + UpdateReadTable(True) + + ' Create a clone of the feature table as a base for a "station" table + dtbStation = clsFeature.GetReadTable().Clone() + ' Left join feature with feature_geometry table to get latitude and longitude columns + ' Select a, b specifies what is returned in the query, a and b ensures all fields available + Dim query = From a In clsFeature.GetReadTable().Copy().AsEnumerable() + Group Join b In clsFeatureGeometry.GetReadTable.Copy() + On a.Field(Of Integer)(strId) Equals b.Field(Of Integer)("feature_id") + Into Group + Let b = Group.FirstOrDefault + Select a, b + + ' TODO how do we deal with different versions when merging tables? + ' TODO which fields do we keep from both tables? + For Each item In query + dtbStation.Rows.Add(item.a.ItemArray) + dtbStation.Rows(dtbStation.Rows.Count - 1)("latitude") = item.b.Item("latitude") + dtbStation.Rows(dtbStation.Rows.Count - 1)("longitude") = item.b.Item("longitude") + Next + + ' Next: + ' 1. Merge dtbFeaturePropertyValue with dtbFeatureTypeProperty + ' (right join if possible to only get properties for station, otherwise filter manually) + ' 2. Unstack result (separate unstack for each type: integer, string etc.) to get properties wide + ' 3. Merge unstacked results together + ' 4. Merge result with dtbStation + + End Function + + ' Next: + ' Add set of Do* functions to manage changes in the DataStructures + ' e.g. DoCorrectPropertyValue(), DoAddPropertyValue(), DoAddStation() + ' Forms can call these by only needing access to the output from GetStationDataTable() + ' The Do* functions then call the appropriate Do* base DataStructure function for the right table +End Class \ No newline at end of file diff --git a/ClimsoftVer4/ClimsoftVer4/clsGlobalVariables.vb b/ClimsoftVer4/ClimsoftVer4/clsGlobalVariables.vb index 55e4ff1f..17c1ce64 100644 --- a/ClimsoftVer4/ClimsoftVer4/clsGlobalVariables.vb +++ b/ClimsoftVer4/ClimsoftVer4/clsGlobalVariables.vb @@ -12,7 +12,6 @@ Public Class GlobalVariables ''' ''' A string, how the "version number" column is stored in the database in all tables it appears in. - ''' This could be moved to a separate class of constants. ''' Public Shared ReadOnly strVersionNumber As String = "version_number" @@ -91,13 +90,11 @@ Public Class GlobalVariables '''//////////////////////////////////////////////////////////////////////////////////////////////////// ''' - ''' Constructs an SQL query to get only the current versions from a table. (Could be moved to a - ''' separate class for query construction) + ''' Constructs an SQL query to get only the current versions from a table. + ''' Could be moved to a separate class for query construction ''' ''' ''' A string, the name of the table to query. - ''' - ''' The current versions query. '''//////////////////////////////////////////////////////////////////////////////////////////////////// Private Function GetCurrentVersionsQuery(strTable As String) As String ' From here: https://stackoverflow.com/questions/612231/how-can-i-select-rows-with-maxcolumn-value-distinct-by-another-column-in-sql @@ -125,8 +122,6 @@ Public Class GlobalVariables ''' (Could be moved to a separate class for query construction) ''' ''' - ''' Danny, 12/03/2020. - ''' ''' A string, the name of the table to query. ''' An integer, the version numbers to extract for each id. ''' diff --git a/ClimsoftVer4/ClimsoftVer4/clsTests.vb b/ClimsoftVer4/ClimsoftVer4/clsTests.vb new file mode 100644 index 00000000..bc1a234e --- /dev/null +++ b/ClimsoftVer4/ClimsoftVer4/clsTests.vb @@ -0,0 +1,56 @@ +Public Class clsTests + + Public Sub TestStationTable() + Dim dtbFeature As New DataTable + Dim dtbFeatureGeometry As New DataTable + Dim dtbFeatureTypeProperty As New DataTable + Dim dtbFeaturePropertyValue As New DataTable + Dim dtbStation As New DataTable + + dtbFeature.Columns.Add("id", GetType(Integer)) + dtbFeature.Columns.Add("version_number", GetType(Integer)) + dtbFeature.Columns.Add("feature_type_id", GetType(Integer)) + dtbFeature.Columns.Add("name", GetType(String)) + dtbFeature.Columns.Add("current", GetType(Integer)) + + dtbFeature.Rows.Add(1, 1, 1, "BINGA", 1) + dtbFeature.Rows.Add(2, 1, 1, "KARIBA AIRPORT", 1) + 'dtbFeature.Rows.Add(3, 1, 2, "Kenya", 1) + dtbFeature.Rows.Add(4, 1, 1, "Kisumu Airport", 1) + + dtbFeatureGeometry.Columns.Add("id", GetType(Integer)) + dtbFeatureGeometry.Columns.Add("version_number", GetType(Integer)) + dtbFeatureGeometry.Columns.Add("feature_id", GetType(Integer)) + dtbFeatureGeometry.Columns.Add("geometry_type", GetType(String)) + dtbFeatureGeometry.Columns.Add("shape", GetType(String)) + dtbFeatureGeometry.Columns.Add("latitude", GetType(Double)) + dtbFeatureGeometry.Columns.Add("longitude", GetType(Double)) + dtbFeatureGeometry.Columns.Add("current", GetType(Integer)) + + dtbFeatureGeometry.Rows.Add(1, 1, 1, "point", "", -1.6, 37.3, 1) + dtbFeatureGeometry.Rows.Add(2, 1, 2, "point", "", -16.5, 28.8, 1) + 'dtbFeatureGeometry.Rows.Add(3, 1, 3, "polygon", "", DBNull.Value, DBNull.Value, 1) + dtbFeatureGeometry.Rows.Add(4, 1, 4, "point", "", -0.01, 34.7, 1) + + dtbStation.Columns.Add("id", GetType(Integer)) + dtbStation.Columns.Add("version_number", GetType(Integer)) + dtbStation.Columns.Add("feature_type_id", GetType(Integer)) + dtbStation.Columns.Add("name", GetType(String)) + dtbStation.Columns.Add("current", GetType(Integer)) + dtbStation.Columns.Add("latitude", GetType(Double)) + dtbStation.Columns.Add("longitude", GetType(Double)) + + Dim query = From a In dtbFeature.AsEnumerable + Group Join b In dtbFeatureGeometry + On a.Field(Of Integer)("id") Equals b.Field(Of Integer)("feature_id") + Into Group + Let b = Group.FirstOrDefault + Select a, b + For Each item In query + dtbStation.Rows.Add(item.a.ItemArray) + dtbStation.Rows(dtbStation.Rows.Count - 1)("latitude") = item.b.Item("latitude") + dtbStation.Rows(dtbStation.Rows.Count - 1)("longitude") = item.b.Item("longitude") + Next + + End Sub +End Class