// // Copyright (C) 2004 Novell, Inc (http://www.novell.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections; namespace System.Data { /// /// Summary description for MergeManager. /// internal class MergeManager { internal static void Merge(DataSet targetSet, DataSet sourceSet, bool preserveChanges, MissingSchemaAction missingSchemaAction) { if(targetSet == null) throw new ArgumentNullException("targetSet"); if(sourceSet == null) throw new ArgumentNullException("sourceSet"); bool prevEC = targetSet.EnforceConstraints; targetSet.EnforceConstraints = false; foreach (DataTable t in sourceSet.Tables) MergeManager.Merge(targetSet, t, preserveChanges, missingSchemaAction); AdjustSchema(targetSet,sourceSet,missingSchemaAction); targetSet.EnforceConstraints = prevEC; } internal static void Merge(DataSet targetSet, DataTable sourceTable, bool preserveChanges, MissingSchemaAction missingSchemaAction) { if(targetSet == null) throw new ArgumentNullException("targetSet"); if(sourceTable == null) throw new ArgumentNullException("sourceTable"); bool savedEnfoceConstraints = targetSet.EnforceConstraints; targetSet.EnforceConstraints = false; DataTable targetTable = null; if (!AdjustSchema(targetSet, sourceTable, missingSchemaAction,ref targetTable)) { return; } if (targetTable != null) { checkColumnTypes(targetTable, sourceTable); // check that the colums datatype is the same fillData(targetTable, sourceTable, preserveChanges); } targetSet.EnforceConstraints = savedEnfoceConstraints; if (!targetSet.EnforceConstraints && targetTable != null) { // indexes are still outdated targetTable.ResetIndexes(); } } internal static void Merge(DataSet targetSet, DataRow[] sourceRows, bool preserveChanges, MissingSchemaAction missingSchemaAction) { if(targetSet == null) throw new ArgumentNullException("targetSet"); if(sourceRows == null) throw new ArgumentNullException("sourceRows"); bool savedEnfoceConstraints = targetSet.EnforceConstraints; targetSet.EnforceConstraints = false; ArrayList targetTables = new ArrayList(); for (int i = 0; i < sourceRows.Length; i++) { DataRow row = sourceRows[i]; DataTable sourceTable = row.Table; DataTable targetTable = null; if (!AdjustSchema(targetSet, sourceTable, missingSchemaAction,ref targetTable)) { return; } if (targetTable != null) { checkColumnTypes(targetTable, row.Table); MergeRow(targetTable, row, preserveChanges); if (!(targetTables.IndexOf(targetTable) >= 0)) { targetTables.Add(targetTable); } } } targetSet.EnforceConstraints = savedEnfoceConstraints; foreach(DataTable table in targetTables) { table.ResetIndexes(); } } // merge a row into a target table. private static void MergeRow(DataTable targetTable, DataRow row, bool preserveChanges) { DataColumnCollection columns = row.Table.Columns; DataColumn[] primaryKeys = targetTable.PrimaryKey; DataRow targetRow = null; DataRowVersion version = DataRowVersion.Default; if (row.RowState == DataRowState.Deleted) version = DataRowVersion.Original; if (primaryKeys != null && primaryKeys.Length > 0) // if there are any primary key. { // initiate an array that has the values of the primary keys. object[] keyValues = new object[primaryKeys.Length]; for (int j = 0; j < keyValues.Length; j++) { keyValues[j] = row[primaryKeys[j].ColumnName, version]; } // find the row in the target table. targetRow = targetTable.Rows.Find(keyValues); } // row doesn't exist in target table, or there are no primary keys. // create new row and copy values from source row to the new row. if (targetRow == null) { targetRow = targetTable.NewRow(); targetRow.Proposed = -1; row.CopyValuesToRow(targetRow); targetTable.Rows.Add(targetRow); } // row exists in target table, and presere changes is false - // change the values of the target row to the values of the source row. else if (!preserveChanges) { row.CopyValuesToRow(targetRow); } } // adjust the dataset schema according to the missingschemaaction param // (relations). // return false if adjusting fails. private static bool AdjustSchema(DataSet targetSet, DataSet sourceSet, MissingSchemaAction missingSchemaAction) { if (missingSchemaAction == MissingSchemaAction.Add || missingSchemaAction == MissingSchemaAction.AddWithKey) { foreach (DataRelation relation in sourceSet.Relations) { // TODO : add more precise condition (columns) if (!targetSet.Relations.Contains(relation.RelationName)) { DataTable targetTable = targetSet.Tables[relation.ParentColumns[0].Table.TableName]; DataColumn[] parentColumns = ResolveColumns(sourceSet,targetTable,relation.ParentColumns); targetTable = targetSet.Tables[relation.ChildColumns[0].Table.TableName]; DataColumn[] childColumns = ResolveColumns(sourceSet,targetTable,relation.ChildColumns); if (parentColumns != null && childColumns != null) { DataRelation newRelation = new DataRelation(relation.RelationName,parentColumns,childColumns); newRelation.Nested = relation.Nested; targetSet.Relations.Add(newRelation); } } else { // TODO : should we throw an exeption ? } } foreach(DataTable sourceTable in sourceSet.Tables) { DataTable targetTable = targetSet.Tables[sourceTable.TableName]; if (targetTable != null) { foreach(Constraint constraint in sourceTable.Constraints) { if (constraint is UniqueConstraint) { UniqueConstraint uc = (UniqueConstraint)constraint; // FIXME : add more precise condition (columns) if ( !targetTable.Constraints.Contains(uc.ConstraintName) ) { DataColumn[] columns = ResolveColumns(sourceSet,targetTable,uc.Columns); if (columns != null) { UniqueConstraint newConstraint = new UniqueConstraint(uc.ConstraintName,columns,uc.IsPrimaryKey); targetTable.Constraints.Add(newConstraint); } } else { // FIXME : should we throw an exception ? } } else { ForeignKeyConstraint fc = (ForeignKeyConstraint)constraint; // FIXME : add more precise condition (columns) if (!targetTable.Constraints.Contains(fc.ConstraintName)) { DataColumn[] columns = ResolveColumns(sourceSet,targetTable,fc.Columns); DataTable relatedTable = targetSet.Tables[fc.RelatedTable.TableName]; DataColumn[] relatedColumns = ResolveColumns(sourceSet,relatedTable,fc.RelatedColumns); if (columns != null && relatedColumns != null) { ForeignKeyConstraint newConstraint = new ForeignKeyConstraint(fc.ConstraintName,relatedColumns,columns); targetTable.Constraints.Add(newConstraint); } } else { // FIXME : should we throw an exception ? } } } } } } return true; } private static DataColumn[] ResolveColumns(DataSet targetSet,DataTable targetTable,DataColumn[] sourceColumns) { if (sourceColumns != null && sourceColumns.Length > 0) { // TODO : worth to check that all source colums come from the same table if (targetTable != null) { int i=0; DataColumn[] targetColumns = new DataColumn[sourceColumns.Length]; foreach(DataColumn sourceColumn in sourceColumns) { targetColumns[i++] = targetTable.Columns[sourceColumn.ColumnName]; } return targetColumns; } } return null; } // adjust the table schema according to the missingschemaaction param. // return false if adjusting fails. private static bool AdjustSchema(DataSet targetSet, DataTable sourceTable, MissingSchemaAction missingSchemaAction, ref DataTable newTable) { string tableName = sourceTable.TableName; // if the source table not exists in the target dataset // we act according to the missingschemaaction param. int tmp = targetSet.Tables.IndexOf(tableName); // we need to check if it is equals names if (tmp != -1 && !targetSet.Tables[tmp].TableName.Equals(tableName)) tmp = -1; if (tmp == -1) { if (missingSchemaAction == MissingSchemaAction.Ignore) { return true; } if (missingSchemaAction == MissingSchemaAction.Error) { throw new ArgumentException("Target DataSet missing definition for "+ tableName + "."); } DataTable cloneTable = (DataTable)sourceTable.Clone(); targetSet.Tables.Add(cloneTable); tableName = cloneTable.TableName; } DataTable table = targetSet.Tables[tableName]; for (int i = 0; i < sourceTable.Columns.Count; i++) { DataColumn sourceColumn = sourceTable.Columns[i]; // if a column from the source table doesn't exists in the target table // we act according to the missingschemaaction param. DataColumn targetColumn = table.Columns[sourceColumn.ColumnName]; if(targetColumn == null) { if (missingSchemaAction == MissingSchemaAction.Ignore) { continue; } if (missingSchemaAction == MissingSchemaAction.Error) { throw new ArgumentException(("Column '" + sourceColumn.ColumnName + "' does not belong to table Items.")); } targetColumn = new DataColumn(sourceColumn.ColumnName, sourceColumn.DataType, sourceColumn.Expression, sourceColumn.ColumnMapping); table.Columns.Add(targetColumn); } if (sourceColumn.Unique) { try { targetColumn.Unique = sourceColumn.Unique; } catch(Exception e){ // Console.WriteLine("targetColumn : {0} targetTable : {1} ",targetColumn.ColumnName,table.TableName); foreach(DataRow row in table.Rows) { // Console.WriteLine(row[targetColumn]); } throw e; } } if(sourceColumn.AutoIncrement) { targetColumn.AutoIncrement = sourceColumn.AutoIncrement; targetColumn.AutoIncrementSeed = sourceColumn.AutoIncrementSeed; targetColumn.AutoIncrementStep = sourceColumn.AutoIncrementStep; } } if (!AdjustPrimaryKeys(table, sourceTable)) { return false; } newTable = table; return true; } // find if there is a valid matching between the targetTable PrimaryKey and the // sourceTable primatyKey. // return true if there is a match, else return false and raise a MergeFailedEvent. private static bool AdjustPrimaryKeys(DataTable targetTable, DataTable sourceTable) { // if the length of one of the tables primarykey if 0 - there is nothing to check. if (sourceTable.PrimaryKey.Length != 0) { if (targetTable.PrimaryKey.Length == 0) { // if target table has no primary key at all - // import primary key from source table DataColumn[] targetColumns = new DataColumn[sourceTable.PrimaryKey.Length]; for(int i=0; i < sourceTable.PrimaryKey.Length; i++){ DataColumn sourceColumn = sourceTable.PrimaryKey[i]; DataColumn targetColumn = targetTable.Columns[sourceColumn.ColumnName]; if (targetColumn == null) { // is target table has no column corresponding // to source table PK column - merge fails string message = "Column " + sourceColumn.ColumnName + " does not belongs to table " + targetTable.TableName; MergeFailedEventArgs e = new MergeFailedEventArgs(sourceTable, message); targetTable.DataSet.OnMergeFailed(e); return false; } else { targetColumns[i] = targetColumn; } } targetTable.PrimaryKey = targetColumns; } else { // if target table already has primary key and // if the length of primarykey is not equal - merge fails if (targetTable.PrimaryKey.Length != sourceTable.PrimaryKey.Length) { string message = ".PrimaryKey and .PrimaryKey have different Length."; MergeFailedEventArgs e = new MergeFailedEventArgs(sourceTable, message); targetTable.DataSet.OnMergeFailed(e); return false; } else { // we have to see that each primary column in the target table // has a column with the same name in the sourcetable primarykey columns. bool foundMatch; DataColumn[] targetDataColumns = targetTable.PrimaryKey; DataColumn[] srcDataColumns = sourceTable.PrimaryKey; // loop on all primary key columns in the targetTable. for (int i = 0; i < targetDataColumns.Length; i++) { foundMatch = false; DataColumn col = targetDataColumns[i]; // find if there is a column with the same name in the // sourceTable primary key columns. for (int j = 0; j < srcDataColumns.Length; j++) { if (srcDataColumns[j].ColumnName == col.ColumnName) { foundMatch = true; break; } } if (!foundMatch) { string message = "Mismatch columns in the PrimaryKey : ." + col.ColumnName + " versus ." + srcDataColumns[i].ColumnName + "."; MergeFailedEventArgs e = new MergeFailedEventArgs(sourceTable, message); targetTable.DataSet.OnMergeFailed(e); return false; } } } } } return true; } // fill the data from the source table to the target table private static void fillData(DataTable targetTable, DataTable sourceTable, bool preserveChanges) { for (int i = 0; i < sourceTable.Rows.Count; i++) { DataRow row = sourceTable.Rows[i]; MergeRow(targetTable, row, preserveChanges); } } // check tha column from 2 tables that has the same name also has the same datatype. private static void checkColumnTypes(DataTable targetTable, DataTable sourceTable) { for (int i = 0; i < sourceTable.Columns.Count; i++) { DataColumn fromCol = sourceTable.Columns[i]; DataColumn toCol = targetTable.Columns[fromCol.ColumnName]; if((toCol != null) && (toCol.DataType != fromCol.DataType)) throw new DataException("." + fromCol.ColumnName + " and ." + fromCol.ColumnName + " have conflicting properties: DataType property mismatch."); } } } }