Click here to Skip to main content
11,500,106 members (64,656 online)
Click here to Skip to main content
Articles » Database » Database » Utilities » Downloads
Add your own
alternative version

DataTable Synchronization Manager

, 4 Mar 2006 52.6K 907 67
Adds synchronization to the DataTable Transaction Logger.
/*
Copyright (c) 2006, Marc Clifton
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list
  of conditions and the following disclaimer. 

* Redistributions in binary form must reproduce the above copyright notice, this 
  list of conditions and the following disclaimer in the documentation and/or other
  materials provided with the distribution. 
 
* Neither the name Marc Clifton nor the names of contributors may be
  used to endorse or promote products derived from this software without specific
  prior written permission. 

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

//	Refactorings:
//		TransType property renamed to TransactionType
//		TransactionType enumeration renamed to RecordType
//		Added a virtual Apply method that implements the logic that used to be in DataTableTransactionLog class
//		Added a non-virtual Revert method that implements the logic that used to be in DataTableTransactionLog class
//		columnValues is exposed (ColumnValues) and is settable.

using System;
using System.Collections.Generic;
using System.Data;

namespace Clifton.Data
{
	/// <summary>
	/// A wrapper for a transaction row, encapsulating the type of transaction, the primary
	/// key associated with the transaction, old and new values (when a field changes) and 
	/// internally manages the entire field-value list when a row is deleted.
	/// </summary>
	public class DataTableTransactionRecord
	{
		/// <summary>
		/// Enumerates the supported transaction types: add row, delete row, and change field.
		/// </summary>
		public enum RecordType
		{
			NewRow,
			DeleteRow,
			ChangeField
		}

		/// <summary>
		/// The row to which this transaction applies.
		/// </summary>
		protected DataRow row;

		/// <summary>
		/// The transaction number.
		/// </summary>
		protected int transNum;

		/// <summary>
		/// The transaction type.
		/// </summary>
		protected RecordType transType;

		/// <summary>
		/// The column name, used only when a field changes.
		/// </summary>
		protected string columnName;

		/// <summary>
		/// The old field value, used only when a field changes.
		/// </summary>
		protected object oldValue;

		/// <summary>
		/// The new field value, used only when a field changes.
		/// </summary>
		protected object newValue;

		/// <summary>
		/// The collection of field-values populated only when a row is deleted,
		/// so that the row can be recovered.
		/// </summary>
		protected Dictionary<string, object> columnValues;

		/// <summary>
		/// Gets/sets the field name/field value dictionary.  This property is settable
		/// so that the dictionary can be shared by multiple rows.
		/// </summary>
		public Dictionary<string, object> ColumnValues
		{
			get { return columnValues; }
			set { columnValues = value; }
		}

		/// <summary>
		/// Get the state of the field-value buffer, which indicates that a the collection of field-values
		/// has been populated, thus the row has been deleted at some point.  True if the collection is
		/// populated, false if the collection is empty.
		/// </summary>
		public bool WasDeleted
		{
			get { return columnValues.Count > 0; }
		}

		/// <summary>
		/// Get the transaction number associated with this transaction.
		/// </summary>
		public int TransNum
		{
			get { return transNum; }
		}

		/// <summary>
		/// Gets the new field value.  Throws DataTableTransactionException if the transaction type is not ChangeField.
		/// </summary>
		public object NewValue
		{
			get { return newValue; }
			set { newValue = value; }
		}

		/// <summary>
		/// Gets the old field value.  Throws DataTableTransactionException if the transaction type is not ChangeField.
		/// </summary>
		public object OldValue
		{
			get { return oldValue; }
		}

		/// <summary>
		/// Gets the name of the column associated with the the transaction.  
		/// Throws DataTableTransactionException if the transaction type is not ChangeField.
		/// </summary>
		public string ColumnName
		{
			get { return columnName; }
		}

		/// <summary>
		/// Gets the transaction type.
		/// </summary>
		public RecordType TransactionType
		{
			get { return transType; }
		}

		public DataRow Row
		{
			get { return row; }
			set { row = value; }
		}

		public bool IsRowDeleted
		{
			get { return row.RowState == DataRowState.Deleted; }
		}

		/// <summary>
		/// Returns the value of the row's column, even if the row has been deleted.
		/// This is useful to acquire the PK fields for a deleted row.
		/// </summary>
		/// <param name="fieldName"></param>
		/// <returns></returns>
		public object GetGuaranteedRowValue(string fieldName)
		{
			object ret = null;

			if (WasDeleted)
			{
				ret = columnValues[fieldName];
			}
			else
			{
				if (row.RowState == DataRowState.Deleted)
				{
					throw new DataTableTransactionException("Row has been deleted and there is no saved column values.");
				}

				ret = row[fieldName];
			}

			return ret;
		}

		protected DataTableTransactionRecord()
		{
		}

		/// <summary>
		/// Constructor used to instantiate a row change (add/delete) transaction.
		/// </summary>
		/// <param name="transNum">The associated transaction number.  Can be any integer value.</param>
		/// <param name="row">The row being added/deleted.</param>
		/// <param name="transType">The transaction type.  Throws DataTableTransactionException if the transaction is ChangeField.</param>
		public DataTableTransactionRecord(int transNum, DataRow row, RecordType transType)
		{
			if (transType == RecordType.ChangeField)
			{
				throw new DataTableTransactionException("ChangeField transactions cannot use this constructor.");
			}

			if (row == null)
			{
				throw new ArgumentNullException("DataRow cannot be null.");
			}

			this.transNum = transNum;
			this.row = row;
			this.transType = transType;
			Initialize();
		}

		/// <summary>
		/// Constructor used to instantiate a field change transaction.
		/// </summary>
		/// <param name="transNum">The transaction number.  This can be any integer value.</param>
		/// <param name="row">The row being added/deleted.</param>
		/// <param name="columnName">The column name of the field being changed.</param>
		/// <param name="oldValue">The old field's value.</param>
		/// <param name="newValue">The new field's value.</param>
		public DataTableTransactionRecord(int transNum, DataRow row, string columnName, object oldValue, object newValue)
		{
			if (columnName == null)
			{
				throw new ArgumentNullException("Column name cannot be null.");
			}

			if (row == null)
			{
				throw new ArgumentNullException("DataRow cannot be null.");
			}

			this.transNum = transNum;
			this.row = row;
			this.transType = RecordType.ChangeField;						// implicit based on the constructor.
			this.columnName = columnName;
			this.oldValue = oldValue;
			this.newValue = newValue;
			Initialize();
		}

		/// <summary>
		/// Adds the value associated with a field to the internal field-value collection used when deleting a row.
		/// </summary>
		/// <param name="name">The column name.</param>
		/// <param name="val">The associated value.</param>
		public void AddColumnNameValuePair(string columnName, object val)
		{
			if (columnName == null)
			{
				throw new ArgumentNullException("Column name cannot be null.");
			}

			columnValues.Add(columnName, val);
		}

		/// <summary>
		/// Gets the value from the field-value collection for the specified field.  Used to retrieve a field value
		/// for a row that has been deleted.
		/// </summary>
		/// <param name="columnName"></param>
		/// <returns></returns>
		public object GetValue(string columnName)
		{
			if (columnName == null)
			{
				throw new ArgumentNullException("Column name cannot be null.");
			}

			if (!columnValues.ContainsKey(columnName))
			{
				throw new ArgumentException("Column name not in deleted column values collection.");
			}

			return columnValues[columnName];
		}

		public virtual DataRow Revert(DataTable sourceTable)
		{
			DataRow newRow = null;

			switch (transType)
			{
				// Delete the row we added.
				case DataTableTransactionRecord.RecordType.NewRow:
					// Only save row fields if this row is first time deleted.
					if (!WasDeleted)
					{
						// Save all the field values for the row being deleted.
						SaveRowFields(row);
					}

					// Delete the row.
					row.Delete();
					break;

				// Add the row we deleted.
				case DataTableTransactionRecord.RecordType.DeleteRow:
					newRow = sourceTable.NewRow();
					RestoreRowFields(newRow);									// Restore all field values into the new row.
					sourceTable.Rows.Add(newRow);
					break;

				// Undo the change to field.
				case DataTableTransactionRecord.RecordType.ChangeField:
					row[columnName] = oldValue;
					break;
			}

			return newRow;
		}

		public virtual DataRow Apply(DataView dataView)
		{
			DataRow newRow = null;
			DataTable sourceTable = dataView.Table;

			switch (transType)
			{
				// Add the row that was previously added but was deleted.
				case DataTableTransactionRecord.RecordType.NewRow:
					newRow = sourceTable.NewRow();

					if (WasDeleted)
					{
						RestoreRowFields(newRow);
					}

					sourceTable.Rows.Add(newRow);

					break;

				case DataTableTransactionRecord.RecordType.DeleteRow:
					if (!WasDeleted)
					{
						SaveRowFields(row);
					}

					row.Delete();
					break;

				case DataTableTransactionRecord.RecordType.ChangeField:
					row[columnName] = newValue;
					break;
			}

			return newRow;
		}

		/// <summary>
		/// Common initialization called by constructors.
		/// </summary>
		protected void Initialize()
		{
			columnValues = new Dictionary<string, object>();
		}

		/// <summary>
		/// Restores all row fields saved in the transaction record's field-value collection.
		/// </summary>
		/// <param name="record"></param>
		/// <param name="row"></param>
		public void RestoreRowFields(DataRow row)
		{
			foreach (DataColumn dc in row.Table.Columns)
			{
				row[dc] = GetValue(dc.ColumnName);
			}
		}

		/// <summary>
		/// Saves all row fields to the transaction record's field-value collection.
		/// </summary>
		/// <param name="record"></param>
		/// <param name="row"></param>
		public void SaveRowFields(DataRow row)
		{
			foreach (DataColumn dc in row.Table.Columns)
			{
				AddColumnNameValuePair(dc.ColumnName, row[dc]);
			}
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Marc Clifton

United States United States
Marc is the creator of two open source projects, MyXaml, a declarative (XML) instantiation engine and the Advanced Unit Testing framework, and Interacx, a commercial n-tier RAD application suite.  Visit his website, www.marcclifton.com, where you will find many of his articles and his blog.

Marc lives in Philmont, NY.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.150520.1 | Last Updated 4 Mar 2006
Article Copyright 2006 by Marc Clifton
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid