32

I have 2 datatables and I just want to know if they are the same or not. By "the same", I mean do they have exactly the same number of rows with exactly the same data in each column, or not. I'd love to write (find) a method which accepts both tables and returns a boolean.

How can I compare 2 datatables in this way? Both have identical schemas.

JDB
  • 25,172
  • 5
  • 72
  • 123
MAW74656
  • 3,449
  • 21
  • 71
  • 118
  • This has been asked here: http://stackoverflow.com/questions/164144/c-how-to-compare-two-datatables-a-b-how-to-show-rows-which-are-in-b-but-not The question is not EXACTLY the same. In your case you'd need to loop through the rows, and within that loop, loop through the columns in teh row to compare values. – David Sep 22 '11 at 16:10
  • 1
    @DavidStratton -Sorry, this is not the same question. – MAW74656 Sep 22 '11 at 16:11
  • What is the purpose for wanting to do this to give it some context? – Paul C Sep 22 '11 at 16:12
  • I know that's why I didn't vote to close. I edited my comment. This post shows how to do it, following the general advice of my edited comment: http://canlu.blogspot.com/2009/05/how-to-compare-two-datatables-in-adonet.html – David Sep 22 '11 at 16:12
  • @CodeBlend -I'm comparing the original table with a possibly modified version of that same table. If nothing has changed, I want to print a report, if something has changed, I want to update the values then print the report. – MAW74656 Sep 22 '11 at 16:13
  • @Shredder -No, I can write such a loop. It seems inefficient, so I asked to see if anyone had a better way. – MAW74656 Sep 22 '11 at 16:14
  • @MAW74656 What is your datasource for the datatables? – Paul C Sep 22 '11 at 16:15
  • @CodeBlend -I've written a stored procedure and I use SQLCommand object and SqlDataAdapter to fill the table. – MAW74656 Sep 22 '11 at 16:16
  • FYI in case you are just trying to "get the job done" there ARE some commercial tools that will do this... RedGate has two products - SQLCompare for the schema and SQLDataCompare for the data. Very nice tools. But since that would assume that this is a "non-programming" question I assume that's not what you were looking for. – Michael Bray Sep 22 '11 at 16:18
  • Maybe my answer will prove useful, it is what I have seen others use to determine if there are changes from the original dataset – Paul C Sep 22 '11 at 16:20
  • @MichaelBray -These sound like they would not work for me, I want to do comparison without involved SQL server. – MAW74656 Sep 22 '11 at 16:24

12 Answers12

29
 public static bool AreTablesTheSame( DataTable tbl1, DataTable tbl2)
 {
    if (tbl1.Rows.Count != tbl2.Rows.Count || tbl1.Columns.Count != tbl2.Columns.Count)
                return false;


    for ( int i = 0; i < tbl1.Rows.Count; i++)
    {
        for ( int c = 0; c < tbl1.Columns.Count; c++)
        {
            if (!Equals(tbl1.Rows[i][c] ,tbl2.Rows[i][c]))
                        return false;
        }
     }
     return true;
  }
Michael Freidgeim
  • 26,542
  • 16
  • 152
  • 170
Nick Rolando
  • 25,879
  • 13
  • 79
  • 119
  • -Yes, I understand how this could work. I might see if its faster than the other option. PizzaTime! – MAW74656 Sep 22 '11 at 16:28
  • 1
    The code is not compilable, and also should use !Equals instead of '!=' operator. See post http://geekswithblogs.net/mnf/archive/2013/08/27/methods-to-verify-are-datatables-or-datasets-the-same.aspx – Michael Freidgeim Aug 27 '13 at 11:55
24

If you were returning a DataTable as a function you could:

DataTable dataTable1; // Load with data
DataTable dataTable2; // Load with data (same schema)

// Fast check for row count equality.
if ( dataTable1.Rows.Count != dataTable2.Rows.Count) {
    return true;
}

var differences =
    dataTable1.AsEnumerable().Except(dataTable2.AsEnumerable(),
                                            DataRowComparer.Default);

return differences.Any() ? differences.CopyToDataTable() : new DataTable();
Dave Markle
  • 95,573
  • 20
  • 147
  • 170
samneric
  • 3,038
  • 2
  • 28
  • 31
  • 1
    Not bad at all, this – netfed Sep 28 '17 at 05:33
  • But when you remove a row from a table, the difference does not show. hmm! – netfed Sep 28 '17 at 06:23
  • 1
    @netfed I updated the answer with a check to make sure that doesn't happen. – Dave Markle Jun 18 '19 at 16:57
  • There is an edge case here if the two tables do not have primary keys - a duplicate row in the first table, plus a different row in the second table. If you need a stronger guarantee and tables might not have primary keys (or in other words, could contain duplicates), then the Except needs to be run twice (table1 except table2, and table2 except table1). – topsail Apr 06 '22 at 20:41
9

You would need to loop through the rows of each table, and then through each column within that loop to compare individual values.

There's a code sample here: http://canlu.blogspot.com/2009/05/how-to-compare-two-datatables-in-adonet.html

David
  • 72,686
  • 18
  • 132
  • 173
  • -I used this and wrote a public method to call the code and return the boolean. See edit for code used. – MAW74656 Sep 22 '11 at 16:22
  • The link sample code only works with 32 columns or less. Otherwise you get a "Cannot have more than 32 columns" error. – Heisenberg Feb 13 '23 at 17:56
7

The OP, MAW74656, originally posted this answer in the question body in response to the accepted answer, as explained in this comment:

I used this and wrote a public method to call the code and return the boolean.

The OP's answer:

Code Used:

public bool tablesAreTheSame(DataTable table1, DataTable table2)
{
    DataTable dt;
    dt = getDifferentRecords(table1, table2);

    if (dt.Rows.Count == 0)
        return true;
    else
        return false;
}

//Found at http://canlu.blogspot.com/2009/05/how-to-compare-two-datatables-in-adonet.html
private DataTable getDifferentRecords(DataTable FirstDataTable, DataTable SecondDataTable)
{
    //Create Empty Table     
    DataTable ResultDataTable = new DataTable("ResultDataTable");

    //use a Dataset to make use of a DataRelation object     
    using (DataSet ds = new DataSet())
    {
        //Add tables     
        ds.Tables.AddRange(new DataTable[] { FirstDataTable.Copy(), SecondDataTable.Copy() });

        //Get Columns for DataRelation     
        DataColumn[] firstColumns = new DataColumn[ds.Tables[0].Columns.Count];
        for (int i = 0; i < firstColumns.Length; i++)
        {
            firstColumns[i] = ds.Tables[0].Columns[i];
        }

        DataColumn[] secondColumns = new DataColumn[ds.Tables[1].Columns.Count];
        for (int i = 0; i < secondColumns.Length; i++)
        {
            secondColumns[i] = ds.Tables[1].Columns[i];
        }

        //Create DataRelation     
        DataRelation r1 = new DataRelation(string.Empty, firstColumns, secondColumns, false);
        ds.Relations.Add(r1);

        DataRelation r2 = new DataRelation(string.Empty, secondColumns, firstColumns, false);
        ds.Relations.Add(r2);

        //Create columns for return table     
        for (int i = 0; i < FirstDataTable.Columns.Count; i++)
        {
            ResultDataTable.Columns.Add(FirstDataTable.Columns[i].ColumnName, FirstDataTable.Columns[i].DataType);
        }

        //If FirstDataTable Row not in SecondDataTable, Add to ResultDataTable.     
        ResultDataTable.BeginLoadData();
        foreach (DataRow parentrow in ds.Tables[0].Rows)
        {
            DataRow[] childrows = parentrow.GetChildRows(r1);
            if (childrows == null || childrows.Length == 0)
                ResultDataTable.LoadDataRow(parentrow.ItemArray, true);
        }

        //If SecondDataTable Row not in FirstDataTable, Add to ResultDataTable.     
        foreach (DataRow parentrow in ds.Tables[1].Rows)
        {
            DataRow[] childrows = parentrow.GetChildRows(r2);
            if (childrows == null || childrows.Length == 0)
                ResultDataTable.LoadDataRow(parentrow.ItemArray, true);
        }
        ResultDataTable.EndLoadData();
    }

    return ResultDataTable;
}
Community
  • 1
  • 1
