Merger.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. //------------------------------------------------------------------------------
  2. // <copyright file="Merger.cs" company="Microsoft">
  3. // Copyright (c) Microsoft Corporation. All rights reserved.
  4. // </copyright>
  5. // <owner current="true" primary="true">[....]</owner>
  6. // <owner current="true" primary="false">[....]</owner>
  7. //------------------------------------------------------------------------------
  8. namespace System.Data {
  9. using System;
  10. using System.Collections;
  11. using System.Collections.Generic;
  12. using System.ComponentModel;
  13. using System.Diagnostics;
  14. /// <devdoc>
  15. /// Merge Utilities.
  16. /// </devdoc>
  17. internal sealed class Merger {
  18. private DataSet dataSet = null;
  19. private DataTable dataTable = null;
  20. private bool preserveChanges;
  21. private MissingSchemaAction missingSchemaAction;
  22. private bool isStandAlonetable = false;
  23. private bool _IgnoreNSforTableLookup = false; // Everett Behavior : SQL BU DT 370850
  24. internal Merger(DataSet dataSet, bool preserveChanges, MissingSchemaAction missingSchemaAction) {
  25. this.dataSet = dataSet;
  26. this.preserveChanges = preserveChanges;
  27. // map AddWithKey -> Add
  28. if (missingSchemaAction == MissingSchemaAction.AddWithKey)
  29. this.missingSchemaAction = MissingSchemaAction.Add;
  30. else
  31. this.missingSchemaAction = missingSchemaAction;
  32. }
  33. internal Merger(DataTable dataTable, bool preserveChanges, MissingSchemaAction missingSchemaAction) {
  34. isStandAlonetable = true;
  35. this.dataTable = dataTable;
  36. this.preserveChanges = preserveChanges;
  37. // map AddWithKey -> Add
  38. if (missingSchemaAction == MissingSchemaAction.AddWithKey)
  39. this.missingSchemaAction = MissingSchemaAction.Add;
  40. else
  41. this.missingSchemaAction = missingSchemaAction;
  42. }
  43. internal void MergeDataSet(DataSet source) {
  44. if (source == dataSet) return; //somebody is doing an 'automerge'
  45. bool fEnforce = dataSet.EnforceConstraints;
  46. dataSet.EnforceConstraints = false;
  47. _IgnoreNSforTableLookup = (dataSet.namespaceURI != source.namespaceURI); // if two DataSets have different
  48. // Namespaces, ignore NS for table lookups as we wont be able to find the right tables which inherits its NS
  49. List<DataColumn> existingColumns = null;// need to cache existing columns
  50. if (MissingSchemaAction.Add == missingSchemaAction) {
  51. existingColumns = new List<DataColumn>(); // need to cache existing columns
  52. foreach(DataTable dt in dataSet.Tables) {
  53. foreach(DataColumn dc in dt.Columns) {
  54. existingColumns.Add(dc);
  55. }
  56. }
  57. }
  58. for (int i = 0; i < source.Tables.Count; i++) {
  59. MergeTableData(source.Tables[i]); // since column expression might have dependency on relation, we do not set
  60. //column expression at this point. We need to set it after adding relations
  61. }
  62. if (MissingSchemaAction.Ignore != missingSchemaAction) {
  63. // Add all independent constraints
  64. MergeConstraints(source);
  65. // Add all relationships
  66. for (int i = 0; i < source.Relations.Count; i++) {
  67. MergeRelation(source.Relations[i]);
  68. }
  69. }
  70. // WebData 88234
  71. if (MissingSchemaAction.Add == missingSchemaAction) { // for which other options we should add expressions also?
  72. foreach (DataTable sourceTable in source.Tables) {
  73. DataTable targetTable;
  74. if (_IgnoreNSforTableLookup) {
  75. targetTable = dataSet.Tables[sourceTable.TableName];
  76. }
  77. else {
  78. targetTable = dataSet.Tables[sourceTable.TableName, sourceTable.Namespace];// we know that target table wont be null since MissingSchemaAction is Add , we have already added it!
  79. }
  80. foreach(DataColumn dc in sourceTable.Columns) { // Should we overwrite the previous expression column? No, refer to spec, if it is new column we need to add the schema
  81. if (dc.Computed) {
  82. DataColumn targetColumn = targetTable.Columns[dc.ColumnName];
  83. if (!existingColumns.Contains(targetColumn)) {
  84. targetColumn.Expression = dc.Expression;
  85. }
  86. }
  87. }
  88. }
  89. }
  90. MergeExtendedProperties(source.ExtendedProperties, dataSet.ExtendedProperties);
  91. foreach(DataTable dt in dataSet.Tables)
  92. dt.EvaluateExpressions();
  93. dataSet.EnforceConstraints = fEnforce;
  94. }
  95. internal void MergeTable(DataTable src) {
  96. bool fEnforce = false;
  97. if (!isStandAlonetable) {
  98. if (src.DataSet == dataSet) return; //somebody is doing an 'automerge'
  99. fEnforce = dataSet.EnforceConstraints;
  100. dataSet.EnforceConstraints = false;
  101. }
  102. else {
  103. if (src == dataTable) return; //somebody is doing an 'automerge'
  104. dataTable.SuspendEnforceConstraints = true;
  105. }
  106. if (this.dataSet != null) { // this is ds.Merge
  107. // if source does not have a DS, or if NS of both DS does not match, ignore the NS
  108. if (src.DataSet == null || src.DataSet.namespaceURI != this.dataSet.namespaceURI) {
  109. _IgnoreNSforTableLookup = true;
  110. }
  111. }
  112. else { // this is dt.Merge
  113. if (this.dataTable.DataSet == null || src.DataSet == null ||
  114. src.DataSet.namespaceURI != this.dataTable.DataSet.namespaceURI) {
  115. _IgnoreNSforTableLookup = true;
  116. }
  117. }
  118. MergeTableData(src);
  119. DataTable dt = dataTable;
  120. if (dt == null && dataSet != null) {
  121. if (_IgnoreNSforTableLookup) {
  122. dt = dataSet.Tables[src.TableName];
  123. }
  124. else {
  125. dt = dataSet.Tables[src.TableName, src.Namespace];
  126. }
  127. }
  128. if (dt != null) {
  129. dt.EvaluateExpressions();
  130. }
  131. if (!isStandAlonetable) {
  132. dataSet.EnforceConstraints = fEnforce;
  133. }
  134. else {
  135. dataTable.SuspendEnforceConstraints = false;
  136. try {
  137. if (dataTable.EnforceConstraints) {
  138. dataTable.EnableConstraints();
  139. }
  140. }
  141. catch(ConstraintException) {
  142. if (dataTable.DataSet != null) {
  143. dataTable.DataSet.EnforceConstraints = false;
  144. }
  145. throw;
  146. }
  147. }
  148. }
  149. private void MergeTable(DataTable src, DataTable dst) {
  150. int rowsCount = src.Rows.Count;
  151. bool wasEmpty = dst.Rows.Count == 0;
  152. if(0 < rowsCount) {
  153. Index ndxSearch = null;
  154. DataKey key = default(DataKey);
  155. dst.SuspendIndexEvents();
  156. try {
  157. if(! wasEmpty && dst.primaryKey != null) {
  158. key = GetSrcKey(src, dst);
  159. if (key.HasValue)
  160. ndxSearch = dst.primaryKey.Key.GetSortIndex(DataViewRowState.OriginalRows | DataViewRowState.Added );
  161. }
  162. // SQLBU 414992: Serious performance issue when calling Merge
  163. // this improves performance by iterating over the rows instead of computing their position
  164. foreach(DataRow sourceRow in src.Rows) {
  165. DataRow targetRow = null;
  166. if(ndxSearch != null) {
  167. targetRow = dst.FindMergeTarget(sourceRow, key, ndxSearch);
  168. }
  169. dst.MergeRow(sourceRow, targetRow, preserveChanges, ndxSearch);
  170. }
  171. }
  172. finally {
  173. dst.RestoreIndexEvents(true);
  174. }
  175. }
  176. MergeExtendedProperties(src.ExtendedProperties, dst.ExtendedProperties);
  177. }
  178. internal void MergeRows(DataRow[] rows) {
  179. DataTable src = null;
  180. DataTable dst = null;
  181. DataKey key = default(DataKey);
  182. Index ndxSearch = null;
  183. bool fEnforce = dataSet.EnforceConstraints;
  184. dataSet.EnforceConstraints = false;
  185. for (int i = 0; i < rows.Length; i++) {
  186. DataRow row = rows[i];
  187. if (row == null) {
  188. throw ExceptionBuilder.ArgumentNull("rows[" + i + "]");
  189. }
  190. if (row.Table == null) {
  191. throw ExceptionBuilder.ArgumentNull("rows[" + i + "].Table");
  192. }
  193. //somebody is doing an 'automerge'
  194. if (row.Table.DataSet == dataSet)
  195. continue;
  196. if (src != row.Table) { // row.Table changed from prev. row.
  197. src = row.Table;
  198. dst = MergeSchema(row.Table);
  199. if (dst == null) {
  200. Debug.Assert(MissingSchemaAction.Ignore == missingSchemaAction, "MergeSchema failed");
  201. dataSet.EnforceConstraints = fEnforce;
  202. return;
  203. }
  204. if(dst.primaryKey != null) {
  205. key = GetSrcKey(src, dst);
  206. }
  207. if (key.HasValue) {
  208. // Getting our own copy instead. ndxSearch = dst.primaryKey.Key.GetSortIndex();
  209. // IMO, Better would be to reuse index
  210. // ndxSearch = dst.primaryKey.Key.GetSortIndex(DataViewRowState.OriginalRows | DataViewRowState.Added );
  211. if (null != ndxSearch) {
  212. ndxSearch.RemoveRef();
  213. ndxSearch = null;
  214. }
  215. ndxSearch = new Index(dst, dst.primaryKey.Key.GetIndexDesc(), DataViewRowState.OriginalRows | DataViewRowState.Added, (IFilter)null);
  216. ndxSearch.AddRef(); // need to addref twice, otherwise it will be collected
  217. ndxSearch.AddRef(); // in past first adref was done in const
  218. }
  219. }
  220. if (row.newRecord == -1 && row.oldRecord == -1)
  221. continue;
  222. DataRow targetRow = null;
  223. if(0 < dst.Rows.Count && ndxSearch != null) {
  224. targetRow = dst.FindMergeTarget(row, key, ndxSearch);
  225. }
  226. targetRow = dst.MergeRow(row, targetRow, preserveChanges, ndxSearch);
  227. if (targetRow.Table.dependentColumns != null && targetRow.Table.dependentColumns.Count > 0)
  228. targetRow.Table.EvaluateExpressions(targetRow, DataRowAction.Change, null);
  229. }
  230. if (null != ndxSearch) {
  231. ndxSearch.RemoveRef();
  232. ndxSearch = null;
  233. }
  234. dataSet.EnforceConstraints = fEnforce;
  235. }
  236. private DataTable MergeSchema(DataTable table) {
  237. DataTable targetTable = null;
  238. if (!isStandAlonetable) {
  239. if (dataSet.Tables.Contains(table.TableName, true))
  240. if (_IgnoreNSforTableLookup) {
  241. targetTable = dataSet.Tables[table.TableName];
  242. }
  243. else {
  244. targetTable = dataSet.Tables[table.TableName, table.Namespace];
  245. }
  246. }
  247. else {
  248. targetTable = dataTable;
  249. }
  250. if (targetTable == null) { // in case of standalone table, we make sure that targetTable is not null, so if this check passes, it will be when it is called via detaset
  251. if (MissingSchemaAction.Add == missingSchemaAction) {
  252. targetTable = table.Clone(table.DataSet); // if we are here mainly we are called from DataSet.Merge at this point we don't set
  253. //expression columns, since it might have refer to other columns via relation, so it wont find the table and we get exception;
  254. // do it after adding relations.
  255. dataSet.Tables.Add(targetTable);
  256. }
  257. else if (MissingSchemaAction.Error == missingSchemaAction) {
  258. throw ExceptionBuilder.MergeMissingDefinition(table.TableName);
  259. }
  260. }
  261. else {
  262. if (MissingSchemaAction.Ignore != missingSchemaAction) {
  263. // Do the columns
  264. int oldCount = targetTable.Columns.Count;
  265. for (int i = 0; i < table.Columns.Count; i++) {
  266. DataColumn src = table.Columns[i];
  267. DataColumn dest = (targetTable.Columns.Contains(src.ColumnName, true)) ? targetTable.Columns[src.ColumnName] : null;
  268. if (dest == null) {
  269. if (MissingSchemaAction.Add == missingSchemaAction) {
  270. dest = src.Clone();
  271. targetTable.Columns.Add(dest);
  272. }
  273. else {
  274. if (!isStandAlonetable)
  275. dataSet.RaiseMergeFailed(targetTable, Res.GetString(Res.DataMerge_MissingColumnDefinition, table.TableName, src.ColumnName), missingSchemaAction);
  276. else
  277. throw ExceptionBuilder.MergeFailed(Res.GetString(Res.DataMerge_MissingColumnDefinition, table.TableName, src.ColumnName));
  278. }
  279. }
  280. else {
  281. if (dest.DataType != src.DataType ||
  282. ((dest.DataType == typeof(DateTime)) && (dest.DateTimeMode != src.DateTimeMode) && ((dest.DateTimeMode & src.DateTimeMode) != DataSetDateTime.Unspecified))) {
  283. if (!isStandAlonetable)
  284. dataSet.RaiseMergeFailed(targetTable, Res.GetString(Res.DataMerge_DataTypeMismatch, src.ColumnName), MissingSchemaAction.Error);
  285. else
  286. throw ExceptionBuilder.MergeFailed(Res.GetString(Res.DataMerge_DataTypeMismatch, src.ColumnName));
  287. }
  288. //
  289. MergeExtendedProperties(src.ExtendedProperties, dest.ExtendedProperties);
  290. }
  291. }
  292. // Set DataExpression
  293. if (isStandAlonetable) {
  294. for (int i = oldCount; i < targetTable.Columns.Count; i++) {
  295. targetTable.Columns[i].Expression = table.Columns[targetTable.Columns[i].ColumnName].Expression;
  296. }
  297. }
  298. // check the PrimaryKey
  299. DataColumn[] targetPKey = targetTable.PrimaryKey;
  300. DataColumn[] tablePKey = table.PrimaryKey;
  301. if (targetPKey.Length != tablePKey.Length) {
  302. // special case when the target table does not have the PrimaryKey
  303. if (targetPKey.Length == 0) {
  304. DataColumn[] key = new DataColumn[tablePKey.Length];
  305. for (int i = 0; i < tablePKey.Length; i++) {
  306. key[i] = targetTable.Columns[tablePKey[i].ColumnName];
  307. }
  308. targetTable.PrimaryKey = key;
  309. }
  310. else if (tablePKey.Length != 0) {
  311. dataSet.RaiseMergeFailed(targetTable, Res.GetString(Res.DataMerge_PrimaryKeyMismatch), missingSchemaAction);
  312. }
  313. }
  314. else {
  315. for (int i = 0; i < targetPKey.Length; i++) {
  316. if (String.Compare(targetPKey[i].ColumnName, tablePKey[i].ColumnName, false, targetTable.Locale) != 0) {
  317. dataSet.RaiseMergeFailed(table,
  318. Res.GetString(Res.DataMerge_PrimaryKeyColumnsMismatch, targetPKey[i].ColumnName, tablePKey[i].ColumnName),
  319. missingSchemaAction
  320. );
  321. }
  322. }
  323. }
  324. }
  325. MergeExtendedProperties(table.ExtendedProperties, targetTable.ExtendedProperties);
  326. }
  327. return targetTable;
  328. }
  329. private void MergeTableData(DataTable src) {
  330. DataTable dest = MergeSchema(src);
  331. if (dest == null) return;
  332. dest.MergingData = true;
  333. try {
  334. MergeTable(src, dest);
  335. }
  336. finally {
  337. dest.MergingData = false;
  338. }
  339. }
  340. private void MergeConstraints(DataSet source) {
  341. for (int i = 0; i < source.Tables.Count; i ++) {
  342. MergeConstraints(source.Tables[i]);
  343. }
  344. }
  345. private void MergeConstraints(DataTable table) {
  346. // Merge constraints
  347. for (int i = 0; i < table.Constraints.Count; i++) {
  348. Constraint src = table.Constraints[i];
  349. Constraint dest = src.Clone(dataSet, _IgnoreNSforTableLookup);
  350. if (dest == null) {
  351. dataSet.RaiseMergeFailed(table,
  352. Res.GetString(Res.DataMerge_MissingConstraint, src.GetType().FullName, src.ConstraintName),
  353. missingSchemaAction
  354. );
  355. }
  356. else {
  357. Constraint cons = dest.Table.Constraints.FindConstraint(dest);
  358. if (cons == null) {
  359. if (MissingSchemaAction.Add == missingSchemaAction) {
  360. try {
  361. // try to keep the original name
  362. dest.Table.Constraints.Add(dest);
  363. }
  364. catch (DuplicateNameException) {
  365. // if fail, assume default name
  366. dest.ConstraintName = "";
  367. dest.Table.Constraints.Add(dest);
  368. }
  369. }
  370. else if (MissingSchemaAction.Error == missingSchemaAction) {
  371. dataSet.RaiseMergeFailed(table,
  372. Res.GetString(Res.DataMerge_MissingConstraint, src.GetType().FullName, src.ConstraintName),
  373. missingSchemaAction
  374. );
  375. }
  376. }
  377. else {
  378. MergeExtendedProperties(src.ExtendedProperties, cons.ExtendedProperties);
  379. }
  380. }
  381. }
  382. }
  383. private void MergeRelation(DataRelation relation) {
  384. Debug.Assert(MissingSchemaAction.Error == missingSchemaAction ||
  385. MissingSchemaAction.Add == missingSchemaAction,
  386. "Unexpected value of MissingSchemaAction parameter : " + ((Enum) missingSchemaAction).ToString());
  387. DataRelation destRelation = null;
  388. // try to find given relation in this dataSet
  389. int iDest = dataSet.Relations.InternalIndexOf(relation.RelationName);
  390. if (iDest >= 0) {
  391. // check the columns and Relation properties..
  392. destRelation = dataSet.Relations[iDest];
  393. if (relation.ParentKey.ColumnsReference.Length != destRelation.ParentKey.ColumnsReference.Length) {
  394. dataSet.RaiseMergeFailed(null,
  395. Res.GetString(Res.DataMerge_MissingDefinition, relation.RelationName),
  396. missingSchemaAction
  397. );
  398. }
  399. for (int i = 0; i < relation.ParentKey.ColumnsReference.Length; i++) {
  400. DataColumn dest = destRelation.ParentKey.ColumnsReference[i];
  401. DataColumn src = relation.ParentKey.ColumnsReference[i];
  402. if (0 != string.Compare(dest.ColumnName, src.ColumnName, false, dest.Table.Locale)) {
  403. dataSet.RaiseMergeFailed(null,
  404. Res.GetString(Res.DataMerge_ReltionKeyColumnsMismatch, relation.RelationName),
  405. missingSchemaAction
  406. );
  407. }
  408. dest = destRelation.ChildKey.ColumnsReference[i];
  409. src = relation.ChildKey.ColumnsReference[i];
  410. if (0 != string.Compare(dest.ColumnName, src.ColumnName, false, dest.Table.Locale)) {
  411. dataSet.RaiseMergeFailed(null,
  412. Res.GetString(Res.DataMerge_ReltionKeyColumnsMismatch, relation.RelationName),
  413. missingSchemaAction
  414. );
  415. }
  416. }
  417. }
  418. else {
  419. if (MissingSchemaAction.Add == missingSchemaAction) {
  420. // create identical realtion in the current dataset
  421. DataTable parent;
  422. if (_IgnoreNSforTableLookup){
  423. parent = dataSet.Tables[relation.ParentTable.TableName];
  424. }
  425. else {
  426. parent = dataSet.Tables[relation.ParentTable.TableName, relation.ParentTable.Namespace];
  427. }
  428. DataTable child;
  429. if (_IgnoreNSforTableLookup) {
  430. child = dataSet.Tables[relation.ChildTable.TableName];
  431. }
  432. else {
  433. child = dataSet.Tables[relation.ChildTable.TableName,relation.ChildTable.Namespace];
  434. }
  435. DataColumn[] parentColumns = new DataColumn[relation.ParentKey.ColumnsReference.Length];
  436. DataColumn[] childColumns = new DataColumn[relation.ParentKey.ColumnsReference.Length];
  437. for (int i = 0; i < relation.ParentKey.ColumnsReference.Length; i++) {
  438. parentColumns[i] = parent.Columns[relation.ParentKey.ColumnsReference[i].ColumnName];
  439. childColumns[i] = child.Columns[relation.ChildKey.ColumnsReference[i].ColumnName];
  440. }
  441. try {
  442. destRelation = new DataRelation(relation.RelationName, parentColumns, childColumns, relation.createConstraints);
  443. destRelation.Nested = relation.Nested;
  444. dataSet.Relations.Add(destRelation);
  445. }
  446. catch (Exception e) {
  447. //
  448. if (!Common.ADP.IsCatchableExceptionType(e)) {
  449. throw;
  450. }
  451. ExceptionBuilder.TraceExceptionForCapture(e);
  452. dataSet.RaiseMergeFailed(null, e.Message, missingSchemaAction);
  453. }
  454. }
  455. else {
  456. Debug.Assert(MissingSchemaAction.Error == missingSchemaAction, "Unexpected value of MissingSchemaAction parameter : " + ((Enum) missingSchemaAction).ToString());
  457. throw ExceptionBuilder.MergeMissingDefinition(relation.RelationName);
  458. }
  459. }
  460. MergeExtendedProperties(relation.ExtendedProperties, destRelation.ExtendedProperties);
  461. return;
  462. }
  463. private void MergeExtendedProperties(PropertyCollection src, PropertyCollection dst) {
  464. if (MissingSchemaAction.Ignore == missingSchemaAction) {
  465. return;
  466. }
  467. IDictionaryEnumerator srcDE = src.GetEnumerator();
  468. while (srcDE.MoveNext()) {
  469. if (!preserveChanges || dst[srcDE.Key] == null)
  470. dst[srcDE.Key] = srcDE.Value;
  471. }
  472. }
  473. private DataKey GetSrcKey(DataTable src, DataTable dst) {
  474. if (src.primaryKey != null)
  475. return src.primaryKey.Key;
  476. DataKey key = default(DataKey);
  477. if (dst.primaryKey != null) {
  478. DataColumn[] dstColumns = dst.primaryKey.Key.ColumnsReference;
  479. DataColumn[] srcColumns = new DataColumn[dstColumns.Length];
  480. for (int j = 0; j < dstColumns.Length; j++) {
  481. srcColumns[j] = src.Columns[dstColumns[j].ColumnName];
  482. }
  483. key = new DataKey(srcColumns, false); // DataKey will take ownership of srcColumns
  484. }
  485. return key;
  486. }
  487. }
  488. }