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.
29 /// Summary description for MergeManager.
31 internal class MergeManager
33 internal static void Merge(DataSet targetSet
, DataSet sourceSet
, bool preserveChanges
, MissingSchemaAction missingSchemaAction
)
36 throw new ArgumentNullException("targetSet");
38 throw new ArgumentNullException("sourceSet");
40 foreach (DataTable t
in sourceSet
.Tables
)
41 MergeManager
.Merge(targetSet
, t
, preserveChanges
, missingSchemaAction
);
43 AdjustSchema(targetSet
,sourceSet
,missingSchemaAction
);
47 internal static void Merge(DataSet targetSet
, DataTable sourceTable
, bool preserveChanges
, MissingSchemaAction missingSchemaAction
)
50 throw new ArgumentNullException("targetSet");
51 if(sourceTable
== null)
52 throw new ArgumentNullException("sourceTable");
54 DataTable targetTable
= null;
55 if (!AdjustSchema(targetSet
, sourceTable
, missingSchemaAction
,ref targetTable
)) {
58 if (targetTable
!= null) {
59 checkColumnTypes(targetTable
, sourceTable
); // check that the colums datatype is the same
60 fillData(targetTable
, sourceTable
, preserveChanges
);
64 internal static void Merge(DataSet targetSet
, DataRow
[] sourceRows
, bool preserveChanges
, MissingSchemaAction missingSchemaAction
)
67 throw new ArgumentNullException("targetSet");
68 if(sourceRows
== null)
69 throw new ArgumentNullException("sourceRows");
71 for (int i
= 0; i
< sourceRows
.Length
; i
++) {
72 DataRow row
= sourceRows
[i
];
73 DataTable sourceTable
= row
.Table
;
74 DataTable targetTable
= null;
75 if (!AdjustSchema(targetSet
, sourceTable
, missingSchemaAction
,ref targetTable
)) {
78 if (targetTable
!= null) {
79 checkColumnTypes(targetTable
, row
.Table
);
80 MergeRow(targetTable
, row
, preserveChanges
);
85 // merge a row into a target table.
86 private static void MergeRow(DataTable targetTable
, DataRow row
, bool preserveChanges
)
88 DataColumnCollection columns
= row
.Table
.Columns
;
89 DataColumn
[] primaryKeys
= targetTable
.PrimaryKey
;
90 DataRow targetRow
= null;
91 DataRowVersion version
= DataRowVersion
.Default
;
92 if (row
.RowState
== DataRowState
.Deleted
)
93 version
= DataRowVersion
.Original
;
95 if (primaryKeys
!= null && primaryKeys
.Length
> 0) // if there are any primary key.
97 // initiate an array that has the values of the primary keys.
98 object[] keyValues
= new object[primaryKeys
.Length
];
100 for (int j
= 0; j
< keyValues
.Length
; j
++)
102 keyValues
[j
] = row
[primaryKeys
[j
].ColumnName
, version
];
105 // find the row in the target table.
106 targetRow
= targetTable
.Rows
.Find(keyValues
);
108 // row doesn't exist in target table, or there are no primary keys.
109 // create new row and copy values from source row to the new row.
110 if (targetRow
== null)
112 targetRow
= targetTable
.NewRow();
113 row
.CopyValuesToRow(targetRow
);
114 targetTable
.Rows
.Add(targetRow
);
116 // row exists in target table, and presere changes is false -
117 // change the values of the target row to the values of the source row.
118 else if (!preserveChanges
)
120 row
.CopyValuesToRow(targetRow
);
126 // adjust the dataset schema according to the missingschemaaction param
128 // return false if adjusting fails.
129 private static bool AdjustSchema(DataSet targetSet
, DataSet sourceSet
, MissingSchemaAction missingSchemaAction
)
131 if (missingSchemaAction
== MissingSchemaAction
.Add
|| missingSchemaAction
== MissingSchemaAction
.AddWithKey
) {
132 foreach (DataRelation relation
in sourceSet
.Relations
) {
133 // TODO : add more precise condition (columns)
134 if (!targetSet
.Relations
.Contains(relation
.RelationName
)) {
135 DataTable targetTable
= targetSet
.Tables
[relation
.ParentColumns
[0].Table
.TableName
];
136 DataColumn
[] parentColumns
= ResolveColumns(sourceSet
,targetTable
,relation
.ParentColumns
);
137 targetTable
= targetSet
.Tables
[relation
.ChildColumns
[0].Table
.TableName
];
138 DataColumn
[] childColumns
= ResolveColumns(sourceSet
,targetTable
,relation
.ChildColumns
);
139 if (parentColumns
!= null && childColumns
!= null) {
140 DataRelation newRelation
= new DataRelation(relation
.RelationName
,parentColumns
,childColumns
);
141 targetSet
.Relations
.Add(newRelation
);
145 // TODO : should we throw an exeption ?
149 foreach(DataTable sourceTable
in sourceSet
.Tables
) {
150 DataTable targetTable
= targetSet
.Tables
[sourceTable
.TableName
];
152 if (targetTable
!= null) {
153 foreach(UniqueConstraint uc
in sourceTable
.Constraints
.UniqueConstraints
) {
154 // TODO : add more precise condition (columns)
155 if ( !targetTable
.Constraints
.Contains(uc
.ConstraintName
) ) {
156 DataColumn
[] columns
= ResolveColumns(sourceSet
,targetTable
,uc
.Columns
);
157 if (columns
!= null) {
158 UniqueConstraint newConstraint
= new UniqueConstraint(uc
.ConstraintName
,columns
,uc
.IsPrimaryKey
);
159 targetTable
.Constraints
.Add(newConstraint
);
163 // TODO : should we throw an exeption ?
167 foreach(ForeignKeyConstraint fc
in sourceTable
.Constraints
.ForeignKeyConstraints
) {
168 // TODO : add more precise condition (columns)
169 if (!targetTable
.Constraints
.Contains(fc
.ConstraintName
)) {
170 DataColumn
[] columns
= ResolveColumns(sourceSet
,targetTable
,fc
.Columns
);
171 DataTable relatedTable
= targetSet
.Tables
[fc
.RelatedTable
.TableName
];
172 DataColumn
[] relatedColumns
= ResolveColumns(sourceSet
,relatedTable
,fc
.RelatedColumns
);
173 if (columns
!= null && relatedColumns
!= null) {
174 ForeignKeyConstraint newConstraint
= new ForeignKeyConstraint(fc
.ConstraintName
,relatedColumns
,columns
);
175 targetTable
.Constraints
.Add(newConstraint
);
179 // TODO : should we throw an exeption ?
189 private static DataColumn
[] ResolveColumns(DataSet targetSet
,DataTable targetTable
,DataColumn
[] sourceColumns
)
191 if (sourceColumns
!= null && sourceColumns
.Length
> 0) {
192 // TODO : worth to check that all source colums come from the same table
193 if (targetTable
!= null) {
195 DataColumn
[] targetColumns
= new DataColumn
[sourceColumns
.Length
];
196 foreach(DataColumn sourceColumn
in sourceColumns
) {
197 targetColumns
[i
++] = targetTable
.Columns
[sourceColumn
.ColumnName
];
199 return targetColumns
;
206 // adjust the table schema according to the missingschemaaction param.
207 // return false if adjusting fails.
208 private static bool AdjustSchema(DataSet targetSet
, DataTable sourceTable
, MissingSchemaAction missingSchemaAction
, ref DataTable newTable
)
210 string tableName
= sourceTable
.TableName
;
212 // if the source table not exists in the target dataset
213 // we act according to the missingschemaaction param.
214 int tmp
= targetSet
.Tables
.IndexOf(tableName
);
215 // we need to check if it is equals names
216 if (tmp
!= -1 && !targetSet
.Tables
[tmp
].TableName
.Equals(tableName
))
219 if (missingSchemaAction
== MissingSchemaAction
.Ignore
) {
222 if (missingSchemaAction
== MissingSchemaAction
.Error
) {
223 throw new ArgumentException("Target DataSet missing definition for "+ tableName
+ ".");
226 DataTable cloneTable
= (DataTable
)sourceTable
.Clone();
227 targetSet
.Tables
.Add(cloneTable
);
228 tableName
= cloneTable
.TableName
;
231 DataTable table
= targetSet
.Tables
[tableName
];
233 for (int i
= 0; i
< sourceTable
.Columns
.Count
; i
++) {
234 DataColumn sourceColumn
= sourceTable
.Columns
[i
];
235 // if a column from the source table doesn't exists in the target table
236 // we act according to the missingschemaaction param.
237 DataColumn targetColumn
= table
.Columns
[sourceColumn
.ColumnName
];
238 if(targetColumn
== null) {
239 if (missingSchemaAction
== MissingSchemaAction
.Ignore
) {
242 if (missingSchemaAction
== MissingSchemaAction
.Error
) {
243 throw new ArgumentException(("Column '" + sourceColumn
.ColumnName
+ "' does not belong to table Items."));
246 targetColumn
= new DataColumn(sourceColumn
.ColumnName
, sourceColumn
.DataType
, sourceColumn
.Expression
, sourceColumn
.ColumnMapping
);
247 table
.Columns
.Add(targetColumn
);
250 if (sourceColumn
.Unique
) {
252 targetColumn
.Unique
= sourceColumn
.Unique
;
255 // Console.WriteLine("targetColumn : {0} targetTable : {1} ",targetColumn.ColumnName,table.TableName);
256 foreach(DataRow row
in table
.Rows
) {
257 // Console.WriteLine(row[targetColumn]);
263 if(sourceColumn
.AutoIncrement
) {
264 targetColumn
.AutoIncrement
= sourceColumn
.AutoIncrement
;
265 targetColumn
.AutoIncrementSeed
= sourceColumn
.AutoIncrementSeed
;
266 targetColumn
.AutoIncrementStep
= sourceColumn
.AutoIncrementStep
;
270 if (!AdjustPrimaryKeys(table
, sourceTable
)) {
278 // find if there is a valid matching between the targetTable PrimaryKey and the
279 // sourceTable primatyKey.
280 // return true if there is a match, else return false and raise a MergeFailedEvent.
281 private static bool AdjustPrimaryKeys(DataTable targetTable
, DataTable sourceTable
)
283 // if the length of one of the tables primarykey if 0 - there is nothing to check.
284 if (sourceTable
.PrimaryKey
.Length
!= 0) {
285 if (targetTable
.PrimaryKey
.Length
== 0) {
286 // if target table has no primary key at all -
287 // import primary key from source table
288 DataColumn
[] targetColumns
= new DataColumn
[sourceTable
.PrimaryKey
.Length
];
290 for(int i
=0; i
< sourceTable
.PrimaryKey
.Length
; i
++){
291 DataColumn sourceColumn
= sourceTable
.PrimaryKey
[i
];
292 DataColumn targetColumn
= targetTable
.Columns
[sourceColumn
.ColumnName
];
294 if (targetColumn
== null) {
295 // is target table has no column corresponding
296 // to source table PK column - merge fails
297 string message
= "Column " + sourceColumn
.ColumnName
+ " does not belongs to table " + targetTable
.TableName
;
298 MergeFailedEventArgs e
= new MergeFailedEventArgs(sourceTable
, message
);
299 targetTable
.DataSet
.OnMergeFailed(e
);
303 targetColumns
[i
] = targetColumn
;
306 targetTable
.PrimaryKey
= targetColumns
;
309 // if target table already has primary key and
310 // if the length of primarykey is not equal - merge fails
311 if (targetTable
.PrimaryKey
.Length
!= sourceTable
.PrimaryKey
.Length
) {
312 string message
= "<target>.PrimaryKey and <source>.PrimaryKey have different Length.";
313 MergeFailedEventArgs e
= new MergeFailedEventArgs(sourceTable
, message
);
314 targetTable
.DataSet
.OnMergeFailed(e
);
318 // we have to see that each primary column in the target table
319 // has a column with the same name in the sourcetable primarykey columns.
321 DataColumn
[] targetDataColumns
= targetTable
.PrimaryKey
;
322 DataColumn
[] srcDataColumns
= sourceTable
.PrimaryKey
;
324 // loop on all primary key columns in the targetTable.
325 for (int i
= 0; i
< targetDataColumns
.Length
; i
++) {
327 DataColumn col
= targetDataColumns
[i
];
329 // find if there is a column with the same name in the
330 // sourceTable primary key columns.
331 for (int j
= 0; j
< srcDataColumns
.Length
; j
++) {
332 if (srcDataColumns
[j
].ColumnName
== col
.ColumnName
) {
338 string message
= "Mismatch columns in the PrimaryKey : <target>." + col
.ColumnName
+ " versus <source>." + srcDataColumns
[i
].ColumnName
+ ".";
339 MergeFailedEventArgs e
= new MergeFailedEventArgs(sourceTable
, message
);
340 targetTable
.DataSet
.OnMergeFailed(e
);
352 // fill the data from the source table to the target table
353 private static void fillData(DataTable targetTable
, DataTable sourceTable
, bool preserveChanges
)
355 for (int i
= 0; i
< sourceTable
.Rows
.Count
; i
++)
357 DataRow row
= sourceTable
.Rows
[i
];
358 MergeRow(targetTable
, row
, preserveChanges
);
362 // check tha column from 2 tables that has the same name also has the same datatype.
363 private static void checkColumnTypes(DataTable targetTable
, DataTable sourceTable
)
365 for (int i
= 0; i
< sourceTable
.Columns
.Count
; i
++)
367 DataColumn fromCol
= sourceTable
.Columns
[i
];
368 DataColumn toCol
= targetTable
.Columns
[fromCol
.ColumnName
];
369 if((toCol
!= null) && (toCol
.DataType
!= fromCol
.DataType
))
370 throw new DataException("<target>." + fromCol
.ColumnName
+ " and <source>." + fromCol
.ColumnName
+ " have conflicting properties: DataType property mismatch.");