this class is still work in progress more documentation to follow ...

Namespace:  CA.Common.Data
Assembly:  CA.Common (in CA.Common.dll) Version: 1.0.0.0 (1.0.0.0)

Remarks

source code:

CopyC#
//Source code from the Code Associate C# code library, Full documentation and latest updates can be found
//@ http://www.codeassociate.com/caapi/
using System.Data;
using System.IO;
using System.Text;

namespace CA.Common.Data
{
    //this class is still work in progress more documentation and examples to follow ... 
    //TODO go through all exception messages to ensure they are appropiate
    //TODO need to come up with a data changes object to record the changes..

    public class DataTableComparer
    {
        // we want to work with clean data Tables as dont want to have to deal 
        // within flight modifications this function tests to see if the DataTable as 
        // any modifications pending
        private bool HasChanges(DataTable dt)
        {
            return (dt.GetChanges() != null);
        }

        private void HasSameDataStructure(DataTable dt1, DataTable dt2)
        {
            if (dt1.Columns.Count != dt2.Columns.Count)
            {
                throw new InvalidDataException("Cannot Compare data tables diffrent colunms defined");
            }
            foreach(DataColumn dc in dt1.Columns)
            {
                if (dt2.Columns.Contains(dc.ColumnName))
                {
                    if (dc.DataType != dt2.Columns[dc.ColumnName].DataType)
                    {
                        throw new InvalidDataException(string.Format("Cannot Compare data tables with colunms defined as diffrent types. {0} does not exist is second datatable", dc.ColumnName));
                    }
                }
                else
                {
                    throw new InvalidDataException(string.Format("Cannot Compare data tables diffrent colunms defined. {0} does not exist is second datatable", dc.ColumnName));
                }
            }
        }

        private void SetPrimaryKey (DataTable dt, string[] strPrimaryKeys)
        {
            DataColumn[] dcPrimaryKeys = new DataColumn[strPrimaryKeys.Length]; 
            for (int i = 0; i < strPrimaryKeys.Length; i++)
            {
                DataColumn keyColumn = dt.Columns[strPrimaryKeys[i]];
                if (keyColumn != null)
                    dcPrimaryKeys[i] = keyColumn;
                else
                {
                    throw new InvalidDataException("Primary Key defined is not a DataColumn in DataTable");
                }
            }
            // TODO place an exception around this for existing bad data.. 
            dt.PrimaryKey = dcPrimaryKeys;
        }

        // possibly refactor this to get the format once and reuse the format with diffrent rows
        public string GenerateSelectFilter(DataRow dr, DataColumn[] Pk)
        {
            StringBuilder sb = new StringBuilder();
            for(int i = 0; i < Pk.Length; i++)
            {
                // if it is not the first element add the word AND
                if (i > 0)
                   sb.Append(" AND "); 
                // get the col name 
                sb.Append(Pk[i].ColumnName);
                //if null output IS NULL
                if (dr.IsNull(Pk[i]))
                    sb.Append(" IS NULL");
                bool excape = false; // must work out if we should excape the data

                if (excape)
                    sb.Append(string.Format(" = '{0}'", dr[Pk[i]].ToString()));
                else
                    sb.Append(string.Format(" = {0} ", dr[Pk[i]].ToString()));

            }
            return sb.ToString();
        }

        private void DoFullDataRowCompare(DataRow dr1, DataRow dr2, DataTableComparerResult result)
        {
            RowDiffrences diff = new RowDiffrences(); 
            foreach (DataColumn dc in dr1.Table.Columns)
            {
                object object1 = dr1[dc];
                object object2 = dr2[dc.ColumnName];
                if (!object1.Equals(object2))
                {
                    diff.ColumnsWithDiffrences.Add(dc.ColumnName);
                    //dr1[dc] = object2; // force the change over the row.. 
                }
            }
            if (diff.ColumnsWithDiffrences.Count > 0)
            {
                diff.FirstDataRow = dr1;
                diff.SecondDataRow = dr2;
                // only add if there are diffrences
                result.RowsWithDiffrences.Add(diff);
            }
        }


        private void FindAndMarkDiffrencesandNewRecordsInDt2(DataTable firstDataTable, DataTable secondDataTable, DataTableComparerResult result)
        {   
            /*
             * loop through secondDataTable, and do a Find() for each PK
                value in firstDataTable. If a match found, check each field value between firstDataTable and secondDataTable If all same, do nothing. 
             * If any differences, 
                  update values in firstDataTable dataset (thus setting rowstate to modified) - 
             * if no match found, 
                  add row to firstDataTable , thus setting rowstate to added. 

             Now my original dataset contains rows marked as added and modified (and unchanged)
            */
            foreach(DataRow dr in secondDataTable.Rows)
            {
                DataRow[] orginaldr = firstDataTable.Select(GenerateSelectFilter(dr, secondDataTable.PrimaryKey));
                if (orginaldr == null || orginaldr.Length == 0)
                {
                    result.RowsOnlyInTable2.Add(dr);
                    // it is new so add the record to 
                    //CopyRow(firstDataTable, dr);
                }
                else
                {
                    DoFullDataRowCompare(orginaldr[0], dr, result);
                }
            }
        }

