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");
40 if (sourceSet == targetSet)
43 bool prevEC = targetSet.EnforceConstraints;
44 targetSet.EnforceConstraints = false;
46 foreach (DataTable t in sourceSet.Tables)
47 MergeManager.Merge(targetSet, t, preserveChanges, missingSchemaAction);
49 AdjustSchemaRelations (targetSet, sourceSet, missingSchemaAction);
50 targetSet.EnforceConstraints = prevEC;
53 internal static void Merge(DataSet targetSet, DataTable sourceTable, bool preserveChanges, MissingSchemaAction missingSchemaAction)
56 throw new ArgumentNullException("targetSet");
57 if(sourceTable == null)
58 throw new ArgumentNullException("sourceTable");
59 if (sourceTable.DataSet == targetSet)
62 bool savedEnfoceConstraints = targetSet.EnforceConstraints;
63 targetSet.EnforceConstraints = false;
65 DataTable targetTable = null;
66 if (!AdjustSchema(targetSet, sourceTable, missingSchemaAction,ref targetTable))
68 if (targetTable != null)
69 fillData(targetTable, sourceTable, preserveChanges);
70 targetSet.EnforceConstraints = savedEnfoceConstraints;
73 internal static void Merge (DataTable targetTable,
74 DataTable sourceTable,
76 MissingSchemaAction missingSchemaAction)
78 if(targetTable== null)
79 throw new ArgumentNullException("targetTable");
80 if(sourceTable == null)
81 throw new ArgumentNullException("sourceTable");
82 if (sourceTable == targetTable)
85 bool savedEnforceConstraints = targetTable.EnforceConstraints;
86 targetTable.EnforceConstraints = false;
88 if (!AdjustSchema(targetTable, sourceTable, missingSchemaAction))
91 fillData(targetTable, sourceTable, preserveChanges);
92 targetTable.EnforceConstraints = savedEnforceConstraints;
95 internal static void Merge(DataSet targetSet, DataRow[] sourceRows, bool preserveChanges, MissingSchemaAction missingSchemaAction)
98 throw new ArgumentNullException("targetSet");
99 if(sourceRows == null)
100 throw new ArgumentNullException("sourceRows");
102 bool savedEnfoceConstraints = targetSet.EnforceConstraints;
103 targetSet.EnforceConstraints = false;
105 ArrayList targetTables = new ArrayList();
106 for (int i = 0; i < sourceRows.Length; i++) {
107 DataRow row = sourceRows[i];
108 DataTable sourceTable = row.Table;
109 DataTable targetTable = null;
110 if (!AdjustSchema(targetSet, sourceTable, missingSchemaAction,ref targetTable))
112 if (targetTable != null) {
113 MergeRow(targetTable, row, preserveChanges);
114 if (!(targetTables.IndexOf(targetTable) >= 0))
115 targetTables.Add(targetTable);
119 targetSet.EnforceConstraints = savedEnfoceConstraints;
122 // merge a row into a target table.
123 private static void MergeRow(DataTable targetTable, DataRow row, bool preserveChanges)
125 DataColumn[] primaryKeys = targetTable.PrimaryKey;
126 DataRow targetRow = null;
127 DataRowVersion version = DataRowVersion.Default;
128 if (row.RowState == DataRowState.Deleted)
129 version = DataRowVersion.Original;
131 if (primaryKeys != null && primaryKeys.Length > 0) // if there are any primary key.
133 // initiate an array that has the values of the primary keys.
134 object[] keyValues = new object[primaryKeys.Length];
136 for (int j = 0; j < keyValues.Length; j++)
138 keyValues[j] = row[primaryKeys[j].ColumnName, version];
141 // find the row in the target table.
142 targetRow = targetTable.Rows.Find(keyValues, DataViewRowState.OriginalRows);
143 if (targetRow == null)
144 targetRow = targetTable.Rows.Find(keyValues);
146 // row doesn't exist in target table, or there are no primary keys.
147 // create new row and copy values from source row to the new row.
148 if (targetRow == null)
150 DataRow newRow = targetTable.NewNotInitializedRow();
151 // Don't check for ReadOnly, when cloning data to new uninitialized row.
152 row.CopyValuesToRow(newRow, false);
153 targetTable.Rows.AddInternal (newRow);
155 // row exists in target table, and presere changes is false -
156 // change the values of the target row to the values of the source row.
159 row.MergeValuesToRow(targetRow, preserveChanges);
163 private static bool AdjustSchemaRelations (DataSet targetSet, DataSet sourceSet, MissingSchemaAction missingSchemaAction)
165 if (missingSchemaAction == MissingSchemaAction.Ignore)
168 foreach(DataTable sourceTable in sourceSet.Tables) {
170 DataTable targetTable = targetSet.Tables[sourceTable.TableName];
171 if (targetTable == null)
174 foreach (Constraint constraint in sourceTable.Constraints) {
176 Constraint targetConstraint = null;
178 string constraintName = constraint.ConstraintName;
179 if (targetTable.Constraints.Contains (constraintName))
182 UniqueConstraint uc = constraint as UniqueConstraint;
183 // PrimaryKey is already taken care of while merging the table
184 // ForeignKey constraint takes care of Parent Unique Constraints
186 if (uc.IsPrimaryKey || uc.ChildConstraint != null)
188 DataColumn[] columns = ResolveColumns (targetTable, uc.Columns);
189 targetConstraint = new UniqueConstraint (constraintName, columns, false);
192 ForeignKeyConstraint fc = constraint as ForeignKeyConstraint;
194 DataColumn[] columns = ResolveColumns (targetTable, fc.Columns);
195 DataColumn[] relatedColumns = ResolveColumns (targetSet.Tables [fc.RelatedTable.TableName],
197 targetConstraint = new ForeignKeyConstraint (constraintName, relatedColumns, columns);
200 bool dupConstraintFound = false;
201 foreach (Constraint cons in targetTable.Constraints) {
202 if (!targetConstraint.Equals (cons))
204 dupConstraintFound = true;
208 // If equivalent-constraint already exists, then just do nothing
209 if (dupConstraintFound)
212 if (missingSchemaAction == MissingSchemaAction.Error)
213 throw new DataException ("Target DataSet missing " + targetConstraint.GetType () +
214 targetConstraint.ConstraintName);
216 targetTable.Constraints.Add (targetConstraint);
220 foreach (DataRelation relation in sourceSet.Relations) {
221 DataRelation targetRelation = targetSet.Relations [relation.RelationName];
222 if (targetRelation == null) {
223 if (missingSchemaAction == MissingSchemaAction.Error)
224 throw new ArgumentException ("Target DataSet mising definition for " +
225 relation.RelationName);
227 DataColumn[] parentColumns = ResolveColumns (targetSet.Tables [relation.ParentTable.TableName],
228 relation.ParentColumns);
229 DataColumn[] childColumns = ResolveColumns (targetSet.Tables [relation.ChildTable.TableName],
230 relation.ChildColumns);
231 targetRelation = targetSet.Relations.Add (relation.RelationName, parentColumns,
232 childColumns, relation.createConstraints);
233 targetRelation.Nested = relation.Nested;
234 } else if (!CompareColumnArrays (relation.ParentColumns, targetRelation.ParentColumns) ||
235 !CompareColumnArrays (relation.ChildColumns, targetRelation.ChildColumns)) {
236 RaiseMergeFailedEvent (null, "Relation " + relation.RelationName +
237 " cannot be merged, because keys have mismatch columns.");
244 private static DataColumn[] ResolveColumns(DataTable targetTable, DataColumn[] sourceColumns)
246 if (sourceColumns != null && sourceColumns.Length > 0) {
247 // lets just assume that all columns are from the Same table
248 if (targetTable != null) {
250 DataColumn[] targetColumns = new DataColumn[sourceColumns.Length];
253 foreach(DataColumn sourceColumn in sourceColumns) {
254 tmpCol = targetTable.Columns[sourceColumn.ColumnName];
256 throw new DataException ("Column " + sourceColumn.ColumnName +
257 " does not belong to table " + targetTable.TableName);
258 targetColumns [i++] = tmpCol;
260 return targetColumns;
267 // adjust the table schema according to the missingschemaaction param.
268 // return false if adjusting fails.
269 private static bool AdjustSchema(DataSet targetSet, DataTable sourceTable, MissingSchemaAction missingSchemaAction, ref DataTable newTable)
271 // if the source table not exists in the target dataset
272 // we act according to the missingschemaaction param.
273 DataTable targetTable = targetSet.Tables [sourceTable.TableName];
274 if (targetTable == null) {
275 if (missingSchemaAction == MissingSchemaAction.Ignore)
278 if (missingSchemaAction == MissingSchemaAction.Error)
279 throw new ArgumentException ("Target DataSet missing definition for " +
280 sourceTable.TableName + ".");
282 targetTable = (DataTable)sourceTable.Clone();
283 targetSet.Tables.Add(targetTable);
286 AdjustSchema (targetTable, sourceTable, missingSchemaAction);
288 newTable = targetTable;
293 private static bool AdjustSchema(DataTable targetTable, DataTable sourceTable, MissingSchemaAction missingSchemaAction)
295 if (missingSchemaAction == MissingSchemaAction.Ignore)
298 for (int i = 0; i < sourceTable.Columns.Count; i++) {
299 DataColumn sourceColumn = sourceTable.Columns[i];
300 // if a column from the source table doesn't exists in the target table
301 // we act according to the missingschemaaction param.
302 DataColumn targetColumn = targetTable.Columns [sourceColumn.ColumnName];
303 if(targetColumn == null) {
304 if (missingSchemaAction == MissingSchemaAction.Error)
305 throw new DataException ("Target table " + targetTable.TableName +
306 " missing definition for column " + sourceColumn.ColumnName);
308 targetColumn = new DataColumn(sourceColumn.ColumnName, sourceColumn.DataType,
309 sourceColumn.Expression, sourceColumn.ColumnMapping);
310 targetTable.Columns.Add(targetColumn);
313 if(sourceColumn.AutoIncrement) {
314 targetColumn.AutoIncrement = sourceColumn.AutoIncrement;
315 targetColumn.AutoIncrementSeed = sourceColumn.AutoIncrementSeed;
316 targetColumn.AutoIncrementStep = sourceColumn.AutoIncrementStep;
320 if (!AdjustPrimaryKeys(targetTable, sourceTable))
323 checkColumnTypes (targetTable, sourceTable);
329 // find if there is a valid matching between the targetTable PrimaryKey and the
330 // sourceTable primatyKey.
331 // return true if there is a match, else return false and raise a MergeFailedEvent.
332 private static bool AdjustPrimaryKeys(DataTable targetTable, DataTable sourceTable)
334 if (sourceTable.PrimaryKey.Length == 0)
337 // If targetTable does not have a PrimaryKey, just import the sourceTable PrimaryKey
338 if (targetTable.PrimaryKey.Length == 0) {
339 DataColumn[] targetColumns = ResolveColumns (targetTable, sourceTable.PrimaryKey);
340 targetTable.PrimaryKey = targetColumns;
344 // If both the tables have a primary key, verify that they are equivalent.
345 // raise a MergeFailedEvent if the keys are not equivalent
346 if (targetTable.PrimaryKey.Length != sourceTable.PrimaryKey.Length) {
347 RaiseMergeFailedEvent (targetTable, "<target>.PrimaryKey and <source>.PrimaryKey have different Length.");
351 for (int i=0; i < targetTable.PrimaryKey.Length; ++i) {
352 if (targetTable.PrimaryKey [i].ColumnName.Equals (sourceTable.PrimaryKey [i].ColumnName))
354 RaiseMergeFailedEvent (targetTable, "Mismatch columns in the PrimaryKey : <target>." +
355 targetTable.PrimaryKey [i].ColumnName + " versus <source>." + sourceTable.PrimaryKey [i].ColumnName);
361 // fill the data from the source table to the target table
362 private static void fillData(DataTable targetTable, DataTable sourceTable, bool preserveChanges)
364 for (int i = 0; i < sourceTable.Rows.Count; i++)
366 DataRow row = sourceTable.Rows[i];
367 MergeRow(targetTable, row, preserveChanges);
371 // check tha column from 2 tables that has the same name also has the same datatype.
372 private static void checkColumnTypes(DataTable targetTable, DataTable sourceTable)
374 for (int i = 0; i < sourceTable.Columns.Count; i++)
376 DataColumn fromCol = sourceTable.Columns[i];
377 DataColumn toCol = targetTable.Columns[fromCol.ColumnName];
380 if (toCol.DataTypeMatches (fromCol))
382 throw new DataException("<target>." + fromCol.ColumnName + " and <source>." +
383 fromCol.ColumnName + " have conflicting properties: DataType " +
384 " property mismatch.");
388 private static bool CompareColumnArrays (DataColumn[] arr1, DataColumn[] arr2)
390 if (arr1.Length != arr2.Length)
393 for (int i=0; i < arr1.Length; ++i)
394 if (!arr1 [i].ColumnName.Equals (arr2 [i].ColumnName))
399 private static void RaiseMergeFailedEvent (DataTable targetTable, string errMsg)
401 MergeFailedEventArgs args = new MergeFailedEventArgs (targetTable, errMsg);
402 if (targetTable.DataSet != null)
403 targetTable.DataSet.OnMergeFailed (args);