JDB
  • 25,172
  • 5
  • 72
  • 123
  • I tried this and it maxes out at 32 columns at DataRelation. – sinDizzy Nov 20 '20 at 17:29
  • Same issue i.e. "maxes out at 32 columns" facing when used that getDifferentRecords method... but it workes in less column.. –  Mar 02 '22 at 11:51
6

Try to make use of linq to Dataset

(from b in table1.AsEnumerable()  
    select new { id = b.Field<int>("id")}).Except(
         from a in table2.AsEnumerable() 
             select new {id = a.Field<int>("id")})

Check this article : Comparing DataSets using LINQ

Pranay Rana
  • 175,020
  • 35
  • 237
  • 263
  • -I didn't specify this in the question (hence the +1), but I want to avoid LINQ so I can run on workstations with .NET 2.0. Yes, its lame, but its sort of a requirement. – MAW74656 Sep 22 '11 at 16:34
3
    /// <summary>
    /// https://stackoverflow.com/a/45620698/2390270
    /// Compare a source and target datatables and return the row that are the same, different, added, and removed
    /// </summary>
    /// <param name="dtOld">DataTable to compare</param>
    /// <param name="dtNew">DataTable to compare to dtOld</param>
    /// <param name="dtSame">DataTable that would give you the common rows in both</param>
    /// <param name="dtDifferences">DataTable that would give you the difference</param>
    /// <param name="dtAdded">DataTable that would give you the rows added going from dtOld to dtNew</param>
    /// <param name="dtRemoved">DataTable that would give you the rows removed going from dtOld to dtNew</param>
    public static void GetTableDiff(DataTable dtOld, DataTable dtNew, ref DataTable dtSame, ref DataTable dtDifferences, ref DataTable dtAdded, ref DataTable dtRemoved)
    {
        try
        {
            dtAdded = dtOld.Clone();
            dtAdded.Clear();
            dtRemoved = dtOld.Clone();
            dtRemoved.Clear();
            dtSame = dtOld.Clone();
            dtSame.Clear();
            if (dtNew.Rows.Count > 0) dtDifferences.Merge(dtNew.AsEnumerable().Except(dtOld.AsEnumerable(), DataRowComparer.Default).CopyToDataTable<DataRow>());
            if (dtOld.Rows.Count > 0) dtDifferences.Merge(dtOld.AsEnumerable().Except(dtNew.AsEnumerable(), DataRowComparer.Default).CopyToDataTable<DataRow>());
            if (dtOld.Rows.Count > 0 && dtNew.Rows.Count > 0) dtSame = dtOld.AsEnumerable().Intersect(dtNew.AsEnumerable(), DataRowComparer.Default).CopyToDataTable<DataRow>();
            foreach (DataRow row in dtDifferences.Rows)
            {
                if (dtOld.AsEnumerable().Any(r => Enumerable.SequenceEqual(r.ItemArray, row.ItemArray))
                    && !dtNew.AsEnumerable().Any(r => Enumerable.SequenceEqual(r.ItemArray, row.ItemArray)))
                {
                    dtRemoved.Rows.Add(row.ItemArray);
                }
                else if (dtNew.AsEnumerable().Any(r => Enumerable.SequenceEqual(r.ItemArray, row.ItemArray))
                    && !dtOld.AsEnumerable().Any(r => Enumerable.SequenceEqual(r.ItemArray, row.ItemArray)))
                {
                    dtAdded.Rows.Add(row.ItemArray);
                }
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.ToString());
        }
    }
Audreth
  • 67
  • 5
0

Inspired by samneric's answer using DataRowComparer.Default but needing something that would only compare a subset of columns within a DataTable, I made a DataTableComparer object where you can specify which columns to use in the comparison. Especially great if they have different columns/schemas.