        private void FindAndMarkNewRecordsInDt1(DataTable dt1, DataTable dt2, DataTableComparerResult result)
        {
            /*
                now need to find which rows from the dt1 need to be marked as
                deleted; to do this  reverse loop through the dt1 (NOT
                using enumerator), and do a row Find() against dt2
                if no match found 
                    call Rows[counter].Delete(), thus
                marking the rowstate as deleted in dt1
             * */

            foreach (DataRow dr in dt1.Rows)
            {
                DataRow[] orginaldr = dt2.Select(GenerateSelectFilter(dr, dt1.PrimaryKey));
                if (orginaldr == null || orginaldr.Length == 0)
                {
                    result.RowsOnlyInTable1.Add(dr);
                    //dr.Delete();
                    // it is new so add the record to dt1..
                }
            }
        }

        /*
        private void tempPrintDT(DataView dt, string change)
        {
            System.Diagnostics.Debug.WriteLine(change);
            if (dt != null)
            {
                foreach (DataRowView dr in dt)
                {
                    System.Diagnostics.Debug.WriteLine(
                        string.Format("{0},{1},{2}", dr[0].ToString(), dr[1].ToString(), dr[2].ToString()));
                }
            }
        }

        private void ReportOnChanges(DataTable dt1)
        {
            /*
            finally, to get the added, modified and deleted rows, create a
            dataview for each of the DataViewRowState (ModifiedCurrent, Added and
            Deleted) - to view the values, get an enumerator on the dataviews and
            get hold of the DataRow views (with delete need to pass the optional
            DataRowVersion.Original arg in the field index to avoid exception being
            thrown)
             * * /

            //DataTable dtNewRecords = dt1.GetChanges(DataRowState.Added);
            dt1.DefaultView.RowStateFilter = DataViewRowState.Added;
            tempPrintDT(dt1.DefaultView, "dtNewRecords");
            //DataTable dtDeletedRecords = dt1.GetChanges(DataRowState.Deleted);
            dt1.DefaultView.RowStateFilter = DataViewRowState.Deleted;
            tempPrintDT(dt1.DefaultView, "dtDeletedRecords");


            dt1.DefaultView.RowStateFilter = DataViewRowState.ModifiedOriginal;
            //DataTable dtModifiedRecords = dt1.GetChanges(DataRowState.Modified);
            tempPrintDT(dt1.DefaultView, "dtModifiedRecords");

            dt1.DefaultView.RowStateFilter = DataViewRowState.ModifiedCurrent;
            //DataTable dtModifiedRecords = dt1.GetChanges(DataRowState.Modified);
            tempPrintDT(dt1.DefaultView, "ModifiedCurrent");

           // DataTable dtModifiedRecords = dt1.GetChanges(DataViewRowState.ModifiedCurrent);
           // tempPrintDT(dtModifiedRecords, "dtModifiedRecords");
        }
         */


        // all the know exceptions get thrown in here.. 
        private void ValidateComparerPreRequsits(DataTable firstDataTable, DataTable secondDataTable, string[] primaryKeys)
        {
            // make sure firstDataTable and secondDataTable have no changes
            if (HasChanges(firstDataTable) || HasChanges(secondDataTable))
            {
                throw new InvalidDataException("Cannot Compare data tables that have pending changes");
            }
            if (primaryKeys.Length == 0)
            {
                throw new InvalidDataException("Cannot Compare data tables with no primary key defined");
            }
            HasSameDataStructure(firstDataTable, secondDataTable);
            SetPrimaryKey(firstDataTable, primaryKeys);
            SetPrimaryKey(secondDataTable, primaryKeys);
        }


        public DataTableComparerResult Compare(DataTable firstDataTable, DataTable secondDataTable, string[] primaryKeys)
        {
            ValidateComparerPreRequsits(firstDataTable, secondDataTable, primaryKeys);   
            DataTableComparerResult result = new DataTableComparerResult();
            // Now we can do the comparison..
            // assume DT1 is orginal data table and secondDataTable is the comparison
            FindAndMarkDiffrencesandNewRecordsInDt2(firstDataTable, secondDataTable, result);
            FindAndMarkNewRecordsInDt1(firstDataTable, secondDataTable, result);
            return result;
        }
    }
}

Inheritance Hierarchy

System..::.Object
  CA.Common.Data..::.DataTableComparer

See Also