├── 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 |
--------------------------------------------------------------------------------