this class is still work in progress more documentation to follow ...
Namespace:
CA.Common.DataAssembly: CA.Common (in CA.Common.dll) Version: 1.0.0.0 (1.0.0.0)
Remarks
source code:
//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; } } }