DataRowComparer.Default works because it implements IEqualityComparer. Then I created an object where you can define which columns of the DataRow will be compared.

public class DataTableComparer : IEqualityComparer<DataRow>
{
    private IEnumerable<String> g_TestColumns;
    public void SetCompareColumns(IEnumerable<String> p_Columns)
    {
        g_TestColumns = p_Columns; 
    }

    public bool Equals(DataRow x, DataRow y)
    {

        foreach (String sCol in g_TestColumns)
            if (!x[sCol].Equals(y[sCol])) return false;

        return true;
    }

    public int GetHashCode(DataRow obj)
    {
        StringBuilder hashBuff = new StringBuilder();

        foreach (String sCol in g_TestColumns)
            hashBuff.AppendLine(obj[sCol].ToString());               

        return hashBuff.ToString().GetHashCode();

    }
}

You can use this by:

DataTableComparer comp = new DataTableComparer();
comp.SetCompareColumns(new String[] { "Name", "DoB" });

DataTable celebrities = SomeDataTableSource();
DataTable politicians = SomeDataTableSource2();

List<DataRow> celebrityPoliticians = celebrities.AsEnumerable().Intersect(politicians.AsEnumerable(), comp).ToList();
Basic.Bear
  • 111
  • 1
  • 9
0

How about merging 2 data tables and then comparing the changes? Not sure if that will fill 100% of your needs but for the quick compare it will do a job.

public DataTable GetTwoDataTablesChanges(DataTable firstDataTable, DataTable secondDataTable)
{ 
     firstDataTable.Merge(secondDataTable);
     return secondDataTable.GetChanges();
}

You can read more about DataTable.Merge()

here

Losbaltica
  • 609
  • 1
  • 10
  • 24
0

There is nothing out there that is going to do this for you; the only way you're going to accomplish this is to iterate all the rows/columns and compare them to each other.

CodingGorilla
  • 19,612
  • 4
  • 45
  • 65
  • -Hopefully next version of .NET will include a DataTable.CompareTo(DataTable) method to take care of this for us. – MAW74656 Sep 22 '11 at 16:35
0

or this, I did not implement the array comparison so you will also have some fun :)

public bool CompareTables(DataTable a, DataTable b)
{
    if(a.Rows.Count != b.Rows.Count)
    {
        // different size means different tables
        return false;
    }

    for(int rowIndex=0; rowIndex<a.Rows.Count; ++rowIndex)
    {
        if(!arraysHaveSameContent(a.Rows[rowIndex].ItemArray, b.Rows[rowIndex].ItemArray,))
        {
            return false;
        }
    }

    // Tables have same data
    return true;
}

private bool arraysHaveSameContent(object[] a, object[] b)
{
    // Here your super cool method to compare the two arrays with LINQ,
    // or if you are a loser do it with a for loop :D
}
Nick Rolando
  • 25,879
  • 13
  • 79
  • 119
Davide Piras
  • 43,984
  • 10
  • 98
  • 147
0

Well if you are using a DataTable at all then rather than comparing two 'DataTables' could you just compare the DataTable that is going to have changes with the original data when it was loaded AKA DataTable.GetChanges Method (DataRowState)

Paul C
  • 4,687
  • 5
  • 39
  • 55
-1

If you have the tables in a database, you can make a full outer join to get the differences. Example:

select t1.Field1, t1.Field2, t2.Field1, t2.Field2
from Table1 t1
full outer join Table2 t2 on t1.Field1 = t2.Field1 and t1.Field2 = t2.Field2
where t1.Field1 is null or t2.Field2 is null

All records that are identical are filtered out. There is data either in the first two or the last two fields, depending on what table the record comes from.

Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • 1
    -I want to do comparison without involved SQL server (trying to save a round trip to DB), so this would not be suitable for me in this case. – MAW74656 Sep 22 '11 at 16:27
  • this question is to compare two datatables. The data could come from different sources of databases. – ArnaldoRivera Dec 26 '20 at 15:12