/// <summary> /// Commits the current changes since the last checkpoint to the ChangeLog, making it available for Rollback as a unit. /// </summary> public void Checkpoint(string name) { lock (SyncRoot) { // Get changes var changes = Data.GetChanges(); // Do nothing when no changes if (changes == null) { return; } // Do not check constraints on changes (may be orphaned) changes.EnforceConstraints = false; // Truncate existing log (remove any roll-forwards entries) if (_logIndex < _changeLog.Count - 1) { while (_changeLog.Count > (_logIndex + 1)) { _changeLog.RemoveAt(_changeLog.Count - 1); } } // Add change log entry var logEntry = new DataSetChangeLogEntry(DateTime.UtcNow, name, changes); _changeLog.Add(logEntry); _logIndex++; // Commit changes Data.AcceptChanges(); } }
/// <summary> /// Rolls the DataSet forwards by the specified number of checkpoint "steps". /// </summary> public void Redo(int steps) { lock (SyncRoot) { // Validate request if ((steps < 1) || (steps > (_changeLog.Count - _logIndex))) { throw new InvalidOperationException(); } // Disable constraints during update (if enabled) bool enforceConstraints = Data.EnforceConstraints; if (enforceConstraints) { Data.EnforceConstraints = false; } // Undo any outstanding changes before Checkpoint Data.RejectChanges(); // Rollforward the specified number of steps... while (steps-- > 0) { // Get change log entry DataSetChangeLogEntry logEntry = _changeLog[_logIndex + 1]; DataSet changes = logEntry.Changes; // Re-apply changes to DataSet foreach (DataTable changedTable in changes.Tables) { foreach (DataRow changedRow in changedTable.Rows) { // Re-apply action switch (changedRow.RowState) { case DataRowState.Added: { // Re-apply INSERT... // Insert the changed row Data.Tables[changedTable.TableName].Rows.Add(changedRow.ItemArray); break; } case DataRowState.Modified: { // Re-apply UPDATE... // Find the original row string primaryKeyStatement = GetPrimaryKeyFilterExpression(changedRow); DataRow[] existingRows = Data.Tables[changedTable.TableName].Select(primaryKeyStatement); if (existingRows.Length == 0) { throw new InvalidOperationException("Rollback could not find original row in DataSet, needed to roll-back UPDATE."); } DataRow row = existingRows[0]; // Update row values row.ItemArray = changedRow.ItemArray; break; } case DataRowState.Deleted: { // Re-apply DELETE... // Temporarily un-delete change row to allow access to key information changedRow.RejectChanges(); // Find the original row string primaryKeyStatement = GetPrimaryKeyFilterExpression(changedRow); DataRow[] existingRows = Data.Tables[changedTable.TableName].Select(primaryKeyStatement); if (existingRows.Length == 0) { throw new InvalidOperationException("Rollback could not find original row in DataSet, needed to roll-back INSERT."); } DataRow row = existingRows[0]; // Restore change row to original state changedRow.Delete(); // Delete the row row.Delete(); break; } } } } // Commit changes Data.AcceptChanges(); // Move change log pointer forwards _logIndex++; } // Restore constraints after update (if previously enabled) if (enforceConstraints) { Data.EnforceConstraints = true; } } }
/// <summary> /// Rolls-back the DataSet by the specified number of checkpoint "steps". /// </summary> public void Undo(int steps) { lock (SyncRoot) { // Validate request if ((steps < 1) || (steps > (_logIndex + 1))) { throw new InvalidOperationException(); } // Disable constraints during update (if enabled) bool enforceConstraints = Data.EnforceConstraints; if (enforceConstraints) { Data.EnforceConstraints = false; } // Undo any outstanding changes before Checkpoint Data.RejectChanges(); // Rollback the specified number of steps... while (steps-- > 0) { // Get change log entry DataSetChangeLogEntry logEntry = _changeLog[_logIndex]; DataSet changes = logEntry.Changes; // Reverse changes to DataSet foreach (DataTable changedTable in changes.Tables) { foreach (DataRow changedRow in changedTable.Rows) { // Reverse action switch (changedRow.RowState) { case DataRowState.Added: { // Reverse INSERT... // Find the original row string primaryKeyStatement = GetPrimaryKeyFilterExpression(changedRow); DataRow[] existingRows = Data.Tables[changedTable.TableName].Select(primaryKeyStatement); if (existingRows.Length == 0) { throw new InvalidOperationException("Rollback could not find original row in DataSet, needed to roll-back INSERT."); } DataRow row = existingRows[0]; // Delete the row row.Delete(); break; } case DataRowState.Modified: { // Reverse UPDATE... // Find the original row string primaryKeyStatement = GetPrimaryKeyFilterExpression(changedRow); DataRow[] existingRows = Data.Tables[changedTable.TableName].Select(primaryKeyStatement); if (existingRows.Length == 0) { throw new InvalidOperationException("Rollback could not find original row in DataSet, needed to roll-back UPDATE."); } DataRow row = existingRows[0]; // Restore the original row values for (int i = 0; i < changedTable.Columns.Count; i++) { row[i] = changedRow[i, DataRowVersion.Original]; } break; } case DataRowState.Deleted: { // Reverse DELETE... // Insert the original row DataTable table = Data.Tables[changedTable.TableName]; DataRow row = table.NewRow(); for (int i = 0; i < changedTable.Columns.Count; i++) { row[i] = changedRow[i, DataRowVersion.Original]; } table.Rows.Add(row); break; } } } } // Commit changes Data.AcceptChanges(); // Move change log pointer backwards (do not remove forward entries until next Checkpoint, to allow roll-forward until that time) _logIndex--; } // Restore constraints after update (if previously enabled) if (enforceConstraints) { Data.EnforceConstraints = true; } } }