├── src ├── DataTableComparison.Tests │ ├── DataTableComparison.Tests.csproj │ └── Unit │ │ └── DataTableComparerTests.cs ├── DataTableComparison │ ├── DataTableComparerResult.cs │ ├── DataTableComparerConfig.cs │ ├── DataTableComparison.csproj │ └── DataTableComparer.cs └── DataTableComparison.sln ├── README.md ├── LICENSE └── .gitignore /src/DataTableComparison.Tests/DataTableComparison.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ..\..\..\ssax-primitive-extensions\src\SSAx.PrimitiveExtensions\bin\Debug\netstandard2.0\SSAx.PrimitiveExtensions.dll 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/DataTableComparison/DataTableComparerResult.cs: -------------------------------------------------------------------------------- 1 | using PrimitiveExtensions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Data; 5 | using System.Linq; 6 | 7 | namespace DataTableComparison 8 | { 9 | /// 10 | /// Helper class to compare multiple data tables 11 | /// 12 | public class DataTableComparerResult 13 | { 14 | public DataTableComparer DataTableComparer { get; internal set; } 15 | public DataTable ResultsDataTable { get; internal set; } 16 | public bool AllTablesContainTheSamePrimaryKeysRows() 17 | { 18 | return ResultsDataTable.Select($"[{DataTableComparer.Config.ExistsInColumnNamePrefix}{DataTableComparer.Config.WordSeperator}Status] = '{DataTableComparer.Config.OutOfSyncPhrase}'") 19 | .Count() == 0; 20 | } 21 | 22 | public DataTableComparerResult(DataTableComparer dataTableComparer) 23 | { 24 | DataTableComparer = dataTableComparer; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/DataTableComparison/DataTableComparerConfig.cs: -------------------------------------------------------------------------------- 1 | using PrimitiveExtensions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Data; 5 | using System.Linq; 6 | 7 | namespace DataTableComparison 8 | { 9 | public class DataTableComparerConfig 10 | { 11 | 12 | public DataTableComparerConfig(string inSyncPhrase = "In Sync", string outOfSyncPhrase = "Out of Sync", string existsInColumnName = "Exists_In", string wordSeperator = "_") 13 | { 14 | InSyncPhrase = inSyncPhrase; 15 | OutOfSyncPhrase = outOfSyncPhrase; 16 | ExistsInColumnNamePrefix = existsInColumnName; 17 | WordSeperator = wordSeperator; 18 | } 19 | 20 | public bool ColumnNamesCaseSensitive { get; set; } 21 | public bool DataFieldValuesCaseSensitive { get; set; } 22 | public bool IncludeUniqueColumnsInOutput { get; set; } 23 | 24 | public string InSyncPhrase { get; set; } 25 | public string OutOfSyncPhrase { get; set; } 26 | public string ExistsInColumnNamePrefix { get; set; } 27 | public string WordSeperator { get; set; } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/DataTableComparison/DataTableComparison.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | true 6 | false 7 | Joseph Amalfitano 8 | Social Security Administration 9 | DataTableComparison 10 | A .NET Standard 2.0 class library that allows for the comparison of the data and structure in two or more System.DataTable objects, with a focus on data comparison. Table structures do not have to be identical, but they need to have the same primary key structure. 11 | https://github.com/SSAgov/datatable-comparer 12 | https://github.com/SSAgov/datatable-comparer/blob/master/LICENSE 13 | DataTable;Comparison;Comparer;Compare;Table 14 | 1.0.0 15 | DataTableComparison 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/DataTableComparison.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.645 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataTableComparison", "DataTableComparison\DataTableComparison.csproj", "{0FEC162E-EF49-42BC-9129-F2B74646B77E}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3E76F9DB-50F8-479F-8F80-5498DDD789F7}" 9 | ProjectSection(SolutionItems) = preProject 10 | ..\.gitignore = ..\.gitignore 11 | ..\LICENSE = ..\LICENSE 12 | ..\README.md = ..\README.md 13 | EndProjectSection 14 | EndProject 15 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataTableComparison.Tests", "DataTableComparison.Tests\DataTableComparison.Tests.csproj", "{8A7B2E22-2103-4FED-A23E-0E9ABCA75DBE}" 16 | EndProject 17 | Global 18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 19 | Debug|Any CPU = Debug|Any CPU 20 | Release|Any CPU = Release|Any CPU 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {0FEC162E-EF49-42BC-9129-F2B74646B77E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {0FEC162E-EF49-42BC-9129-F2B74646B77E}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {0FEC162E-EF49-42BC-9129-F2B74646B77E}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {0FEC162E-EF49-42BC-9129-F2B74646B77E}.Release|Any CPU.Build.0 = Release|Any CPU 27 | {8A7B2E22-2103-4FED-A23E-0E9ABCA75DBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {8A7B2E22-2103-4FED-A23E-0E9ABCA75DBE}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {8A7B2E22-2103-4FED-A23E-0E9ABCA75DBE}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {8A7B2E22-2103-4FED-A23E-0E9ABCA75DBE}.Release|Any CPU.Build.0 = Release|Any CPU 31 | EndGlobalSection 32 | GlobalSection(SolutionProperties) = preSolution 33 | HideSolutionNode = FALSE 34 | EndGlobalSection 35 | GlobalSection(ExtensibilityGlobals) = postSolution 36 | SolutionGuid = {45734A30-5B1F-46A8-918E-8540A26CDB3D} 37 | EndGlobalSection 38 | EndGlobal 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # DataTable Comparer 3 | 4 | A .NET Standard 2.0 class library that allows for the comparison of the data and structure in two or more System.DataTable objects, with a focus on data comparison. Table structures do not have to be identical, but they need to have the same primary key structure. 5 | 6 | ## NuGet Package 7 | 8 | This library is available as a compiled package is on Nuget.org 9 | 10 | 11 | 12 | ## Example 13 | 14 | In the example below are two tables. The row 2 First_Name is different (Table->"Beatice", Table_1->"Samone"). 15 | In the Rest table (Datatabe Comparer) the *_`Out of sync`_* in row 2 indicates the differnce between the two rows. 16 | 17 | Table 18 | 19 | | ID |First_Name | Last_Name | 20 | | --- |:-------------| ----------:| 21 | | 1 | Joe | Smith | 22 | | 2 | Beatrice | Smith | 23 | | 3 | James | Bond | 24 | 25 | Table_1 26 | 27 | | ID |First_Name | Last_Name | 28 | | --- |:-------------| ----------:| 29 | | 1 | Joe | Smith | 30 | | 2 | Samone | Smith | 31 | | 3 | James | Bond | 32 | 33 | Datatable Comparer Result 34 | 35 | | ID | Exists_In_Table | Exists_In Table_1 | Exists_In_Status | First_Name_Status | Last_Name Status | First_Name_Table | First_Name_Table_1 | Last_Name_Table | Last_Name_Table_1 | 36 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | 37 | | 1 | Yes | Yes | In sync | In sync | In sync | Joe | Joe | Smith | Smith | 38 | | 2 | Yes | Yes | In sync | **_`Out of sync`_** | In sync | Beatrice | Samone | Smith | Smith | 39 | | 3 | Yes | Yes | In sync | In sync | In sync | James | James | Bond | Bond | 40 | 41 | Here is another example with three tables, table 3 has extra row 42 | 43 | Table 44 | 45 | | ID |First_Name | Last_Name | 46 | | --- |:-------------| ----------:| 47 | | 1 | Joe | Smith | 48 | | 2 | Samone | Smith | 49 | | 3 | James | Bond | 50 | 51 | Table_1 52 | 53 | | ID |First_Name | Last_Name | 54 | | --- |:-------------| ----------:| 55 | | 1 | Joe | Smith | 56 | | 2 | Samone | Smith | 57 | | 3 | James | Bond | 58 | 59 | Table_2 60 | 61 | | ID |First_Name | Last_Name | 62 | | --- |:-------------| ----------:| 63 | | 1 | Joe | Smith | 64 | | 2 | Samone | Smith | 65 | | 3 | James | Bond | 66 | | 4 | Joe | Bond | 67 | 68 | Datatable Comparer Result 69 | 70 | | ID |Exists_In_Table|Exists_In_Table_1|Exists_In_Table_2|Exists_In_Status|FIRST_NAME_Status|LAST_NAME_Status|First_Name_Table|First_Name_Table_1|First_Name_Table_2|Last_Name_Table|Last_Name_Table_1|Last_Name_Table_2| 71 | | --- |:--------------| ---------------:|----------------:| --------------:| ---------------:| --------------:| --------------:| ----------------:| ----------------:| -------------:| ---------------:| ---------------:| 72 | | 1 | Yes | Yes | Yes| In sync | In sync| In sync| Joe| Joe| Joe| Smith| Smith| Smith| 73 | | 2 | Yes | Yes | Yes| In sync | In sync| In sync| Samone| Samone| Samone| Smith| Smith| Smith| 74 | | 3 | Yes | Yes | Yes| In sync | In sync| In sync| James| James| James| Bond| Bond| Bond| 75 | | 4 | | | Yes| Out of sync| | | | | Joe| | | Bond| 76 | -------------------------------------------------------------------------------- /src/DataTableComparison.Tests/Unit/DataTableComparerTests.cs: -------------------------------------------------------------------------------- 1 | using PrimitiveExtensions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Text; 7 | using Xunit; 8 | 9 | namespace DataTableComparison.Tests.Unit 10 | { 11 | public class DataTableComparerTests 12 | { 13 | 14 | [Fact] 15 | public void Add_0_expect_empty() 16 | { 17 | DataTableComparer comparer = new DataTableComparer(); 18 | Assert.Empty(comparer.DataTables); 19 | } 20 | 21 | 22 | [Fact] 23 | public void Add_1_expect_1() 24 | { 25 | DataTableComparer comparer = new DataTableComparer(); 26 | comparer.AddTable(new DataTable()); 27 | Assert.Single(comparer.DataTables); 28 | } 29 | 30 | [Fact] 31 | public void Add_2_expect_2() 32 | { 33 | DataTableComparer comparer = new DataTableComparer(); 34 | comparer.AddTable(new DataTable()); 35 | comparer.AddTable(new DataTable()); 36 | Assert.Equal(2, comparer.DataTables.Count); 37 | } 38 | 39 | [Fact] 40 | public void Add_3_expect_3() 41 | { 42 | DataTableComparer comparer = new DataTableComparer(); 43 | comparer.AddTable(new DataTable("table1")); 44 | comparer.AddTable(new DataTable("table2")); 45 | comparer.AddTable(new DataTable("table3")); 46 | IEnumerable tableNames = comparer.DataTables.Select(a => a.TableName); 47 | 48 | Assert.Equal(3, comparer.DataTables.Count); 49 | Assert.Contains("table1", tableNames); 50 | Assert.Contains("table2", tableNames); 51 | Assert.Contains("table3", tableNames); 52 | } 53 | 54 | [Fact] 55 | public void Remove_ValidRemovalOf1FromCollection_expect_2() 56 | { 57 | DataTableComparer comparer = new DataTableComparer(); 58 | DataTable dt1 = new DataTable("table1"); 59 | comparer.AddTable(dt1); 60 | comparer.AddTable(new DataTable("table2")); 61 | comparer.AddTable(new DataTable("table3")); 62 | Assert.True(comparer.RemoveTable(dt1)); 63 | 64 | IEnumerable tableNames = comparer.DataTables.Select(a => a.TableName); 65 | Assert.Equal(2, comparer.DataTables.Count); 66 | Assert.DoesNotContain("table1", tableNames); 67 | Assert.Contains("table2", tableNames); 68 | Assert.Contains("table3", tableNames); 69 | } 70 | 71 | [Fact] 72 | public void Remove_TableNeverAdded_expect_false() 73 | { 74 | DataTableComparer comparer = new DataTableComparer(); 75 | DataTable dt1 = new DataTable("table1"); 76 | Assert.False(comparer.RemoveTable(dt1)); 77 | } 78 | 79 | [Fact] 80 | public void GetResultsDataTable_NoTables_ExpectException() 81 | { 82 | DataTableComparer comparer = new DataTableComparer(); 83 | 84 | Assert.Throws(() => comparer.Compare()); 85 | } 86 | 87 | [Fact] 88 | public void GetResultsDataTable_OneTable_ExpectException() 89 | { 90 | DataTableComparer comparer = new DataTableComparer(); 91 | comparer.AddTable(new DataTable()); 92 | Assert.Throws(() => comparer.Compare()); 93 | } 94 | 95 | [Fact] 96 | public void GetResultsDataTable_TwoEmptyTables_Expect_no_rows_3_columns() 97 | { 98 | DataTableComparer comparer = new DataTableComparer(); 99 | comparer.AddTable(new DataTable("table1")); 100 | comparer.AddTable(new DataTable("table2")); 101 | DataTableComparerResult result = comparer.Compare(); 102 | DataTable dataTable = result.ResultsDataTable; 103 | IEnumerable columnNames = dataTable.Columns.Cast().Select(a => a.ColumnName); 104 | 105 | 106 | Assert.Contains("Exists_In_table1", columnNames); 107 | Assert.Contains("Exists_In_table2", columnNames); 108 | Assert.Contains("Exists_In_Status", columnNames); 109 | Assert.Empty(dataTable.Rows); 110 | Assert.Equal(3, dataTable.Columns.Count); 111 | } 112 | 113 | [Fact] 114 | public void GetResultsDataTable_TwoEmptyTables_Expect_no_rows_3_columns_customconfig() 115 | { 116 | DataTableComparerConfig config = new DataTableComparerConfig(); 117 | config.ExistsInColumnNamePrefix = "ABC"; 118 | DataTableComparer comparer = new DataTableComparer(config); 119 | comparer.AddTable(new DataTable("table1")); 120 | comparer.AddTable(new DataTable("table2")); 121 | DataTableComparerResult result = comparer.Compare(); 122 | DataTable dataTable = result.ResultsDataTable; 123 | IEnumerable columnNames = dataTable.Columns.Cast().Select(a => a.ColumnName); 124 | 125 | Assert.Contains("ABC_table1", columnNames); 126 | Assert.Contains("ABC_table2", columnNames); 127 | Assert.Contains("ABC_Status", columnNames); 128 | Assert.Empty(dataTable.Rows); 129 | Assert.Equal(3, dataTable.Columns.Count); 130 | } 131 | 132 | [Fact] 133 | public void GetResultsDataTable_TwoTablesWithSameData_Expect_2_rows_in_sync() 134 | { 135 | DataTableComparerConfig config = new DataTableComparerConfig(); 136 | config.InSyncPhrase = "true"; 137 | config.OutOfSyncPhrase = "false"; 138 | 139 | 140 | DataTableComparer comparer = new DataTableComparer(); 141 | DataTable dt1 = new DataTable("table1"); 142 | dt1.Columns.Add("Id"); 143 | dt1.Columns.Add("Name"); 144 | dt1.SetBestGuessPrimaryKey(); 145 | dt1.Rows.Add("1", "Apple"); 146 | dt1.Rows.Add("2", "Orange"); 147 | 148 | DataTable dt2 = new DataTable("table2"); 149 | dt2.Columns.Add("Id"); 150 | dt2.Columns.Add("Name"); 151 | dt2.SetBestGuessPrimaryKey(); 152 | dt2.Rows.Add("1", "Apple"); 153 | dt2.Rows.Add("2", "Orange"); 154 | 155 | comparer.AddTable(dt1); 156 | comparer.AddTable(dt2); 157 | 158 | DataTableComparerResult result = comparer.Compare(); 159 | DataTable dataTable = result.ResultsDataTable; 160 | IEnumerable columnNames = dataTable.Columns.Cast().Select(a => a.ColumnName); 161 | 162 | 163 | Assert.True(result.AllTablesContainTheSamePrimaryKeysRows()); 164 | Assert.Contains("Id", columnNames); 165 | Assert.Contains("Exists_In_table1", columnNames); 166 | Assert.Contains("Exists_In_table2", columnNames); 167 | Assert.Contains("Exists_In_Status", columnNames); 168 | Assert.Contains("Name_Status", columnNames); 169 | Assert.Contains("Name_table1", columnNames); 170 | Assert.Contains("Name_table2", columnNames); 171 | Assert.Equal(2, dataTable.Rows.Count); 172 | } 173 | [Fact] 174 | public void GetResultsDataTable_TwoTablesOneHasExtraRow_Expect_3() 175 | { 176 | DataTableComparerConfig config = new DataTableComparerConfig(); 177 | config.InSyncPhrase = "true"; 178 | config.OutOfSyncPhrase = "false"; 179 | 180 | 181 | DataTableComparer comparer = new DataTableComparer(); 182 | DataTable dt1 = new DataTable("table1"); 183 | dt1.Columns.Add("Id"); 184 | dt1.Columns.Add("Name"); 185 | dt1.SetBestGuessPrimaryKey(); 186 | dt1.Rows.Add("1", "Apple"); 187 | dt1.Rows.Add("2", "Orange"); 188 | 189 | DataTable dt2 = new DataTable("table2"); 190 | dt2.Columns.Add("Id"); 191 | dt2.Columns.Add("Name"); 192 | dt2.SetBestGuessPrimaryKey(); 193 | dt2.Rows.Add("1", "Apple"); 194 | dt2.Rows.Add("2", "Orange"); 195 | dt2.Rows.Add("3", "Orange"); 196 | 197 | comparer.AddTable(dt1); 198 | comparer.AddTable(dt2); 199 | 200 | DataTableComparerResult result = comparer.Compare(); 201 | DataTable dataTable = result.ResultsDataTable; 202 | IEnumerable columnNames = dataTable.Columns.Cast().Select(a => a.ColumnName); 203 | 204 | Assert.False(result.AllTablesContainTheSamePrimaryKeysRows()); 205 | Assert.Contains("Id", columnNames); 206 | Assert.Contains("Exists_In_table1", columnNames); 207 | Assert.Contains("Exists_In_table2", columnNames); 208 | Assert.Contains("Exists_In_Status", columnNames); 209 | Assert.Contains("Name_Status", columnNames); 210 | Assert.Contains("Name_table1", columnNames); 211 | Assert.Contains("Name_table2", columnNames); 212 | Assert.Equal(3, dataTable.Rows.Count); 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/csharp,visualstudio,visualstudiocode 3 | # Edit at https://www.gitignore.io/?templates=csharp,visualstudio,visualstudiocode 4 | 5 | ### Csharp ### 6 | ## Ignore Visual Studio temporary files, build results, and 7 | ## files generated by popular Visual Studio add-ons. 8 | ## 9 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 10 | 11 | # User-specific files 12 | *.rsuser 13 | *.suo 14 | *.user 15 | *.userosscache 16 | *.sln.docstates 17 | 18 | # User-specific files (MonoDevelop/Xamarin Studio) 19 | *.userprefs 20 | 21 | # Mono auto generated files 22 | mono_crash.* 23 | 24 | # Build results 25 | [Dd]ebug/ 26 | [Dd]ebugPublic/ 27 | [Rr]elease/ 28 | [Rr]eleases/ 29 | x64/ 30 | x86/ 31 | [Aa][Rr][Mm]/ 32 | [Aa][Rr][Mm]64/ 33 | bld/ 34 | [Bb]in/ 35 | [Oo]bj/ 36 | [Ll]og/ 37 | 38 | # Visual Studio 2015/2017 cache/options directory 39 | .vs/ 40 | # Uncomment if you have tasks that create the project's static files in wwwroot 41 | #wwwroot/ 42 | 43 | # Visual Studio 2017 auto generated files 44 | Generated\ Files/ 45 | 46 | # MSTest test Results 47 | [Tt]est[Rr]esult*/ 48 | [Bb]uild[Ll]og.* 49 | 50 | # NUnit 51 | *.VisualState.xml 52 | TestResult.xml 53 | nunit-*.xml 54 | 55 | # Build Results of an ATL Project 56 | [Dd]ebugPS/ 57 | [Rr]eleasePS/ 58 | dlldata.c 59 | 60 | # Benchmark Results 61 | BenchmarkDotNet.Artifacts/ 62 | 63 | # .NET Core 64 | project.lock.json 65 | project.fragment.lock.json 66 | artifacts/ 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.vspscc 94 | *.vssscc 95 | .builds 96 | *.pidb 97 | *.svclog 98 | *.scc 99 | 100 | # Chutzpah Test files 101 | _Chutzpah* 102 | 103 | # Visual C++ cache files 104 | ipch/ 105 | *.aps 106 | *.ncb 107 | *.opendb 108 | *.opensdf 109 | *.sdf 110 | *.cachefile 111 | *.VC.db 112 | *.VC.VC.opendb 113 | 114 | # Visual Studio profiler 115 | *.psess 116 | *.vsp 117 | *.vspx 118 | *.sap 119 | 120 | # Visual Studio Trace Files 121 | *.e2e 122 | 123 | # TFS 2012 Local Workspace 124 | $tf/ 125 | 126 | # Guidance Automation Toolkit 127 | *.gpState 128 | 129 | # ReSharper is a .NET coding add-in 130 | _ReSharper*/ 131 | *.[Rr]e[Ss]harper 132 | *.DotSettings.user 133 | 134 | # JustCode is a .NET coding add-in 135 | .JustCode 136 | 137 | # TeamCity is a build add-in 138 | _TeamCity* 139 | 140 | # DotCover is a Code Coverage Tool 141 | *.dotCover 142 | 143 | # AxoCover is a Code Coverage Tool 144 | .axoCover/* 145 | !.axoCover/settings.json 146 | 147 | # Visual Studio code coverage results 148 | *.coverage 149 | *.coveragexml 150 | 151 | # NCrunch 152 | _NCrunch_* 153 | .*crunch*.local.xml 154 | nCrunchTemp_* 155 | 156 | # MightyMoose 157 | *.mm.* 158 | AutoTest.Net/ 159 | 160 | # Web workbench (sass) 161 | .sass-cache/ 162 | 163 | # Installshield output folder 164 | [Ee]xpress/ 165 | 166 | # DocProject is a documentation generator add-in 167 | DocProject/buildhelp/ 168 | DocProject/Help/*.HxT 169 | DocProject/Help/*.HxC 170 | DocProject/Help/*.hhc 171 | DocProject/Help/*.hhk 172 | DocProject/Help/*.hhp 173 | DocProject/Help/Html2 174 | DocProject/Help/html 175 | 176 | # Click-Once directory 177 | publish/ 178 | 179 | # Publish Web Output 180 | *.[Pp]ublish.xml 181 | *.azurePubxml 182 | # Note: Comment the next line if you want to checkin your web deploy settings, 183 | # but database connection strings (with potential passwords) will be unencrypted 184 | *.pubxml 185 | *.publishproj 186 | 187 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 188 | # checkin your Azure Web App publish settings, but sensitive information contained 189 | # in these scripts will be unencrypted 190 | PublishScripts/ 191 | 192 | # NuGet Packages 193 | *.nupkg 194 | # NuGet Symbol Packages 195 | *.snupkg 196 | # The packages folder can be ignored because of Package Restore 197 | **/[Pp]ackages/* 198 | # except build/, which is used as an MSBuild target. 199 | !**/[Pp]ackages/build/ 200 | # Uncomment if necessary however generally it will be regenerated when needed 201 | #!**/[Pp]ackages/repositories.config 202 | # NuGet v3's project.json files produces more ignorable files 203 | *.nuget.props 204 | *.nuget.targets 205 | 206 | # Microsoft Azure Build Output 207 | csx/ 208 | *.build.csdef 209 | 210 | # Microsoft Azure Emulator 211 | ecf/ 212 | rcf/ 213 | 214 | # Windows Store app package directories and files 215 | AppPackages/ 216 | BundleArtifacts/ 217 | Package.StoreAssociation.xml 218 | _pkginfo.txt 219 | *.appx 220 | *.appxbundle 221 | *.appxupload 222 | 223 | # Visual Studio cache files 224 | # files ending in .cache can be ignored 225 | *.[Cc]ache 226 | # but keep track of directories ending in .cache 227 | !?*.[Cc]ache/ 228 | 229 | # Others 230 | ClientBin/ 231 | ~$* 232 | *~ 233 | *.dbmdl 234 | *.dbproj.schemaview 235 | *.jfm 236 | *.pfx 237 | *.publishsettings 238 | orleans.codegen.cs 239 | 240 | # Including strong name files can present a security risk 241 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 242 | #*.snk 243 | 244 | # Since there are multiple workflows, uncomment next line to ignore bower_components 245 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 246 | #bower_components/ 247 | 248 | # RIA/Silverlight projects 249 | Generated_Code/ 250 | 251 | # Backup & report files from converting an old project file 252 | # to a newer Visual Studio version. Backup files are not needed, 253 | # because we have git ;-) 254 | _UpgradeReport_Files/ 255 | Backup*/ 256 | UpgradeLog*.XML 257 | UpgradeLog*.htm 258 | ServiceFabricBackup/ 259 | *.rptproj.bak 260 | 261 | # SQL Server files 262 | *.mdf 263 | *.ldf 264 | *.ndf 265 | 266 | # Business Intelligence projects 267 | *.rdl.data 268 | *.bim.layout 269 | *.bim_*.settings 270 | *.rptproj.rsuser 271 | *- [Bb]ackup.rdl 272 | *- [Bb]ackup ([0-9]).rdl 273 | *- [Bb]ackup ([0-9][0-9]).rdl 274 | 275 | # Microsoft Fakes 276 | FakesAssemblies/ 277 | 278 | # GhostDoc plugin setting file 279 | *.GhostDoc.xml 280 | 281 | # Node.js Tools for Visual Studio 282 | .ntvs_analysis.dat 283 | node_modules/ 284 | 285 | # Visual Studio 6 build log 286 | *.plg 287 | 288 | # Visual Studio 6 workspace options file 289 | *.opt 290 | 291 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 292 | *.vbw 293 | 294 | # Visual Studio LightSwitch build output 295 | **/*.HTMLClient/GeneratedArtifacts 296 | **/*.DesktopClient/GeneratedArtifacts 297 | **/*.DesktopClient/ModelManifest.xml 298 | **/*.Server/GeneratedArtifacts 299 | **/*.Server/ModelManifest.xml 300 | _Pvt_Extensions 301 | 302 | # Paket dependency manager 303 | .paket/paket.exe 304 | paket-files/ 305 | 306 | # FAKE - F# Make 307 | .fake/ 308 | 309 | # CodeRush personal settings 310 | .cr/personal 311 | 312 | # Python Tools for Visual Studio (PTVS) 313 | __pycache__/ 314 | *.pyc 315 | 316 | # Cake - Uncomment if you are using it 317 | # tools/** 318 | # !tools/packages.config 319 | 320 | # Tabs Studio 321 | *.tss 322 | 323 | # Telerik's JustMock configuration file 324 | *.jmconfig 325 | 326 | # BizTalk build output 327 | *.btp.cs 328 | *.btm.cs 329 | *.odx.cs 330 | *.xsd.cs 331 | 332 | # OpenCover UI analysis results 333 | OpenCover/ 334 | 335 | # Azure Stream Analytics local run output 336 | ASALocalRun/ 337 | 338 | # MSBuild Binary and Structured Log 339 | *.binlog 340 | 341 | # NVidia Nsight GPU debugger configuration file 342 | *.nvuser 343 | 344 | # MFractors (Xamarin productivity tool) working folder 345 | .mfractor/ 346 | 347 | # Local History for Visual Studio 348 | .localhistory/ 349 | 350 | # BeatPulse healthcheck temp database 351 | healthchecksdb 352 | 353 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 354 | MigrationBackup/ 355 | 356 | ### VisualStudioCode ### 357 | .vscode/* 358 | !.vscode/settings.json 359 | !.vscode/tasks.json 360 | !.vscode/launch.json 361 | !.vscode/extensions.json 362 | 363 | ### VisualStudioCode Patch ### 364 | # Ignore all local history of files 365 | .history 366 | 367 | ### VisualStudio ### 368 | 369 | # User-specific files 370 | 371 | # User-specific files (MonoDevelop/Xamarin Studio) 372 | 373 | # Mono auto generated files 374 | 375 | # Build results 376 | 377 | # Visual Studio 2015/2017 cache/options directory 378 | # Uncomment if you have tasks that create the project's static files in wwwroot 379 | 380 | # Visual Studio 2017 auto generated files 381 | 382 | # MSTest test Results 383 | 384 | # NUnit 385 | 386 | # Build Results of an ATL Project 387 | 388 | # Benchmark Results 389 | 390 | # .NET Core 391 | 392 | # StyleCop 393 | 394 | # Files built by Visual Studio 395 | 396 | # Chutzpah Test files 397 | 398 | # Visual C++ cache files 399 | 400 | # Visual Studio profiler 401 | 402 | # Visual Studio Trace Files 403 | 404 | # TFS 2012 Local Workspace 405 | 406 | # Guidance Automation Toolkit 407 | 408 | # ReSharper is a .NET coding add-in 409 | 410 | # JustCode is a .NET coding add-in 411 | 412 | # TeamCity is a build add-in 413 | 414 | # DotCover is a Code Coverage Tool 415 | 416 | # AxoCover is a Code Coverage Tool 417 | 418 | # Visual Studio code coverage results 419 | 420 | # NCrunch 421 | 422 | # MightyMoose 423 | 424 | # Web workbench (sass) 425 | 426 | # Installshield output folder 427 | 428 | # DocProject is a documentation generator add-in 429 | 430 | # Click-Once directory 431 | 432 | # Publish Web Output 433 | # Note: Comment the next line if you want to checkin your web deploy settings, 434 | # but database connection strings (with potential passwords) will be unencrypted 435 | 436 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 437 | # checkin your Azure Web App publish settings, but sensitive information contained 438 | # in these scripts will be unencrypted 439 | 440 | # NuGet Packages 441 | # NuGet Symbol Packages 442 | # The packages folder can be ignored because of Package Restore 443 | # except build/, which is used as an MSBuild target. 444 | # Uncomment if necessary however generally it will be regenerated when needed 445 | # NuGet v3's project.json files produces more ignorable files 446 | 447 | # Microsoft Azure Build Output 448 | 449 | # Microsoft Azure Emulator 450 | 451 | # Windows Store app package directories and files 452 | 453 | # Visual Studio cache files 454 | # files ending in .cache can be ignored 455 | # but keep track of directories ending in .cache 456 | 457 | # Others 458 | 459 | # Including strong name files can present a security risk 460 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 461 | 462 | # Since there are multiple workflows, uncomment next line to ignore bower_components 463 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 464 | 465 | # RIA/Silverlight projects 466 | 467 | # Backup & report files from converting an old project file 468 | # to a newer Visual Studio version. Backup files are not needed, 469 | # because we have git ;-) 470 | 471 | # SQL Server files 472 | 473 | # Business Intelligence projects 474 | 475 | # Microsoft Fakes 476 | 477 | # GhostDoc plugin setting file 478 | 479 | # Node.js Tools for Visual Studio 480 | 481 | # Visual Studio 6 build log 482 | 483 | # Visual Studio 6 workspace options file 484 | 485 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 486 | 487 | # Visual Studio LightSwitch build output 488 | 489 | # Paket dependency manager 490 | 491 | # FAKE - F# Make 492 | 493 | # CodeRush personal settings 494 | 495 | # Python Tools for Visual Studio (PTVS) 496 | 497 | # Cake - Uncomment if you are using it 498 | # tools/** 499 | # !tools/packages.config 500 | 501 | # Tabs Studio 502 | 503 | # Telerik's JustMock configuration file 504 | 505 | # BizTalk build output 506 | 507 | # OpenCover UI analysis results 508 | 509 | # Azure Stream Analytics local run output 510 | 511 | # MSBuild Binary and Structured Log 512 | 513 | # NVidia Nsight GPU debugger configuration file 514 | 515 | # MFractors (Xamarin productivity tool) working folder 516 | 517 | # Local History for Visual Studio 518 | 519 | # BeatPulse healthcheck temp database 520 | 521 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 522 | 523 | # End of https://www.gitignore.io/api/csharp,visualstudio,visualstudiocode -------------------------------------------------------------------------------- /src/DataTableComparison/DataTableComparer.cs: -------------------------------------------------------------------------------- 1 | using PrimitiveExtensions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Data; 5 | using System.Linq; 6 | 7 | namespace DataTableComparison 8 | { 9 | /// 10 | /// Helper class to compare multiple data tables 11 | /// 12 | public class DataTableComparer 13 | { 14 | public DataTableComparer() 15 | { 16 | DataTables = new List(); 17 | Config = new DataTableComparerConfig(); 18 | } 19 | 20 | public DataTableComparer(DataTableComparerConfig config) 21 | : this() 22 | { 23 | Config = config; 24 | } 25 | 26 | public DataTableComparerConfig Config { get; set; } 27 | 28 | public List DataTables { get; set; } 29 | 30 | public DataTableComparerResult Compare() 31 | { 32 | DataTableComparerResult result = new DataTableComparerResult(this); 33 | result.ResultsDataTable = GetResultsDataTable(); 34 | return result; 35 | } 36 | 37 | internal DataTable GetResultsDataTable() 38 | { 39 | if (DataTables.Count < 2) 40 | throw new Exception("Must have at least 2 tables added to compare"); 41 | 42 | 43 | List dtsTemp = new List(); 44 | List dts = new List(); 45 | DataTable dtResult = new DataTable(); 46 | dtResult.CaseSensitive = Config.DataFieldValuesCaseSensitive; 47 | 48 | DataTables.CopyTo(dts); 49 | 50 | foreach (DataTable dt in dts) 51 | dtsTemp.Add(dt); 52 | 53 | 54 | if (dts != null && dts.Count() > 0) 55 | { 56 | DataTable dtTemp = new DataTable(); 57 | List dtsCopy = new List(); 58 | dts.CopyTo(dtsCopy); 59 | 60 | foreach (DataTable dt in dtsCopy) 61 | { 62 | dt.AddColumnIfNotExists(Config.ExistsInColumnNamePrefix, typeof(string)); 63 | foreach (DataRow r in dt.Rows) 64 | r[Config.ExistsInColumnNamePrefix] = "Yes"; 65 | } 66 | 67 | DataColumn[] keys = new DataColumn[dtsCopy[0].PrimaryKey.Length]; 68 | for (int i = 0; i < dtsCopy[0].PrimaryKey.Length; i++) 69 | { 70 | try 71 | { 72 | Type t = dtsCopy[0].PrimaryKey[i].DataType; 73 | dtResult.AddColumnIfNotExists(dtsCopy[0].PrimaryKey[i].ColumnName, t);//typeof(string)); 74 | keys[i] = dtResult.Columns[dtsCopy[0].PrimaryKey[i].ColumnName]; 75 | //// result.CommonKeyColumnNames.Add(dtsCopy[0].PrimaryKey[i].ColumnName); 76 | } 77 | catch (Exception ex) 78 | { 79 | throw ex; 80 | } 81 | } 82 | 83 | dtResult.PrimaryKey = keys; 84 | List tableNames = new List(); 85 | List commonNonKeyColumns = dtsCopy.GetColumnNames_Shared_NonPrimaryKey().ToList(); 86 | List uniqueNonKeyColumns = dtsCopy.GetColumnNames_NotShared_NonPrimaryKey().ToList(); 87 | 88 | foreach (DataTable dt in dtsCopy) 89 | { 90 | tableNames.Add(dt.TableName); 91 | foreach (DataColumn col in dt.Columns) 92 | { 93 | col.AllowDBNull = true; 94 | if (commonNonKeyColumns.IndexOf(col.ColumnName) >= 0) 95 | { 96 | col.ColumnName = col.ColumnName + Config.WordSeperator + dt.TableName; 97 | } 98 | } 99 | foreach (DataColumn col in dtResult.Columns) 100 | { 101 | col.AllowDBNull = true; 102 | } 103 | try 104 | { 105 | dtResult.Merge(dt); 106 | } 107 | catch (Exception ex) 108 | { 109 | dtResult.Merge(dt, true, MissingSchemaAction.AddWithKey); 110 | throw ex; 111 | } 112 | } 113 | 114 | string expression = GetKeyStatusExpression(dtsCopy); 115 | dtResult.AddColumnIfNotExists($"{Config.ExistsInColumnNamePrefix}{Config.WordSeperator}Status", typeof(string), expression); 116 | 117 | foreach (string commonNonKeyColumn in commonNonKeyColumns) 118 | { 119 | expression = GetNonKeyStatusExpression(dtsCopy, commonNonKeyColumn, dtResult); 120 | if (expression != "") 121 | { 122 | dtResult.AddColumnIfNotExists($"{commonNonKeyColumn}{Config.WordSeperator}Status", typeof(string), expression, false); 123 | } 124 | } 125 | 126 | commonNonKeyColumns.Remove(Config.ExistsInColumnNamePrefix); 127 | foreach (string commonNonKeyColumn in commonNonKeyColumns) 128 | { 129 | foreach (string tableName in tableNames) 130 | { 131 | dtResult.SetColumnOrdinalIfExists(commonNonKeyColumn + Config.WordSeperator + tableName, dtResult.Columns.Count - 1); 132 | } 133 | } 134 | 135 | foreach (string uniqueNonKeyColumn in uniqueNonKeyColumns) 136 | { 137 | foreach (DataTable dt in dtsCopy) 138 | { 139 | if (Config.IncludeUniqueColumnsInOutput) 140 | { 141 | if (dt.Columns.Contains(uniqueNonKeyColumn)) 142 | { 143 | dtResult.SetColumnOrdinalIfExists(uniqueNonKeyColumn, dtResult.Columns.Count - 1); 144 | dtResult.RenameColumnIfExists(uniqueNonKeyColumn, uniqueNonKeyColumn + " " + dt.TableName); 145 | } 146 | } 147 | else 148 | { 149 | dtResult.DeleteColumnIfExists(uniqueNonKeyColumn); 150 | } 151 | } 152 | } 153 | 154 | dtResult.TableName = (dtResult.Namespace + " Comparison Results").Trim(); 155 | dtResult.AcceptChanges(); 156 | dtsTemp.Clear(); 157 | } 158 | return dtResult; 159 | } 160 | 161 | 162 | /// 163 | /// Return the comparison restults in a table 164 | /// 165 | /// 166 | /// 167 | //public DaataTableComparerResult Compare() 168 | //{ 169 | // DaataTableComparerResult result = new DaataTableComparerResult(this); 170 | // List dtsTemp = new List(); 171 | // List dts = new List(); 172 | 173 | // result.CommonColumnNames.AddRange(_DataTableList.GetColumnNames_Shared_All()); 174 | // result.AllColumnNamesIdentical = _DataTableList.GetColumnNames_NotShared_All().Count() == 0; 175 | 176 | // if (_DataTableList.Count < 2) 177 | // throw new Exception("Must have at least 2 tables added to compare"); 178 | 179 | // _DataTableList.CopyTo(dts); 180 | // //Add a CompareResult for each Collection ModelID 181 | 182 | // foreach (DataTable dt in dts) 183 | // dtsTemp.Add(dt); 184 | 185 | // // DataTableCompareResult _result = DataTableHelper.CompareDataTables(dtsTemp, CaseSensitive,includeUniqueColumns); 186 | // // public static DataTableCompareResult CompareDataTables(List dts, bool caseSensitive = false, bool includeUniqueColumns = false) 187 | 188 | // if (dts != null && dts.Count() > 0) 189 | // { 190 | // //////List indexesOracleDataTables = new List(); 191 | // DataTable dtTemp = new DataTable(); 192 | // List dtsCopy = new List(); 193 | 194 | // dts.CopyTo(dtsCopy); 195 | 196 | // foreach (DataTable dt in dtsCopy) 197 | // { 198 | // dt.AddColumnIfNotExists("Exists In", typeof(string)); 199 | // foreach (DataRow r in dt.Rows) 200 | // r["Exists In"] = "Yes"; 201 | 202 | // //////if (dt.TableName.Length >= 3) 203 | // //////{ 204 | // ////// if (dt.TableName.ToString().Substring(0, 3) == "ORA") 205 | // ////// { 206 | // ////// indexesOracleDataTables.Add(dtsCopy.IndexOf(dt)); 207 | // ////// } 208 | // //////} 209 | // } 210 | 211 | // //Create a new DataTable that will be a merge of all the 212 | // DataTable dtResult = new DataTable(); 213 | // dtResult.CaseSensitive = _CaseSensitive; 214 | // DataColumn[] keys = new DataColumn[dtsCopy[0].PrimaryKey.Length]; 215 | // for (int i = 0; i < dtsCopy[0].PrimaryKey.Length; i++) 216 | // { 217 | // try 218 | // { 219 | // Type t = dtsCopy[0].PrimaryKey[i].DataType; 220 | // dtResult.AddColumnIfNotExists(dtsCopy[0].PrimaryKey[i].ColumnName, t);//typeof(string)); 221 | // keys[i] = dtResult.Columns[dtsCopy[0].PrimaryKey[i].ColumnName]; 222 | // result.CommonKeyColumnNames.Add(dtsCopy[0].PrimaryKey[i].ColumnName); 223 | // } 224 | // catch (Exception ex) 225 | // { 226 | // throw ex; 227 | // } 228 | // } 229 | 230 | // dtResult.PrimaryKey = keys; 231 | // List tableNames = new List(); 232 | // List commonNonKeyColumns = dtsCopy.GetColumnNames_Shared_NonPrimaryKey().ToList(); 233 | // List uniqueNonKeyColumns = dtsCopy.GetColumnNames_NotShared_NonPrimaryKey().ToList(); 234 | 235 | // foreach (DataTable dt in dtsCopy) 236 | // { 237 | // tableNames.Add(dt.TableName); 238 | // foreach (DataColumn col in dt.Columns) 239 | // { 240 | // col.AllowDBNull = true; 241 | // if (commonNonKeyColumns.IndexOf(col.ColumnName) >= 0) 242 | // { 243 | // col.ColumnName = col.ColumnName + " " + dt.TableName; 244 | // } 245 | // } 246 | // foreach (DataColumn col in dtResult.Columns) 247 | // { 248 | // col.AllowDBNull = true; 249 | // } 250 | // try 251 | // { 252 | // dtResult.Merge(dt); 253 | // } 254 | // catch (Exception ex) 255 | // { 256 | // dtResult.Merge(dt, true, MissingSchemaAction.AddWithKey); 257 | // throw ex; 258 | // } 259 | // } 260 | 261 | // string expression = GetKeyStatusExpression(dtsCopy); 262 | // dtResult.AddColumnIfNotExists("Exists In Status", typeof(string), expression); 263 | 264 | // foreach (string commonNonKeyColumn in commonNonKeyColumns) 265 | // { 266 | // expression = GetNonKeyStatusExpression(dtsCopy, commonNonKeyColumn + " ", dtResult); 267 | // if (expression != "") 268 | // { 269 | // dtResult.AddColumnIfNotExists(commonNonKeyColumn + " Status", typeof(string), expression, false); 270 | // } 271 | // } 272 | 273 | // commonNonKeyColumns.Remove("Exists In"); 274 | // foreach (string commonNonKeyColumn in commonNonKeyColumns) 275 | // { 276 | // foreach (string tableName in tableNames) 277 | // { 278 | // dtResult.SetColumnOrdinalIfExists(commonNonKeyColumn + " " + tableName, dtResult.Columns.Count - 1); 279 | // } 280 | // } 281 | 282 | // foreach (string uniqueNonKeyColumn in uniqueNonKeyColumns) 283 | // { 284 | // foreach (DataTable dt in dtsCopy) 285 | // { 286 | // if (Config.IncludeUniqueColumnsInOutput) 287 | // { 288 | // if (dt.Columns.Contains(uniqueNonKeyColumn)) 289 | // { 290 | // dtResult.SetColumnOrdinalIfExists(uniqueNonKeyColumn, dtResult.Columns.Count - 1); 291 | // dtResult.RenameColumnIfExists(uniqueNonKeyColumn, uniqueNonKeyColumn + " " + dt.TableName); 292 | // } 293 | // } 294 | // else 295 | // { 296 | // dtResult.DeleteColumnIfExists(uniqueNonKeyColumn); 297 | // } 298 | // } 299 | // } 300 | 301 | // dtResult.TableName = (dtResult.Namespace + " Comparison Results").Trim(); 302 | // dtResult.AcceptChanges(); 303 | 304 | // result.UniqueNonKeyColumnNames = uniqueNonKeyColumns; 305 | // result.CommonNonKeyColumnNames = commonNonKeyColumns; 306 | // result.ResultDataTable = dtResult; 307 | // } 308 | 309 | 310 | // dtsTemp.Clear(); 311 | // return result; 312 | //} 313 | 314 | public void AddTable(DataTable dt) 315 | { 316 | if (dt.TableName == "") 317 | dt.TableName = $"Table_{new Random().Next(1, 1000)}"; 318 | 319 | int i = 1; 320 | string tbName = dt.TableName; 321 | while (DataTables.ContainsTableName(tbName)) 322 | { 323 | tbName = dt.TableName + "_" + i; 324 | i++; 325 | } 326 | dt.TableName = tbName; 327 | 328 | DataTables.Add(dt); 329 | //foreach (DataColumn c in dt.Columns) 330 | //{ 331 | // c.ColumnName = c.ColumnName.ToUpper(); 332 | //} 333 | 334 | //if (DataTableHelper.DataTableExists(_DataTableList,dt.Name)) 335 | //{ 336 | 337 | //throw new Exception("This item or one just like it already exists in the compare."); 338 | //} 339 | 340 | //TODO Build the Collection ModelID List based the table'_Locator namespace 341 | 342 | 343 | //NotifyTableAdded(); 344 | } 345 | public bool RemoveTable(DataTable datatable) 346 | { 347 | return DataTables.Remove(datatable); 348 | } 349 | 350 | //private void NotifyTableAdded() 351 | //{ 352 | // if (DataTableAdded != null) 353 | // { 354 | // DataTableAdded(this, EventArgs.Empty); 355 | // } 356 | //} 357 | 358 | //public void RemoveAt(int i) 359 | //{ 360 | // try 361 | // { 362 | // DataTables.RemoveAt(i); 363 | // if (DataTableRemoved != null) 364 | // { 365 | // DataTableRemoved(this, EventArgs.Empty); 366 | // } 367 | // } 368 | // catch (Exception ex) 369 | // { 370 | // throw ex; 371 | // } 372 | //} 373 | 374 | //public void Clear() 375 | //{ 376 | 377 | // DataTables.Clear(); 378 | // // TODO EVENTBD cleared 379 | // if (DataTableCollectionCleared != null) 380 | // { 381 | // DataTableCollectionCleared(this, EventArgs.Empty); 382 | // } 383 | //} 384 | 385 | 386 | private string GetKeyStatusExpression(List dts) 387 | { 388 | string sExpression = ""; 389 | List tableNames = new List(); 390 | foreach (DataTable dt in dts) 391 | { 392 | tableNames.Add(dt.TableName); 393 | } 394 | 395 | foreach (string tableName in tableNames) 396 | { 397 | if (tableNames.IndexOf(tableName) == 0) 398 | { 399 | sExpression = $"isnull([{Config.ExistsInColumnNamePrefix}{Config.WordSeperator}{tableName}],'0')='0'"; 400 | } 401 | else 402 | { 403 | sExpression = sExpression + $" OR isnull([{Config.ExistsInColumnNamePrefix}{Config.WordSeperator}{tableName}],'0')='0'"; 404 | } 405 | } 406 | sExpression = $"iif({sExpression},'{Config.OutOfSyncPhrase}','{Config.InSyncPhrase}')"; 407 | 408 | return sExpression; 409 | } 410 | 411 | private string GetNonKeyStatusExpression(List dts, string columnName, DataTable dtResults) 412 | { 413 | string sExpression = ""; 414 | List tableNames = new List(); 415 | foreach (DataTable dt in dts) 416 | { 417 | tableNames.Add(columnName + Config.WordSeperator + dt.TableName); 418 | } 419 | 420 | foreach (string tableName in tableNames) 421 | { 422 | Type t = dtResults.Columns[tableName].DataType; 423 | 424 | switch (Type.GetTypeCode(t)) 425 | { 426 | case TypeCode.DBNull: 427 | 428 | break; 429 | 430 | case TypeCode.DateTime: 431 | case TypeCode.Boolean: 432 | case TypeCode.Byte: 433 | case TypeCode.Decimal: 434 | case TypeCode.Double: 435 | case TypeCode.Int16: 436 | case TypeCode.Int32: 437 | case TypeCode.Int64: 438 | case TypeCode.SByte: 439 | case TypeCode.Single: 440 | case TypeCode.UInt16: 441 | case TypeCode.UInt32: 442 | case TypeCode.UInt64: 443 | if (tableNames.IndexOf(tableName) == 0) 444 | { 445 | sExpression = $"isnull([{tableName}],0) = isnull([{tableNames[tableNames.IndexOf(tableName) + 1]}],0)"; 446 | //throwing error on 1/4/2017{"Index was out of range. Must be non-negative and less than the size of the collection.\r\nParameter name: index"} 447 | } 448 | else 449 | { 450 | if (tableNames.IndexOf(tableName) == tableNames.Count - 1) 451 | { 452 | 453 | } 454 | else 455 | { 456 | sExpression = sExpression + $" AND Isnull([{tableName}],0) = isnull([{tableNames[tableNames.IndexOf(tableName) + 1]}],0)"; 457 | } 458 | } 459 | break; 460 | 461 | case TypeCode.Empty: 462 | case TypeCode.Char: 463 | case TypeCode.String: 464 | if (tableNames.IndexOf(tableName) == 0) 465 | { 466 | sExpression = $"isnull([{tableName}],'') = isnull([{tableNames[tableNames.IndexOf(tableName) + 1]}],'')"; 467 | //throwing error on 1/4/2017{"Index was out of range. Must be non-negative and less than the size of the collection.\r\nParameter name: index"} 468 | } 469 | else 470 | { 471 | if (tableNames.IndexOf(tableName) == tableNames.Count - 1) 472 | { 473 | 474 | } 475 | else 476 | { 477 | sExpression = sExpression + $" AND isnull([{tableName}],'') = isnull([{tableNames[tableNames.IndexOf(tableName) + 1]}],'')"; 478 | } 479 | } 480 | break; 481 | default: 482 | return ""; 483 | } 484 | } 485 | sExpression = $"iif([{Config.ExistsInColumnNamePrefix}{Config.WordSeperator}Status]='{Config.InSyncPhrase}',iif({sExpression},'{Config.InSyncPhrase}','{Config.OutOfSyncPhrase}'),'')"; 486 | return sExpression; 487 | } 488 | } 489 | 490 | //public List GetDataTables(string CollectionName = null) 491 | //{ 492 | 493 | // if (CollectionName == null) 494 | // { 495 | // return _DataTableList; 496 | // } 497 | // else 498 | // { 499 | // List dataTableListFiltered = new List(); 500 | // dataTableListFiltered.Clear(); 501 | 502 | // foreach (DataTable dt in _DataTableList) 503 | // { 504 | // dataTableListFiltered.Add(dt.Copy()); 505 | // } 506 | 507 | // return dataTableListFiltered; 508 | // } 509 | //} 510 | 511 | } 512 | --------------------------------------------------------------------------------