Click here to Skip to main content
15,883,531 members
Articles / Programming Languages / C# 4.0

Relationship Oriented Programming - The IDE plus More on Agile Project Management

Rate me:
Please Sign up or sign in to vote.
4.98/5 (25 votes)
12 Mar 2012CPOL81 min read 77.4K   1.2K   49  
An Integrated Development Environment (IDE) for the Relationship Oriented Programming Tool.
/*
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
		{
			Nothing,
			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 != null) && (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/sets the transaction type.
		/// </summary>
		public RecordType TransactionType
		{
			get { return transType; }
			set { transType = value; }
		}

		/// <summary>
		/// The row to which this transaction applies.
		/// </summary>
		public DataRow Row
		{
			get { return row; }
			set { row = value; }
		}

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

		protected DataTableTransactionRecord()
		{
			Initialize();
		}

		/// <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];
		}

		/// <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;
		}

		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)
		{
			// Ignore rows that are detached or deleted.
			if ((row.RowState != DataRowState.Detached) && (row.RowState != DataRowState.Deleted))
			{
				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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Interacx
United States United States
Blog: https://marcclifton.wordpress.com/
Home Page: http://www.marcclifton.com
Research: http://www.higherorderprogramming.com/
GitHub: https://github.com/cliftonm

All my life I have been passionate about architecture / software design, as this is the cornerstone to a maintainable and extensible application. As such, I have enjoyed exploring some crazy ideas and discovering that they are not so crazy after all. I also love writing about my ideas and seeing the community response. As a consultant, I've enjoyed working in a wide range of industries such as aerospace, boatyard management, remote sensing, emergency services / data management, and casino operations. I've done a variety of pro-bono work non-profit organizations related to nature conservancy, drug recovery and women's health.

Comments and Discussions