PageRenderTime 35ms CodeModel.GetById 22ms app.highlight 10ms RepoModel.GetById 0ms app.codeStats 0ms

/mcs/class/System.Data/System.Data/MergeManager.cs

https://github.com/ccflo/mono
C# | 406 lines | 295 code | 60 blank | 51 comment | 103 complexity | 8c4dca6bdfea8717a6d8b6598ee286dd MD5 | raw file
  1
  2//
  3// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
  4//
  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:
 12// 
 13// The above copyright notice and this permission notice shall be
 14// included in all copies or substantial portions of the Software.
 15// 
 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.
 23//
 24using System;
 25using System.Collections;
 26
 27namespace System.Data
 28{
 29	/// <summary>
 30	/// Summary description for MergeManager.
 31	/// </summary>
 32	internal class MergeManager
 33	{
 34		internal static void Merge(DataSet targetSet, DataSet sourceSet, bool preserveChanges, MissingSchemaAction missingSchemaAction)
 35		{
 36			if(targetSet == null)
 37				throw new ArgumentNullException("targetSet");
 38			if(sourceSet == null)
 39				throw new ArgumentNullException("sourceSet");
 40			if (sourceSet == targetSet)
 41				return;
 42
 43			bool prevEC = targetSet.EnforceConstraints;
 44			targetSet.EnforceConstraints = false;
 45
 46			foreach (DataTable t in sourceSet.Tables)
 47				MergeManager.Merge(targetSet, t, preserveChanges, missingSchemaAction);
 48
 49			AdjustSchemaRelations (targetSet, sourceSet, missingSchemaAction);
 50			targetSet.EnforceConstraints = prevEC;
 51		}
 52
 53		internal static void Merge(DataSet targetSet, DataTable sourceTable, bool preserveChanges, MissingSchemaAction missingSchemaAction)
 54		{
 55			if(targetSet == null)
 56				throw new ArgumentNullException("targetSet");
 57			if(sourceTable == null)
 58				throw new ArgumentNullException("sourceTable");
 59			if (sourceTable.DataSet == targetSet)
 60				return;
 61
 62			bool savedEnfoceConstraints = targetSet.EnforceConstraints;
 63			targetSet.EnforceConstraints = false;
 64
 65			DataTable targetTable = null;
 66			if (!AdjustSchema(targetSet, sourceTable, missingSchemaAction,ref targetTable))
 67				return;
 68			if (targetTable != null)
 69				fillData(targetTable, sourceTable, preserveChanges);
 70			targetSet.EnforceConstraints = savedEnfoceConstraints;
 71		}
 72
 73		internal static void Merge (DataTable targetTable, 
 74					    DataTable sourceTable, 
 75					    bool preserveChanges, 
 76					    MissingSchemaAction missingSchemaAction)
 77		{
 78			if(targetTable== null)
 79				throw new ArgumentNullException("targetTable");
 80			if(sourceTable == null)
 81				throw new ArgumentNullException("sourceTable");
 82			if (sourceTable == targetTable)
 83				return;
 84
 85			bool savedEnforceConstraints = targetTable.EnforceConstraints;
 86			targetTable.EnforceConstraints = false;
 87
 88			if (!AdjustSchema(targetTable, sourceTable, missingSchemaAction))
 89				return;
 90
 91			fillData(targetTable, sourceTable, preserveChanges);
 92			targetTable.EnforceConstraints = savedEnforceConstraints;
 93		}
 94
 95		internal static void Merge(DataSet targetSet, DataRow[] sourceRows, bool preserveChanges, MissingSchemaAction missingSchemaAction)
 96		{
 97			if(targetSet == null)
 98				throw new ArgumentNullException("targetSet");
 99			if(sourceRows == null)
100				throw new ArgumentNullException("sourceRows");
101
102			bool savedEnfoceConstraints = targetSet.EnforceConstraints;
103			targetSet.EnforceConstraints = false;
104
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))
111					return;
112				if (targetTable != null) {
113					MergeRow(targetTable, row, preserveChanges);
114					if (!(targetTables.IndexOf(targetTable) >= 0))
115						targetTables.Add(targetTable);
116				}
117			}
118
119			targetSet.EnforceConstraints = savedEnfoceConstraints;
120		}
121
122		// merge a row into a target table.
123		private static void MergeRow(DataTable targetTable, DataRow row, bool preserveChanges)
124		{
125			DataColumn[] primaryKeys = targetTable.PrimaryKey;
126			DataRow targetRow = null;
127			DataRowVersion version = DataRowVersion.Default;
128			if (row.RowState == DataRowState.Deleted)
129				version = DataRowVersion.Original;
130
131			if (primaryKeys != null && primaryKeys.Length > 0) // if there are any primary key.
132			{
133				// initiate an array that has the values of the primary keys.
134				object[] keyValues = new object[primaryKeys.Length];
135				
136				for (int j = 0; j < keyValues.Length; j++)
137				{
138					keyValues[j] = row[primaryKeys[j].ColumnName, version];
139				}
140			
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);
145			}
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)
149			{ 
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);
154			}
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.
157			else
158			{
159				row.MergeValuesToRow(targetRow, preserveChanges);
160			}
161		}
162			
163		private static bool AdjustSchemaRelations (DataSet targetSet, DataSet sourceSet, MissingSchemaAction missingSchemaAction)
164		{
165			if (missingSchemaAction == MissingSchemaAction.Ignore)
166				return true;
167
168			foreach(DataTable sourceTable in sourceSet.Tables) {
169
170				DataTable targetTable = targetSet.Tables[sourceTable.TableName];
171				if (targetTable == null)
172					continue;
173
174				foreach (Constraint constraint in sourceTable.Constraints) {
175
176					Constraint targetConstraint = null;
177
178					string constraintName = constraint.ConstraintName;
179					if (targetTable.Constraints.Contains (constraintName))
180						constraintName = "";
181
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
185					if (uc != null) {
186						if (uc.IsPrimaryKey || uc.ChildConstraint != null)
187							continue;
188						DataColumn[] columns = ResolveColumns (targetTable, uc.Columns);
189						targetConstraint = new UniqueConstraint (constraintName, columns, false);
190					}
191
192					ForeignKeyConstraint fc = constraint as ForeignKeyConstraint;
193					if (fc != null) {
194						DataColumn[] columns = ResolveColumns (targetTable, fc.Columns);
195						DataColumn[] relatedColumns = ResolveColumns (targetSet.Tables [fc.RelatedTable.TableName],
196											fc.RelatedColumns);
197						targetConstraint = new ForeignKeyConstraint (constraintName, relatedColumns, columns);
198					}
199
200					bool dupConstraintFound = false;
201					foreach (Constraint cons in targetTable.Constraints) {
202					if (!targetConstraint.Equals (cons))
203						continue;
204					dupConstraintFound = true;
205					break;
206					}
207
208					// If equivalent-constraint already exists, then just do nothing
209					if (dupConstraintFound)
210						continue;
211
212					if (missingSchemaAction == MissingSchemaAction.Error)
213						throw new DataException ("Target DataSet missing " + targetConstraint.GetType () +
214								targetConstraint.ConstraintName);
215					else
216						targetTable.Constraints.Add (targetConstraint);
217				}
218			}
219
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);
226
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.");
238				}
239			}
240
241			return true;
242		}
243
244		private static DataColumn[] ResolveColumns(DataTable targetTable, DataColumn[] sourceColumns)
245		{
246			if (sourceColumns != null && sourceColumns.Length > 0) {
247				// lets just assume that all columns are from the Same table
248				if (targetTable != null) {
249					int i=0;
250					DataColumn[] targetColumns = new DataColumn[sourceColumns.Length];
251
252					DataColumn tmpCol ;
253					foreach(DataColumn sourceColumn in sourceColumns) {
254						tmpCol = targetTable.Columns[sourceColumn.ColumnName];
255						if (tmpCol == null)
256							throw new DataException ("Column " + sourceColumn.ColumnName  + 
257									" does not belong to table " + targetTable.TableName);
258						targetColumns [i++] = tmpCol;
259					}
260					return targetColumns;
261				}
262			}
263			return null;
264		}
265
266
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)
270		{
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)
276					return true;
277
278				if (missingSchemaAction == MissingSchemaAction.Error)
279					throw new ArgumentException ("Target DataSet missing definition for " +
280							sourceTable.TableName + ".");
281
282				targetTable = (DataTable)sourceTable.Clone();
283				targetSet.Tables.Add(targetTable);
284			}
285
286			AdjustSchema (targetTable, sourceTable, missingSchemaAction);
287
288			newTable = targetTable;
289			return true;
290		}
291
292
293		private static bool AdjustSchema(DataTable targetTable, DataTable sourceTable, MissingSchemaAction missingSchemaAction)
294		{
295			if (missingSchemaAction == MissingSchemaAction.Ignore)
296				return true;
297
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);
307					
308					targetColumn = new DataColumn(sourceColumn.ColumnName, sourceColumn.DataType, 
309								sourceColumn.Expression, sourceColumn.ColumnMapping);
310					targetTable.Columns.Add(targetColumn);
311				}
312
313				if(sourceColumn.AutoIncrement) {
314					targetColumn.AutoIncrement = sourceColumn.AutoIncrement;
315					targetColumn.AutoIncrementSeed = sourceColumn.AutoIncrementSeed;
316					targetColumn.AutoIncrementStep = sourceColumn.AutoIncrementStep;
317				}
318			}
319
320			if (!AdjustPrimaryKeys(targetTable, sourceTable))
321				return false;
322
323			checkColumnTypes (targetTable, sourceTable);
324
325			return true;
326		}
327	
328	
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)
333		{
334			if (sourceTable.PrimaryKey.Length == 0)
335				return true;
336
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;
341				return true;
342			}
343			
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.");
348				return false;
349			}
350
351			for (int i=0; i < targetTable.PrimaryKey.Length; ++i) {
352				if (targetTable.PrimaryKey [i].ColumnName.Equals (sourceTable.PrimaryKey [i].ColumnName))
353					continue;
354				RaiseMergeFailedEvent (targetTable, "Mismatch columns in the PrimaryKey : <target>." + 
355					targetTable.PrimaryKey [i].ColumnName + " versus <source>." + sourceTable.PrimaryKey [i].ColumnName);
356				return false;
357			}
358			return true;
359		}
360
361		// fill the data from the source table to the target table
362		private static void fillData(DataTable targetTable, DataTable sourceTable, bool preserveChanges)
363		{
364			for (int i = 0; i < sourceTable.Rows.Count; i++)
365			{
366				DataRow row = sourceTable.Rows[i];
367				MergeRow(targetTable, row, preserveChanges);
368			}
369		}
370
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)
373		{
374			for (int i = 0; i < sourceTable.Columns.Count; i++)
375			{
376				DataColumn fromCol = sourceTable.Columns[i];
377				DataColumn toCol = targetTable.Columns[fromCol.ColumnName];
378				if (toCol == null)
379					continue;
380				if (toCol.DataTypeMatches (fromCol))
381					continue;
382				throw new DataException("<target>." + fromCol.ColumnName + " and <source>." + 
383						fromCol.ColumnName + " have conflicting properties: DataType " + 
384						" property mismatch.");
385			}
386		}
387
388		private static bool CompareColumnArrays (DataColumn[] arr1, DataColumn[] arr2)
389		{
390			if (arr1.Length != arr2.Length)
391				return false;
392
393			for (int i=0; i < arr1.Length; ++i)
394				if (!arr1 [i].ColumnName.Equals (arr2 [i].ColumnName))
395					return false;
396			return true;
397		}
398
399		private static void RaiseMergeFailedEvent (DataTable targetTable, string errMsg)
400		{
401			MergeFailedEventArgs args = new MergeFailedEventArgs (targetTable, errMsg);
402			if (targetTable.DataSet != null)
403				targetTable.DataSet.OnMergeFailed (args);
404		}
405	}
406}