3 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
5 // Permission is hereby granted, free of charge, to any person obtaining
6 // a copy of this software and associated documentation files (the
7 // "Software"), to deal in the Software without restriction, including
8 // without limitation the rights to use, copy, modify, merge, publish,
9 // distribute, sublicense, and/or sell copies of the Software, and to
10 // permit persons to whom the Software is furnished to do so, subject to
11 // the following conditions:
13 // The above copyright notice and this permission notice shall be
14 // included in all copies or substantial portions of the Software.
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 using System.Collections;
30 /// Summary description for MergeManager.
32 internal class MergeManager
34 internal static void Merge(DataSet targetSet, DataSet sourceSet, bool preserveChanges, MissingSchemaAction missingSchemaAction)
37 throw new ArgumentNullException("targetSet");
39 throw new ArgumentNullException("sourceSet");
41 bool prevEC = targetSet.EnforceConstraints;
42 targetSet.EnforceConstraints = false;
44 foreach (DataTable t in sourceSet.Tables)
45 MergeManager.Merge(targetSet, t, preserveChanges, missingSchemaAction);
47 AdjustSchemaRelations (targetSet, sourceSet, missingSchemaAction);
48 targetSet.EnforceConstraints = prevEC;
51 internal static void Merge(DataSet targetSet, DataTable sourceTable, bool preserveChanges, MissingSchemaAction missingSchemaAction)
54 throw new ArgumentNullException("targetSet");
55 if(sourceTable == null)
56 throw new ArgumentNullException("sourceTable");
58 bool savedEnfoceConstraints = targetSet.EnforceConstraints;
59 targetSet.EnforceConstraints = false;
61 DataTable targetTable = null;
62 if (!AdjustSchema(targetSet, sourceTable, missingSchemaAction,ref targetTable))
64 if (targetTable != null)
65 fillData(targetTable, sourceTable, preserveChanges);
66 targetSet.EnforceConstraints = savedEnfoceConstraints;
69 internal static void Merge (DataTable targetTable,
70 DataTable sourceTable,
72 MissingSchemaAction missingSchemaAction)
74 if(targetTable== null)
75 throw new ArgumentNullException("targetTable");
76 if(sourceTable == null)
77 throw new ArgumentNullException("sourceTable");
79 bool savedEnforceConstraints = targetTable.EnforceConstraints;
80 targetTable.EnforceConstraints = false;
82 if (!AdjustSchema(targetTable, sourceTable, missingSchemaAction))
85 fillData(targetTable, sourceTable, preserveChanges);
86 targetTable.EnforceConstraints = savedEnforceConstraints;
89 internal static void Merge(DataSet targetSet, DataRow[] sourceRows, bool preserveChanges, MissingSchemaAction missingSchemaAction)
92 throw new ArgumentNullException("targetSet");
93 if(sourceRows == null)
94 throw new ArgumentNullException("sourceRows");
96 bool savedEnfoceConstraints = targetSet.EnforceConstraints;
97 targetSet.EnforceConstraints = false;
99 ArrayList targetTables = new ArrayList();
100 for (int i = 0; i < sourceRows.Length; i++) {
101 DataRow row = sourceRows[i];
102 DataTable sourceTable = row.Table;
103 DataTable targetTable = null;
104 if (!AdjustSchema(targetSet, sourceTable, missingSchemaAction,ref targetTable))
106 if (targetTable != null) {
107 MergeRow(targetTable, row, preserveChanges);
108 if (!(targetTables.IndexOf(targetTable) >= 0))
109 targetTables.Add(targetTable);
113 targetSet.EnforceConstraints = savedEnfoceConstraints;
116 // merge a row into a target table.
117 private static void MergeRow(DataTable targetTable, DataRow row, bool preserveChanges)
119 DataColumnCollection columns = row.Table.Columns;
120 DataColumn[] primaryKeys = targetTable.PrimaryKey;
121 DataRow targetRow = null;
122 DataRowVersion version = DataRowVersion.Default;
123 if (row.RowState == DataRowState.Deleted)
124 version = DataRowVersion.Original;
126 if (primaryKeys != null && primaryKeys.Length > 0) // if there are any primary key.
128 // initiate an array that has the values of the primary keys.
129 object[] keyValues = new object[primaryKeys.Length];
131 for (int j = 0; j < keyValues.Length; j++)
133 keyValues[j] = row[primaryKeys[j].ColumnName, version];
136 // find the row in the target table.
137 targetRow = targetTable.Rows.Find(keyValues, DataViewRowState.OriginalRows);
138 if (targetRow == null)
139 targetRow = targetTable.Rows.Find(keyValues);
141 // row doesn't exist in target table, or there are no primary keys.
142 // create new row and copy values from source row to the new row.
143 if (targetRow == null)
145 DataRow newRow = targetTable.NewNotInitializedRow();
146 row.CopyValuesToRow(newRow);
147 targetTable.Rows.AddInternal (newRow);
149 // row exists in target table, and presere changes is false -
150 // change the values of the target row to the values of the source row.
153 row.MergeValuesToRow(targetRow, preserveChanges);
157 private static bool AdjustSchemaRelations (DataSet targetSet, DataSet sourceSet, MissingSchemaAction missingSchemaAction)
159 if (missingSchemaAction == MissingSchemaAction.Ignore)
162 foreach(DataTable sourceTable in sourceSet.Tables) {
164 DataTable targetTable = targetSet.Tables[sourceTable.TableName];
165 if (targetTable == null)
168 foreach (Constraint constraint in sourceTable.Constraints) {
170 Constraint targetConstraint = null;
172 string constraintName = constraint.ConstraintName;
173 if (targetTable.Constraints.Contains (constraintName))
176 UniqueConstraint uc = constraint as UniqueConstraint;
177 // PrimaryKey is already taken care of while merging the table
178 // ForeignKey constraint takes care of Parent Unique Constraints
180 if (uc.IsPrimaryKey || uc.ChildConstraint != null)
182 DataColumn[] columns = ResolveColumns (targetTable, uc.Columns);
183 targetConstraint = new UniqueConstraint (constraintName, columns, false);
186 ForeignKeyConstraint fc = constraint as ForeignKeyConstraint;
188 DataColumn[] columns = ResolveColumns (targetTable, fc.Columns);
189 DataColumn[] relatedColumns = ResolveColumns (targetSet.Tables [fc.RelatedTable.TableName],
191 targetConstraint = new ForeignKeyConstraint (constraintName, relatedColumns, columns);
194 bool dupConstraintFound = false;
195 foreach (Constraint cons in targetTable.Constraints) {
196 if (!targetConstraint.Equals (cons))
198 dupConstraintFound = true;
202 // If equivalent-constraint already exists, then just do nothing
203 if (dupConstraintFound)
206 if (missingSchemaAction == MissingSchemaAction.Error)
207 throw new DataException ("Target DataSet missing " + targetConstraint.GetType () +
208 targetConstraint.ConstraintName);
210 targetTable.Constraints.Add (targetConstraint);
214 foreach (DataRelation relation in sourceSet.Relations) {
215 DataRelation targetRelation = targetSet.Relations [relation.RelationName];
216 if (targetRelation == null) {
217 if (missingSchemaAction == MissingSchemaAction.Error)
218 throw new ArgumentException ("Target DataSet mising definition for " +
219 relation.RelationName);
221 DataColumn[] parentColumns = ResolveColumns (targetSet.Tables [relation.ParentTable.TableName],
222 relation.ParentColumns);
223 DataColumn[] childColumns = ResolveColumns (targetSet.Tables [relation.ChildTable.TableName],
224 relation.ChildColumns);
225 targetRelation = targetSet.Relations.Add (relation.RelationName, parentColumns,
226 childColumns, false);
227 targetRelation.Nested = relation.Nested;
228 } else if (!CompareColumnArrays (relation.ParentColumns, targetRelation.ParentColumns) ||
229 !CompareColumnArrays (relation.ChildColumns, targetRelation.ChildColumns)) {
230 RaiseMergeFailedEvent (null, "Relation " + relation.RelationName +
231 " cannot be merged, because keys have mismatch columns.");
238 private static DataColumn[] ResolveColumns(DataTable targetTable, DataColumn[] sourceColumns)
240 if (sourceColumns != null && sourceColumns.Length > 0) {
241 // lets just assume that all columns are from the Same table
242 if (targetTable != null) {
244 DataColumn[] targetColumns = new DataColumn[sourceColumns.Length];
247 foreach(DataColumn sourceColumn in sourceColumns) {
248 tmpCol = targetTable.Columns[sourceColumn.ColumnName];
250 throw new DataException ("Column " + sourceColumn.ColumnName +
251 " does not belong to table " + targetTable.TableName);
252 targetColumns [i++] = tmpCol;
254 return targetColumns;
261 // adjust the table schema according to the missingschemaaction param.
262 // return false if adjusting fails.
263 private static bool AdjustSchema(DataSet targetSet, DataTable sourceTable, MissingSchemaAction missingSchemaAction, ref DataTable newTable)
265 // if the source table not exists in the target dataset
266 // we act according to the missingschemaaction param.
267 DataTable targetTable = targetSet.Tables [sourceTable.TableName];
268 if (targetTable == null) {
269 if (missingSchemaAction == MissingSchemaAction.Ignore)
272 if (missingSchemaAction == MissingSchemaAction.Error)
273 throw new ArgumentException ("Target DataSet missing definition for " +
274 sourceTable.TableName + ".");
276 targetTable = (DataTable)sourceTable.Clone();
277 targetSet.Tables.Add(targetTable);
280 AdjustSchema (targetTable, sourceTable, missingSchemaAction);
282 newTable = targetTable;
287 private static bool AdjustSchema(DataTable targetTable, DataTable sourceTable, MissingSchemaAction missingSchemaAction)
289 if (missingSchemaAction == MissingSchemaAction.Ignore)
292 for (int i = 0; i < sourceTable.Columns.Count; i++) {
293 DataColumn sourceColumn = sourceTable.Columns[i];
294 // if a column from the source table doesn't exists in the target table
295 // we act according to the missingschemaaction param.
296 DataColumn targetColumn = targetTable.Columns [sourceColumn.ColumnName];
297 if(targetColumn == null) {
298 if (missingSchemaAction == MissingSchemaAction.Error)
299 throw new DataException ("Target table " + targetTable.TableName +
300 " missing definition for column " + sourceColumn.ColumnName);
302 targetColumn = new DataColumn(sourceColumn.ColumnName, sourceColumn.DataType,
303 sourceColumn.Expression, sourceColumn.ColumnMapping);
304 targetTable.Columns.Add(targetColumn);
307 if(sourceColumn.AutoIncrement) {
308 targetColumn.AutoIncrement = sourceColumn.AutoIncrement;
309 targetColumn.AutoIncrementSeed = sourceColumn.AutoIncrementSeed;
310 targetColumn.AutoIncrementStep = sourceColumn.AutoIncrementStep;
314 if (!AdjustPrimaryKeys(targetTable, sourceTable))
317 checkColumnTypes (targetTable, sourceTable);
323 // find if there is a valid matching between the targetTable PrimaryKey and the
324 // sourceTable primatyKey.
325 // return true if there is a match, else return false and raise a MergeFailedEvent.
326 private static bool AdjustPrimaryKeys(DataTable targetTable, DataTable sourceTable)
328 if (sourceTable.PrimaryKey.Length == 0)
331 // If targetTable does not have a PrimaryKey, just import the sourceTable PrimaryKey
332 if (targetTable.PrimaryKey.Length == 0) {
333 DataColumn[] targetColumns = ResolveColumns (targetTable, sourceTable.PrimaryKey);
334 targetTable.PrimaryKey = targetColumns;
338 // If both the tables have a primary key, verify that they are equivalent.
339 // raise a MergeFailedEvent if the keys are not equivalent
340 if (targetTable.PrimaryKey.Length != sourceTable.PrimaryKey.Length) {
341 RaiseMergeFailedEvent (targetTable, "<target>.PrimaryKey and <source>.PrimaryKey have different Length.");
345 for (int i=0; i < targetTable.PrimaryKey.Length; ++i) {
346 if (targetTable.PrimaryKey [i].ColumnName.Equals (sourceTable.PrimaryKey [i].ColumnName))
348 RaiseMergeFailedEvent (targetTable, "Mismatch columns in the PrimaryKey : <target>." +
349 targetTable.PrimaryKey [i].ColumnName + " versus <source>." + sourceTable.PrimaryKey [i].ColumnName);
355 // fill the data from the source table to the target table
356 private static void fillData(DataTable targetTable, DataTable sourceTable, bool preserveChanges)
358 for (int i = 0; i < sourceTable.Rows.Count; i++)
360 DataRow row = sourceTable.Rows[i];
361 MergeRow(targetTable, row, preserveChanges);
365 // check tha column from 2 tables that has the same name also has the same datatype.
366 private static void checkColumnTypes(DataTable targetTable, DataTable sourceTable)
368 for (int i = 0; i < sourceTable.Columns.Count; i++)
370 DataColumn fromCol = sourceTable.Columns[i];
371 DataColumn toCol = targetTable.Columns[fromCol.ColumnName];
374 if (toCol.DataTypeMatches (fromCol))
376 throw new DataException("<target>." + fromCol.ColumnName + " and <source>." +
377 fromCol.ColumnName + " have conflicting properties: DataType " +
378 " property mismatch.");
382 private static bool CompareColumnArrays (DataColumn[] arr1, DataColumn[] arr2)
384 if (arr1.Length != arr2.Length)
387 for (int i=0; i < arr1.Length; ++i)
388 if (!arr1 [i].ColumnName.Equals (arr2 [i].ColumnName))
393 private static void RaiseMergeFailedEvent (DataTable targetTable, string errMsg)
395 MergeFailedEventArgs args = new MergeFailedEventArgs (targetTable, errMsg);
396 if (targetTable.DataSet != null)
397 targetTable.DataSet.OnMergeFailed (args);