1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System.Collections; 6 using System.Collections.Generic; 7 using System.ComponentModel; 8 using System.Data; 9 using System.Diagnostics; 10 using System.IO; 11 using System.Xml.XPath; 12 13 namespace System.Xml 14 { 15 /// <summary> 16 /// Represents an entire document. An XmlDataDocument can contain XML 17 /// data or relational data (DataSet). 18 /// </summary> 19 [Obsolete("XmlDataDocument class will be removed in a future release.")] 20 #if MONO // keep it public in MONO 21 public class XmlDataDocument : XmlDocument 22 #else 23 internal class XmlDataDocument : XmlDocument 24 #endif 25 { 26 private DataSet _dataSet; 27 28 private DataSetMapper _mapper; 29 internal Hashtable _pointers; // Hastable w/ all pointer objects used by this XmlDataDocument. Hashtable are guaranteed to work OK w/ one writer and mutiple readers, so as long as we guarantee 30 // that there is at most one thread in AddPointer we are OK. 31 private int _countAddPointer; // Approximate count of how many times AddPointer was called since the last time we removed the unused pointer objects from pointers hashtable. 32 private ArrayList _columnChangeList; 33 private DataRowState _rollbackState; 34 35 private bool _fBoundToDataSet; // true if our permanent event listeners are registered to receive DataSet events 36 private bool _fBoundToDocument; // true if our permanent event listeners are registered to receive XML events. Note that both fBoundToDataSet and fBoundToDataSet should be both true or false. 37 private bool _fDataRowCreatedSpecial; // true if our special event listener is registered to receive DataRowCreated events. Note that we either have special listeners subsribed or permanent ones (i.e. fDataRowCreatedSpecial and fBoundToDocument/fBoundToDataSet cannot be both true). 38 private bool _ignoreXmlEvents; // true if XML events should not be processed 39 private bool _ignoreDataSetEvents; // true if DataSet events should not be processed 40 private bool _isFoliationEnabled; // true if we should create and reveal the virtual nodes, false if we should reveal only the physical stored nodes 41 private bool _optimizeStorage; // false if we should only have foilated regions. 42 private ElementState _autoFoliationState; // When XmlBoundElement will foliate because of member functions, this will contain the foliation mode: usually this is 43 // ElementState.StrongFoliation, however when foliation occurs due to DataDocumentNavigator operations (InsertNode for example), 44 // it it usually ElementState.WeakFoliation 45 private bool _fAssociateDataRow; // if true, CreateElement will create and associate data rows w/ the newly created XmlBoundElement. 46 // If false, then CreateElement will just create the XmlBoundElement nodes. This is usefull for Loading case, 47 // when CreateElement is called by DOM. 48 private object _foliationLock; 49 internal const string XSI_NIL = "xsi:nil"; 50 internal const string XSI = "xsi"; 51 private bool _bForceExpandEntity = false; 52 internal XmlAttribute _attrXml = null; 53 internal bool _bLoadFromDataSet = false; 54 internal bool _bHasXSINIL = false; 55 AddPointer(IXmlDataVirtualNode pointer)56 internal void AddPointer(IXmlDataVirtualNode pointer) 57 { 58 Debug.Assert(_pointers.ContainsValue(pointer) == false); 59 lock (_pointers) 60 { 61 _countAddPointer++; 62 if (_countAddPointer >= 5) 63 { // 5 is choosed to be small enough to not affect perf, but high enough so we will not scan all the time 64 ArrayList al = new ArrayList(); 65 foreach (DictionaryEntry entry in _pointers) 66 { 67 IXmlDataVirtualNode temp = (IXmlDataVirtualNode)(entry.Value); 68 Debug.Assert(temp != null); 69 if (!temp.IsInUse()) 70 al.Add(temp); 71 } 72 for (int i = 0; i < al.Count; i++) 73 { 74 _pointers.Remove(al[i]); 75 } 76 _countAddPointer = 0; 77 } 78 _pointers[pointer] = pointer; 79 } 80 } 81 82 [System.Diagnostics.Conditional("DEBUG")] AssertPointerPresent(IXmlDataVirtualNode pointer)83 internal void AssertPointerPresent(IXmlDataVirtualNode pointer) 84 { 85 #if DEBUG 86 object val = _pointers[pointer]; 87 if (val != (object)pointer) 88 Debug.Assert(false); 89 #endif 90 } 91 // This function attaches the DataSet to XmlDataDocument 92 // We also register a special listener (OnDataRowCreatedSpecial) to DataSet, so we know when we should setup all regular listeners (OnDataRowCreated, OnColumnChanging, etc). 93 // We need to do this because of the following scenario: 94 // - XmlDataDocument doc = new XmlDataDocument(); 95 // - DataSet ds = doc.DataSet; // doc.DataSet creates a data-set, however does not sets-up the regular listeners. 96 // - ds.ReadXmlSchema(); // since there are regular listeners in doc that track ds schema changes, doc does not know about the new tables/columns/etc 97 // - ds.ReadXmlData(); // ds is now filled, however doc has no content (since there were no listeners for the new created DataRow's) 98 // We can set-up listeners and track each change in schema, but it is more perf-friendly to do it laizily, all at once, when the first DataRow is created 99 // (we rely on the fact that DataRowCreated is a DataSet wide event, rather than a DataTable event) AttachDataSet(DataSet ds)100 private void AttachDataSet(DataSet ds) 101 { 102 // You should not have already an associated dataset 103 Debug.Assert(_dataSet == null); 104 Debug.Assert(ds != null); 105 if (ds.FBoundToDocument) 106 throw new ArgumentException(SR.DataDom_MultipleDataSet); 107 ds.FBoundToDocument = true; 108 _dataSet = ds; 109 // Register the special listener to catch the first DataRow event(s) 110 BindSpecialListeners(); 111 } 112 113 // after loading, all detached DataRows are synchronized with the xml tree and inserted to their tables 114 // or after setting the innerxml, synchronize the rows and if created new and detached, will be inserted. SyncRows(DataRow parentRow, XmlNode node, bool fAddRowsToTable)115 internal void SyncRows(DataRow parentRow, XmlNode node, bool fAddRowsToTable) 116 { 117 XmlBoundElement be = node as XmlBoundElement; 118 if (be != null) 119 { 120 DataRow r = be.Row; 121 if (r != null && be.ElementState == ElementState.Defoliated) 122 return; //no need of syncRow 123 124 if (r != null) 125 { 126 // get all field values. 127 SynchronizeRowFromRowElement(be); 128 129 // defoliate if possible 130 be.ElementState = ElementState.WeakFoliation; 131 DefoliateRegion(be); 132 133 if (parentRow != null) 134 SetNestedParentRow(r, parentRow); 135 if (fAddRowsToTable && r.RowState == DataRowState.Detached) 136 r.Table.Rows.Add(r); 137 parentRow = r; 138 } 139 } 140 141 // Attach all rows from children nodes 142 for (XmlNode child = node.FirstChild; child != null; child = child.NextSibling) 143 SyncRows(parentRow, child, fAddRowsToTable); 144 } 145 146 // All detached DataRows are synchronized with the xml tree and inserted to their tables. 147 // Synchronize the rows and if created new and detached, will be inserted. SyncTree(XmlNode node)148 internal void SyncTree(XmlNode node) 149 { 150 XmlBoundElement be = null; 151 _mapper.GetRegion(node, out be); 152 DataRow parentRow = null; 153 bool fAddRowsToTable = IsConnected(node); 154 155 if (be != null) 156 { 157 DataRow r = be.Row; 158 if (r != null && be.ElementState == ElementState.Defoliated) 159 return; //no need of syncRow 160 161 if (r != null) 162 { 163 // get all field values. 164 SynchronizeRowFromRowElement(be); 165 166 // defoliation will not be done on the node which is not RowElement, in case of node is externally being used 167 if (node == be) 168 { 169 // defoliate if possible 170 be.ElementState = ElementState.WeakFoliation; 171 DefoliateRegion(be); 172 } 173 174 if (fAddRowsToTable && r.RowState == DataRowState.Detached) 175 r.Table.Rows.Add(r); 176 177 parentRow = r; 178 } 179 } 180 181 // Attach all rows from children nodes 182 for (XmlNode child = node.FirstChild; child != null; child = child.NextSibling) 183 SyncRows(parentRow, child, fAddRowsToTable); 184 } 185 186 internal ElementState AutoFoliationState 187 { 188 get { return _autoFoliationState; } 189 set { _autoFoliationState = value; } 190 } 191 BindForLoad()192 private void BindForLoad() 193 { 194 Debug.Assert(_ignoreXmlEvents == true); 195 _ignoreDataSetEvents = true; 196 _mapper.SetupMapping(this, _dataSet); 197 if (_dataSet.Tables.Count > 0) 198 { 199 //at least one table 200 LoadDataSetFromTree(); 201 } 202 BindListeners(); 203 _ignoreDataSetEvents = false; 204 } 205 Bind(bool fLoadFromDataSet)206 private void Bind(bool fLoadFromDataSet) 207 { 208 // If we have a DocumentElement then it is illegal to call this func to load from data-set 209 Debug.Assert(DocumentElement == null || !fLoadFromDataSet); 210 211 _ignoreDataSetEvents = true; 212 _ignoreXmlEvents = true; 213 214 // Do the mapping. This could be a successive mapping in case of this scenario: xd = XmlDataDocument( emptyDataSet ); xd.Load( "file.xml" ); 215 _mapper.SetupMapping(this, _dataSet); 216 217 if (DocumentElement != null) 218 { 219 LoadDataSetFromTree(); 220 BindListeners(); 221 } 222 else if (fLoadFromDataSet) 223 { 224 _bLoadFromDataSet = true; 225 LoadTreeFromDataSet(DataSet); 226 BindListeners(); 227 } 228 229 _ignoreDataSetEvents = false; 230 _ignoreXmlEvents = false; 231 } 232 Bind(DataRow r, XmlBoundElement e)233 internal void Bind(DataRow r, XmlBoundElement e) 234 { 235 r.Element = e; 236 e.Row = r; 237 } 238 239 // Binds special listeners to catch the 1st data-row created. When the 1st DataRow is created, XmlDataDocument will automatically bind all regular listeners. BindSpecialListeners()240 private void BindSpecialListeners() 241 { 242 Debug.Assert(_fDataRowCreatedSpecial == false); 243 Debug.Assert(_fBoundToDataSet == false && _fBoundToDocument == false); 244 _dataSet.DataRowCreated += new DataRowCreatedEventHandler(OnDataRowCreatedSpecial); 245 _fDataRowCreatedSpecial = true; 246 } UnBindSpecialListeners()247 private void UnBindSpecialListeners() 248 { 249 Debug.Assert(_fDataRowCreatedSpecial == true); 250 _dataSet.DataRowCreated -= new DataRowCreatedEventHandler(OnDataRowCreatedSpecial); 251 _fDataRowCreatedSpecial = false; 252 } 253 BindListeners()254 private void BindListeners() 255 { 256 BindToDocument(); 257 BindToDataSet(); 258 } 259 BindToDataSet()260 private void BindToDataSet() 261 { 262 // We could be already bound to DataSet in this scenario: 263 // xd = new XmlDataDocument( dataSetThatHasNoData ); xd.Load( "foo.xml" ); 264 // so we must not rebound again to it. 265 if (_fBoundToDataSet) 266 { 267 Debug.Assert(_dataSet != null); 268 return; 269 } 270 271 // Unregister the DataRowCreatedSpecial notification 272 if (_fDataRowCreatedSpecial) 273 UnBindSpecialListeners(); 274 275 _dataSet.Tables.CollectionChanging += new CollectionChangeEventHandler(OnDataSetTablesChanging); 276 _dataSet.Relations.CollectionChanging += new CollectionChangeEventHandler(OnDataSetRelationsChanging); 277 _dataSet.DataRowCreated += new DataRowCreatedEventHandler(OnDataRowCreated); 278 _dataSet.PropertyChanging += new PropertyChangedEventHandler(OnDataSetPropertyChanging); 279 280 //this is the hack for this release, should change it in the future 281 _dataSet.ClearFunctionCalled += new DataSetClearEventhandler(OnClearCalled); 282 283 if (_dataSet.Tables.Count > 0) 284 { 285 foreach (DataTable t in _dataSet.Tables) 286 { 287 BindToTable(t); 288 } 289 } 290 291 foreach (DataRelation rel in _dataSet.Relations) 292 { 293 rel.PropertyChanging += new PropertyChangedEventHandler(OnRelationPropertyChanging); 294 } 295 _fBoundToDataSet = true; 296 } 297 BindToDocument()298 private void BindToDocument() 299 { 300 if (!_fBoundToDocument) 301 { 302 NodeInserting += new XmlNodeChangedEventHandler(OnNodeInserting); 303 NodeInserted += new XmlNodeChangedEventHandler(OnNodeInserted); 304 NodeRemoving += new XmlNodeChangedEventHandler(OnNodeRemoving); 305 NodeRemoved += new XmlNodeChangedEventHandler(OnNodeRemoved); 306 NodeChanging += new XmlNodeChangedEventHandler(OnNodeChanging); 307 NodeChanged += new XmlNodeChangedEventHandler(OnNodeChanged); 308 _fBoundToDocument = true; 309 } 310 } 311 BindToTable(DataTable t)312 private void BindToTable(DataTable t) 313 { 314 t.ColumnChanged += new DataColumnChangeEventHandler(OnColumnChanged); 315 t.RowChanging += new DataRowChangeEventHandler(OnRowChanging); 316 t.RowChanged += new DataRowChangeEventHandler(OnRowChanged); 317 t.RowDeleting += new DataRowChangeEventHandler(OnRowChanging); 318 t.RowDeleted += new DataRowChangeEventHandler(OnRowChanged); 319 t.PropertyChanging += new PropertyChangedEventHandler(OnTablePropertyChanging); 320 t.Columns.CollectionChanging += new CollectionChangeEventHandler(OnTableColumnsChanging); 321 322 foreach (DataColumn col in t.Columns) 323 { 324 // Hook column properties changes, so we can react properly to ROM changes. 325 col.PropertyChanging += new PropertyChangedEventHandler(OnColumnPropertyChanging); 326 } 327 } 328 329 /// <summary> 330 /// Creates an element with the specified Prefix, LocalName, and 331 /// NamespaceURI. 332 /// </summary> CreateElement(string prefix, string localName, string namespaceURI)333 public override XmlElement CreateElement(string prefix, string localName, string namespaceURI) 334 { 335 // There are three states for the document: 336 // - special listeners ON, no permananent listeners: this is when the data doc was created w/o any dataset, and the 1st time a new row/element 337 // is created we should subscribe the permenent listeners. 338 // - special listeners OFF, permanent listeners ON: this is when the data doc is loaded (from dataset or XML file) and synchronization takes place. 339 // - special listeners OFF, permanent listeners OFF: this is then the data doc is LOADING (from dataset or XML file) - the synchronization is done by code, 340 // not based on listening to events. 341 #if DEBUG 342 // Cannot have both special and permananent listeners ON 343 if (_fDataRowCreatedSpecial) 344 Debug.Assert((_fBoundToDataSet == false) && (_fBoundToDocument == false)); 345 // fBoundToDataSet and fBoundToDocument should have the same value 346 Debug.Assert(_fBoundToDataSet ? _fBoundToDocument : (!_fBoundToDocument)); 347 #endif 348 if (prefix == null) 349 prefix = string.Empty; 350 if (namespaceURI == null) 351 namespaceURI = string.Empty; 352 353 if (!_fAssociateDataRow) 354 { 355 // Loading state: create just the XmlBoundElement: the LoadTreeFromDataSet/LoadDataSetFromTree will take care of synchronization 356 return new XmlBoundElement(prefix, localName, namespaceURI, this); 357 } 358 359 // This is the 1st time an element is beeing created on an empty XmlDataDocument - unbind special listeners, bind permanent ones and then go on w/ 360 // creation of this element 361 EnsurePopulatedMode(); 362 Debug.Assert(_fDataRowCreatedSpecial == false); 363 364 // Loaded state: create a DataRow, this in turn will create and associate the XmlBoundElement, which we will return. 365 DataTable dt = _mapper.SearchMatchingTableSchema(localName, namespaceURI); 366 if (dt != null) 367 { 368 DataRow row = dt.CreateEmptyRow(); 369 // We need to make sure all fields are DBNull 370 foreach (DataColumn col in dt.Columns) 371 { 372 if (col.ColumnMapping != MappingType.Hidden) 373 SetRowValueToNull(row, col); 374 } 375 XmlBoundElement be = row.Element; 376 Debug.Assert(be != null); 377 be.Prefix = prefix; 378 return be; 379 } 380 // No matching table schema for this element: just create the element 381 return new XmlBoundElement(prefix, localName, namespaceURI, this); 382 } 383 CreateEntityReference(string name)384 public override XmlEntityReference CreateEntityReference(string name) 385 { 386 throw new NotSupportedException(SR.DataDom_NotSupport_EntRef); 387 } 388 389 /// <summary> 390 /// Gets a DataSet that provides a relational representation of the data in this 391 /// XmlDataDocument. 392 /// </summary> 393 public DataSet DataSet 394 { 395 get 396 { 397 return _dataSet; 398 } 399 } 400 DefoliateRegion(XmlBoundElement rowElem)401 private void DefoliateRegion(XmlBoundElement rowElem) 402 { 403 // You must pass a row element (which s/b associated w/ a DataRow) 404 Debug.Assert(rowElem.Row != null); 405 406 if (!_optimizeStorage) 407 return; 408 409 if (rowElem.ElementState != ElementState.WeakFoliation) 410 return; 411 412 if (!_mapper.IsRegionRadical(rowElem)) 413 { 414 return; 415 } 416 417 bool saveIgnore = IgnoreXmlEvents; 418 IgnoreXmlEvents = true; 419 420 rowElem.ElementState = ElementState.Defoliating; 421 422 try 423 { 424 // drop all attributes 425 rowElem.RemoveAllAttributes(); 426 427 XmlNode node = rowElem.FirstChild; 428 while (node != null) 429 { 430 XmlNode next = node.NextSibling; 431 432 XmlBoundElement be = node as XmlBoundElement; 433 if (be != null && be.Row != null) 434 break; 435 436 // The node must be mapped to a column (since the region is radically structured) 437 Debug.Assert(_mapper.GetColumnSchemaForNode(rowElem, node) != null); 438 rowElem.RemoveChild(node); 439 440 node = next; 441 } 442 #if DEBUG 443 // All subsequent siblings must be sub-regions 444 for (; node != null; node = node.NextSibling) 445 { 446 Debug.Assert((node is XmlBoundElement) && (((XmlBoundElement)node).Row != null)); 447 } 448 #endif 449 450 rowElem.ElementState = ElementState.Defoliated; 451 } 452 finally 453 { 454 IgnoreXmlEvents = saveIgnore; 455 } 456 } 457 EnsureDocumentElement()458 private XmlElement EnsureDocumentElement() 459 { 460 XmlElement docelem = DocumentElement; 461 if (docelem == null) 462 { 463 string docElemName = XmlConvert.EncodeLocalName(DataSet.DataSetName); 464 if (docElemName == null || docElemName.Length == 0) 465 docElemName = "Xml"; 466 string ns = DataSet.Namespace; 467 if (ns == null) 468 ns = string.Empty; 469 docelem = new XmlBoundElement(string.Empty, docElemName, ns, this); 470 AppendChild(docelem); 471 } 472 473 return docelem; 474 } EnsureNonRowDocumentElement()475 private XmlElement EnsureNonRowDocumentElement() 476 { 477 XmlElement docElem = DocumentElement; 478 if (docElem == null) 479 return EnsureDocumentElement(); 480 481 DataRow rowDocElem = GetRowFromElement(docElem); 482 if (rowDocElem == null) 483 return docElem; 484 485 return DemoteDocumentElement(); 486 } DemoteDocumentElement()487 private XmlElement DemoteDocumentElement() 488 { 489 // Changes of Xml here should not affect ROM 490 Debug.Assert(_ignoreXmlEvents == true); 491 // There should be no reason to call this function if docElem is not a rowElem 492 Debug.Assert(GetRowFromElement(DocumentElement) != null); 493 494 // Remove the DocumentElement and create a new one 495 XmlElement oldDocElem = DocumentElement; 496 RemoveChild(oldDocElem); 497 XmlElement docElem = EnsureDocumentElement(); 498 docElem.AppendChild(oldDocElem); 499 // We should have only one child now 500 Debug.Assert(docElem.LastChild == docElem.FirstChild); 501 return docElem; 502 } 503 // This function ensures that the special listeners are un-subscribed, the permanent listeners are subscribed and 504 // CreateElement will attach DataRows to newly created XmlBoundElement. 505 // It should be called when we have special listeners hooked and we need to change from the special-listeners mode to the 506 // populated/permanenet mode where all listeners are correctly hooked up and the mapper is correctly set-up. EnsurePopulatedMode()507 private void EnsurePopulatedMode() 508 { 509 // Unbind special listeners, bind permanent ones, setup the mapping, etc 510 #if DEBUG 511 bool fDataRowCreatedSpecialOld = _fDataRowCreatedSpecial; 512 bool fAssociateDataRowOld = _fAssociateDataRow; 513 #endif 514 if (_fDataRowCreatedSpecial) 515 { 516 UnBindSpecialListeners(); 517 // If a special listener was ON, we should not have had an already set-up mapper or permanent listeners subscribed 518 Debug.Assert(!_mapper.IsMapped()); 519 Debug.Assert(!_fBoundToDocument); 520 Debug.Assert(!_fBoundToDataSet); 521 522 _mapper.SetupMapping(this, _dataSet); 523 BindListeners(); 524 525 // CreateElement should now create associate DataRows w/ new XmlBoundElement nodes 526 // We should do this ONLY if we switch from special listeners to permanent listeners. The reason is 527 // that DataDocumentNavigator wants to put XmlDataDocument in a batch mode, where CreateElement will just 528 // create a XmlBoundElement (see DataDocumentNavigator.CloneTree) 529 _fAssociateDataRow = true; 530 } 531 532 Debug.Assert(_fDataRowCreatedSpecial == false); 533 Debug.Assert(_mapper.IsMapped()); 534 Debug.Assert(_fBoundToDataSet && _fBoundToDocument); 535 #if DEBUG 536 // In case we EnsurePopulatedMode was called on an already populated mode, we should NOT change fAssociateDataRow 537 if (fDataRowCreatedSpecialOld == false) 538 Debug.Assert(fAssociateDataRowOld == _fAssociateDataRow); 539 #endif 540 } 541 542 // Move regions that are marked in ROM as nested children of row/rowElement as last children in XML fragment FixNestedChildren(DataRow row, XmlElement rowElement)543 private void FixNestedChildren(DataRow row, XmlElement rowElement) 544 { 545 foreach (DataRelation dr in GetNestedChildRelations(row)) 546 { 547 foreach (DataRow r in row.GetChildRows(dr)) 548 { 549 XmlElement childElem = r.Element; 550 // childElem can be null when we create XML from DataSet (XmlDataDocument( DataSet ) is called) and we insert rowElem of the parentRow before 551 // we insert the rowElem of children rows. 552 if (childElem != null) 553 { 554 #if DEBUG 555 bool fIsChildConnected = IsConnected(childElem); 556 #endif 557 if (childElem.ParentNode != rowElement) 558 { 559 childElem.ParentNode.RemoveChild(childElem); 560 rowElement.AppendChild(childElem); 561 } 562 #if DEBUG 563 // We should not have changed the connected/disconnected state of the node (since the row state did not change) 564 Debug.Assert(fIsChildConnected == IsConnected(childElem)); 565 Debug.Assert(IsRowLive(r) ? IsConnected(childElem) : !IsConnected(childElem)); 566 #endif 567 } 568 } 569 } 570 } 571 572 // This function accepts node params that are not row-elements. In this case, calling this function is a no-op Foliate(XmlBoundElement node, ElementState newState)573 internal void Foliate(XmlBoundElement node, ElementState newState) 574 { 575 Debug.Assert(newState == ElementState.WeakFoliation || newState == ElementState.StrongFoliation); 576 #if DEBUG 577 // If we want to strong foliate one of the non-row-elem in a region, then the region MUST be strong-foliated (or there must be no region) 578 // Do this only when we are not loading 579 if (IsFoliationEnabled) 580 { 581 if (newState == ElementState.StrongFoliation && node.Row == null) 582 { 583 XmlBoundElement rowElem; 584 ElementState rowElemState = ElementState.None; 585 if (_mapper.GetRegion(node, out rowElem)) 586 { 587 rowElemState = rowElem.ElementState; 588 Debug.Assert(rowElemState == ElementState.StrongFoliation || rowElemState == ElementState.WeakFoliation); 589 } 590 // Add a no-op, so we can still debug in the assert fails 591 592 #pragma warning disable 1717 // assignment to self 593 rowElemState = rowElemState; 594 #pragma warning restore 1717 595 } 596 } 597 #endif 598 599 if (IsFoliationEnabled) 600 { 601 if (node.ElementState == ElementState.Defoliated) 602 { 603 ForceFoliation(node, newState); 604 } 605 else if (node.ElementState == ElementState.WeakFoliation && newState == ElementState.StrongFoliation) 606 { 607 // Node must be a row-elem 608 Debug.Assert(node.Row != null); 609 node.ElementState = newState; 610 } 611 } 612 } 613 Foliate(XmlElement element)614 private void Foliate(XmlElement element) 615 { 616 if (element is XmlBoundElement) 617 ((XmlBoundElement)element).Foliate(ElementState.WeakFoliation); 618 } 619 620 // Foliate rowElement region if there are DataPointers that points into it FoliateIfDataPointers(DataRow row, XmlElement rowElement)621 private void FoliateIfDataPointers(DataRow row, XmlElement rowElement) 622 { 623 if (!IsFoliated(rowElement) && HasPointers(rowElement)) 624 { 625 bool wasFoliationEnabled = IsFoliationEnabled; 626 IsFoliationEnabled = true; 627 try 628 { 629 Foliate(rowElement); 630 } 631 finally 632 { 633 IsFoliationEnabled = wasFoliationEnabled; 634 } 635 } 636 } 637 EnsureFoliation(XmlBoundElement rowElem, ElementState foliation)638 private void EnsureFoliation(XmlBoundElement rowElem, ElementState foliation) 639 { 640 if (rowElem.IsFoliated) //perf reason, avoid unecessary lock. 641 return; 642 ForceFoliation(rowElem, foliation); 643 } 644 ForceFoliation(XmlBoundElement node, ElementState newState)645 private void ForceFoliation(XmlBoundElement node, ElementState newState) 646 { 647 lock (_foliationLock) 648 { 649 if (node.ElementState != ElementState.Defoliated) 650 // The region was foliated by an other thread while this thread was locked 651 return; 652 653 // Node must be a row-elem associated w/ a non-deleted row 654 Debug.Assert(node.Row != null); 655 Debug.Assert(node.Row.RowState != DataRowState.Deleted); 656 657 node.ElementState = ElementState.Foliating; 658 659 bool saveIgnore = IgnoreXmlEvents; 660 IgnoreXmlEvents = true; 661 662 try 663 { 664 XmlNode priorNode = null; 665 DataRow row = node.Row; 666 667 // create new attrs & elements for row 668 // For detached rows: we are in sync w/ temp values 669 // For non-detached rows: we are in sync w/ the current values 670 // For deleted rows: we never sync 671 DataRowVersion rowVersion = (row.RowState == DataRowState.Detached) ? DataRowVersion.Proposed : DataRowVersion.Current; 672 foreach (DataColumn col in row.Table.Columns) 673 { 674 if (!IsNotMapped(col)) 675 { 676 object value = row[col, rowVersion]; 677 678 if (!Convert.IsDBNull(value)) 679 { 680 if (col.ColumnMapping == MappingType.Attribute) 681 { 682 node.SetAttribute(col.EncodedColumnName, col.Namespace, col.ConvertObjectToXml(value)); 683 } 684 else 685 { 686 XmlNode newNode = null; 687 if (col.ColumnMapping == MappingType.Element) 688 { 689 newNode = new XmlBoundElement(string.Empty, col.EncodedColumnName, col.Namespace, this); 690 newNode.AppendChild(CreateTextNode(col.ConvertObjectToXml(value))); 691 if (priorNode != null) 692 { 693 node.InsertAfter(newNode, priorNode); 694 } 695 else if (node.FirstChild != null) 696 { 697 node.InsertBefore(newNode, node.FirstChild); 698 } 699 else 700 { 701 node.AppendChild(newNode); 702 } 703 priorNode = newNode; 704 } 705 else 706 { 707 Debug.Assert(col.ColumnMapping == MappingType.SimpleContent); 708 newNode = CreateTextNode(col.ConvertObjectToXml(value)); 709 if (node.FirstChild != null) 710 node.InsertBefore(newNode, node.FirstChild); 711 else 712 node.AppendChild(newNode); 713 if (priorNode == null) 714 priorNode = newNode; 715 } 716 } 717 } 718 else 719 { 720 if (col.ColumnMapping == MappingType.SimpleContent) 721 { 722 XmlAttribute attr = CreateAttribute(XSI, Keywords.XSI_NIL, Keywords.XSINS); 723 attr.Value = Keywords.TRUE; 724 node.SetAttributeNode(attr); 725 _bHasXSINIL = true; 726 } 727 } 728 } 729 } 730 } 731 finally 732 { 733 IgnoreXmlEvents = saveIgnore; 734 node.ElementState = newState; 735 } 736 // update all live pointers 737 OnFoliated(node); 738 } 739 } 740 741 //Determine best radical insert position for inserting column elements GetColumnInsertAfterLocation(DataRow row, DataColumn col, XmlBoundElement rowElement)742 private XmlNode GetColumnInsertAfterLocation(DataRow row, DataColumn col, XmlBoundElement rowElement) 743 { 744 XmlNode prev = null; 745 XmlNode node = null; 746 747 // text only columns appear first 748 if (IsTextOnly(col)) 749 return null; 750 751 // insert location must be after free text 752 for (node = rowElement.FirstChild; node != null; prev = node, node = node.NextSibling) 753 { 754 if (!IsTextLikeNode(node)) 755 break; 756 } 757 758 for (; node != null; prev = node, node = node.NextSibling) 759 { 760 // insert location must be before any non-element nodes 761 if (node.NodeType != XmlNodeType.Element) 762 break; 763 XmlElement e = node as XmlElement; 764 765 // insert location must be before any non-mapped elements or separate regions 766 if (_mapper.GetRowFromElement(e) != null) 767 break; 768 769 object schema = _mapper.GetColumnSchemaForNode(rowElement, node); 770 if (schema == null || !(schema is DataColumn)) 771 break; 772 773 // insert location must be before any columns logically after this column 774 if (((DataColumn)schema).Ordinal > col.Ordinal) 775 break; 776 } 777 778 return prev; 779 } 780 GetNestedChildRelations(DataRow row)781 private ArrayList GetNestedChildRelations(DataRow row) 782 { 783 ArrayList list = new ArrayList(); 784 785 foreach (DataRelation r in row.Table.ChildRelations) 786 { 787 if (r.Nested) 788 list.Add(r); 789 } 790 791 return list; 792 } 793 GetNestedParent(DataRow row)794 private DataRow GetNestedParent(DataRow row) 795 { 796 DataRelation relation = GetNestedParentRelation(row); 797 if (relation != null) 798 return row.GetParentRow(relation); 799 return null; 800 } 801 GetNestedParentRelation(DataRow row)802 private static DataRelation GetNestedParentRelation(DataRow row) 803 { 804 DataRelation[] relations = row.Table.NestedParentRelations; 805 if (relations.Length == 0) 806 return null; 807 return relations[0]; 808 } 809 GetTextOnlyColumn(DataRow row)810 private DataColumn GetTextOnlyColumn(DataRow row) 811 { 812 #if DEBUG 813 { 814 // Make sure there is at most only one text column, and the text column (if present) is the one reported by row.Table.XmlText 815 DataColumnCollection columns = row.Table.Columns; 816 int cCols = columns.Count; 817 int cTextCols = 0; 818 for (int iCol = 0; iCol < cCols; iCol++) 819 { 820 DataColumn c = columns[iCol]; 821 if (IsTextOnly(c)) 822 { 823 Debug.Assert(c == row.Table.XmlText); 824 ++cTextCols; 825 } 826 } 827 Debug.Assert(cTextCols == 0 || cTextCols == 1); 828 if (cTextCols == 0) 829 Debug.Assert(row.Table.XmlText == null); 830 } 831 #endif 832 return row.Table.XmlText; 833 } 834 835 /// <summary> 836 /// Retrieves the DataRow associated with the specified XmlElement. 837 /// </summary> GetRowFromElement(XmlElement e)838 public DataRow GetRowFromElement(XmlElement e) 839 { 840 return _mapper.GetRowFromElement(e); 841 } 842 GetRowInsertBeforeLocation(DataRow row, XmlElement rowElement, XmlNode parentElement)843 private XmlNode GetRowInsertBeforeLocation(DataRow row, XmlElement rowElement, XmlNode parentElement) 844 { 845 DataRow refRow = row; 846 int i = 0; 847 int pos; 848 849 // Find position 850 // int pos = row.Table.Rows[row]; 851 for (i = 0; i < row.Table.Rows.Count; i++) 852 if (row == row.Table.Rows[i]) 853 break; 854 pos = i; 855 856 DataRow parentRow = GetNestedParent(row); 857 for (i = pos + 1; i < row.Table.Rows.Count; i++) 858 { 859 refRow = row.Table.Rows[i]; 860 if (GetNestedParent(refRow) == parentRow && GetElementFromRow(refRow).ParentNode == parentElement) 861 break; 862 } 863 864 if (i < row.Table.Rows.Count) 865 return GetElementFromRow(refRow); 866 else 867 return null; 868 } 869 870 871 /// <summary> 872 /// Retrieves the XmlElement associated with the specified DataRow. 873 /// </summary> GetElementFromRow(DataRow r)874 public XmlElement GetElementFromRow(DataRow r) 875 { 876 XmlBoundElement be = r.Element; 877 Debug.Assert(be != null); 878 return be; 879 } 880 HasPointers(XmlNode node)881 internal bool HasPointers(XmlNode node) 882 { 883 while (true) 884 { 885 try 886 { 887 if (_pointers.Count > 0) 888 { 889 object pointer = null; 890 foreach (DictionaryEntry entry in _pointers) 891 { 892 pointer = entry.Value; 893 Debug.Assert(pointer != null); 894 if (((IXmlDataVirtualNode)pointer).IsOnNode(node)) 895 return true; 896 } 897 } 898 return false; 899 } 900 catch (Exception e) when (Data.Common.ADP.IsCatchableExceptionType(e)) 901 { 902 // This can happens only when some threads are creating navigators (thus modifying this.pointers) while other threads are in the foreach loop. 903 } 904 } 905 //should never get to this point due to while (true) loop 906 } 907 908 internal bool IgnoreXmlEvents 909 { 910 get { return _ignoreXmlEvents; } 911 set { _ignoreXmlEvents = value; } 912 } 913 914 internal bool IgnoreDataSetEvents 915 { 916 get { return _ignoreDataSetEvents; } 917 set { _ignoreDataSetEvents = value; } 918 } 919 IsFoliated(XmlElement element)920 private bool IsFoliated(XmlElement element) 921 { 922 if (element is XmlBoundElement) 923 { 924 return ((XmlBoundElement)element).IsFoliated; 925 } 926 927 return true; 928 } IsFoliated(XmlBoundElement be)929 private bool IsFoliated(XmlBoundElement be) 930 { 931 return be.IsFoliated; 932 } 933 934 internal bool IsFoliationEnabled 935 { 936 get { return _isFoliationEnabled; } 937 set { _isFoliationEnabled = value; } 938 } 939 940 // This creates a tree and synchronize ROM w/ the created tree. 941 // It requires the populated mode to be on - in case we are not in populated mode, it will make the XmlDataDocument be in populated mode. 942 // It takes advantage of the fAssociateDataRow flag for populated mode, which allows creation of XmlBoundElement w/o associating DataRow objects. CloneTree(DataPointer other)943 internal XmlNode CloneTree(DataPointer other) 944 { 945 EnsurePopulatedMode(); 946 947 bool oldIgnoreDataSetEvents = _ignoreDataSetEvents; 948 bool oldIgnoreXmlEvents = _ignoreXmlEvents; 949 bool oldFoliationEnabled = IsFoliationEnabled; 950 bool oldAssociateDataRow = _fAssociateDataRow; 951 952 // Caller should ensure that the EnforceConstraints == false. See 60486 for more info about why this was changed from DataSet.EnforceConstraints = false to an assert. 953 Debug.Assert(DataSet.EnforceConstraints == false); 954 XmlNode newNode; 955 956 try 957 { 958 _ignoreDataSetEvents = true; 959 _ignoreXmlEvents = true; 960 IsFoliationEnabled = false; 961 _fAssociateDataRow = false; 962 963 // Create the diconnected tree based on the other navigator 964 newNode = CloneTreeInternal(other); 965 Debug.Assert(newNode != null); 966 967 // Synchronize DataSet from XML 968 LoadRows(null, newNode); 969 SyncRows(null, newNode, false); 970 } 971 finally 972 { 973 _ignoreDataSetEvents = oldIgnoreDataSetEvents; 974 _ignoreXmlEvents = oldIgnoreXmlEvents; 975 IsFoliationEnabled = oldFoliationEnabled; 976 _fAssociateDataRow = oldAssociateDataRow; 977 } 978 return newNode; 979 } 980 CloneTreeInternal(DataPointer other)981 private XmlNode CloneTreeInternal(DataPointer other) 982 { 983 Debug.Assert(_ignoreDataSetEvents == true); 984 Debug.Assert(_ignoreXmlEvents == true); 985 Debug.Assert(IsFoliationEnabled == false); 986 987 // Create the diconnected tree based on the other navigator 988 XmlNode newNode = CloneNode(other); 989 990 DataPointer dp = new DataPointer(other); 991 try 992 { 993 dp.AddPointer(); 994 if (newNode.NodeType == XmlNodeType.Element) 995 { 996 int cAttributes = dp.AttributeCount; 997 for (int i = 0; i < cAttributes; i++) 998 { 999 dp.MoveToOwnerElement(); 1000 if (dp.MoveToAttribute(i)) 1001 { 1002 newNode.Attributes.Append((XmlAttribute)CloneTreeInternal(dp)); 1003 } 1004 } 1005 1006 dp.MoveTo(other); 1007 } 1008 1009 for (bool fMore = dp.MoveToFirstChild(); fMore; fMore = dp.MoveToNextSibling()) 1010 newNode.AppendChild(CloneTreeInternal(dp)); 1011 } 1012 finally 1013 { 1014 dp.SetNoLongerUse(); 1015 } 1016 1017 return newNode; 1018 } 1019 CloneNode(bool deep)1020 public override XmlNode CloneNode(bool deep) 1021 { 1022 XmlDataDocument clone = (XmlDataDocument)(base.CloneNode(false)); 1023 clone.Init(DataSet.Clone()); 1024 1025 clone._dataSet.EnforceConstraints = _dataSet.EnforceConstraints; 1026 Debug.Assert(clone.FirstChild == null); 1027 if (deep) 1028 { 1029 DataPointer dp = new DataPointer(this, this); 1030 try 1031 { 1032 dp.AddPointer(); 1033 for (bool fMore = dp.MoveToFirstChild(); fMore; fMore = dp.MoveToNextSibling()) 1034 { 1035 XmlNode cloneNode; 1036 if (dp.NodeType == XmlNodeType.Element) 1037 cloneNode = clone.CloneTree(dp); 1038 else 1039 cloneNode = clone.CloneNode(dp); 1040 clone.AppendChild(cloneNode); 1041 } 1042 } 1043 finally 1044 { 1045 dp.SetNoLongerUse(); 1046 } 1047 } 1048 1049 return clone; 1050 } 1051 CloneNode(DataPointer dp)1052 private XmlNode CloneNode(DataPointer dp) 1053 { 1054 switch (dp.NodeType) 1055 { 1056 //for the nodes without value and have no children 1057 case XmlNodeType.DocumentFragment: 1058 return CreateDocumentFragment(); 1059 case XmlNodeType.DocumentType: 1060 return CreateDocumentType(dp.Name, dp.PublicId, dp.SystemId, dp.InternalSubset); 1061 case XmlNodeType.XmlDeclaration: 1062 return CreateXmlDeclaration(dp.Version, dp.Encoding, dp.Standalone); 1063 1064 //for the nodes with value but no children 1065 case XmlNodeType.Text: 1066 return CreateTextNode(dp.Value); 1067 case XmlNodeType.CDATA: 1068 return CreateCDataSection(dp.Value); 1069 case XmlNodeType.ProcessingInstruction: 1070 return CreateProcessingInstruction(dp.Name, dp.Value); 1071 case XmlNodeType.Comment: 1072 return CreateComment(dp.Value); 1073 case XmlNodeType.Whitespace: 1074 return CreateWhitespace(dp.Value); 1075 case XmlNodeType.SignificantWhitespace: 1076 return CreateSignificantWhitespace(dp.Value); 1077 //for the nodes that don't have values, but might have children -- only clone the node and leave the children untouched 1078 case XmlNodeType.Element: 1079 return CreateElement(dp.Prefix, dp.LocalName, dp.NamespaceURI); 1080 case XmlNodeType.Attribute: 1081 return CreateAttribute(dp.Prefix, dp.LocalName, dp.NamespaceURI); 1082 case XmlNodeType.EntityReference: 1083 return CreateEntityReference(dp.Name); 1084 } 1085 throw new InvalidOperationException(SR.Format(SR.DataDom_CloneNode, dp.NodeType.ToString())); 1086 } 1087 IsTextLikeNode(XmlNode n)1088 internal static bool IsTextLikeNode(XmlNode n) 1089 { 1090 switch (n.NodeType) 1091 { 1092 case XmlNodeType.Text: 1093 case XmlNodeType.CDATA: 1094 case XmlNodeType.Whitespace: 1095 case XmlNodeType.SignificantWhitespace: 1096 return true; 1097 1098 case XmlNodeType.EntityReference: 1099 Debug.Assert(false); 1100 return false; 1101 1102 default: 1103 return false; 1104 } 1105 } 1106 IsNotMapped(DataColumn c)1107 internal bool IsNotMapped(DataColumn c) 1108 { 1109 return DataSetMapper.IsNotMapped(c); 1110 } 1111 IsSame(DataColumn c, int recNo1, int recNo2)1112 private bool IsSame(DataColumn c, int recNo1, int recNo2) 1113 { 1114 if (c.Compare(recNo1, recNo2) == 0) 1115 return true; 1116 1117 return false; 1118 } 1119 IsTextOnly(DataColumn c)1120 internal bool IsTextOnly(DataColumn c) 1121 { 1122 return c.ColumnMapping == MappingType.SimpleContent; 1123 } 1124 1125 1126 /// <summary> 1127 /// Loads the XML document from the specified file. 1128 /// </summary> Load(string filename)1129 public override void Load(string filename) 1130 { 1131 _bForceExpandEntity = true; 1132 base.Load(filename); 1133 _bForceExpandEntity = false; 1134 } 1135 1136 /// <summary> 1137 /// Loads the XML document from the specified Stream. 1138 /// </summary> Load(Stream inStream)1139 public override void Load(Stream inStream) 1140 { 1141 _bForceExpandEntity = true; 1142 base.Load(inStream); 1143 _bForceExpandEntity = false; 1144 } 1145 1146 /// <summary> 1147 /// Loads the XML document from the specified TextReader. 1148 /// </summary> Load(TextReader txtReader)1149 public override void Load(TextReader txtReader) 1150 { 1151 _bForceExpandEntity = true; 1152 base.Load(txtReader); 1153 _bForceExpandEntity = false; 1154 } 1155 1156 /// <summary> 1157 /// Loads the XML document from the specified XmlReader. 1158 /// </summary> Load(XmlReader reader)1159 public override void Load(XmlReader reader) 1160 { 1161 if (FirstChild != null) 1162 throw new InvalidOperationException(SR.DataDom_MultipleLoad); 1163 1164 try 1165 { 1166 _ignoreXmlEvents = true; 1167 1168 // Unhook the DataRowCreatedSpecial listener, since we no longer base on the first created DataRow to do the Bind 1169 if (_fDataRowCreatedSpecial) 1170 UnBindSpecialListeners(); 1171 1172 // We should NOT create DataRow objects when calling XmlDataDocument.CreateElement 1173 _fAssociateDataRow = false; 1174 // Foliation s/b disabled 1175 _isFoliationEnabled = false; 1176 1177 //now if we load from file we need to set the ExpandEntity flag to ExpandEntities 1178 if (_bForceExpandEntity) 1179 { 1180 Debug.Assert(reader is XmlTextReader); 1181 ((XmlTextReader)reader).EntityHandling = EntityHandling.ExpandEntities; 1182 } 1183 base.Load(reader); 1184 BindForLoad(); 1185 } 1186 finally 1187 { 1188 _ignoreXmlEvents = false; 1189 _isFoliationEnabled = true; 1190 _autoFoliationState = ElementState.StrongFoliation; 1191 _fAssociateDataRow = true; 1192 } 1193 } 1194 LoadDataSetFromTree()1195 private void LoadDataSetFromTree() 1196 { 1197 _ignoreDataSetEvents = true; 1198 _ignoreXmlEvents = true; 1199 bool wasFoliationEnabled = IsFoliationEnabled; 1200 IsFoliationEnabled = false; 1201 bool saveEnforce = _dataSet.EnforceConstraints; 1202 _dataSet.EnforceConstraints = false; 1203 1204 try 1205 { 1206 Debug.Assert(DocumentElement != null); 1207 LoadRows(null, DocumentElement); 1208 SyncRows(null, DocumentElement, true); 1209 1210 _dataSet.EnforceConstraints = saveEnforce; 1211 } 1212 finally 1213 { 1214 _ignoreDataSetEvents = false; 1215 _ignoreXmlEvents = false; 1216 IsFoliationEnabled = wasFoliationEnabled; 1217 } 1218 } 1219 LoadTreeFromDataSet(DataSet ds)1220 private void LoadTreeFromDataSet(DataSet ds) 1221 { 1222 _ignoreDataSetEvents = true; 1223 _ignoreXmlEvents = true; 1224 bool wasFoliationEnabled = IsFoliationEnabled; 1225 IsFoliationEnabled = false; 1226 _fAssociateDataRow = false; 1227 1228 DataTable[] orderedTables = OrderTables(ds); 1229 // problem is after we add support for Namespace for DataTable, when infering we do not guarantee that table would be 1230 // in the same sequence that they were in XML because of namespace, some would be on different schema, so since they 1231 // won't be in the same sequence as in XML, we may end up with having a child table, before its parent (which is not doable 1232 // with XML; and this happend because they are in different namespace) 1233 // this kind of problems are known and please see comment in "OnNestedParentChange" 1234 // so to fix it in general, we try to iterate over ordered tables instead of going over all tables in DataTableCollection with their own sequence 1235 1236 try 1237 { 1238 for (int i = 0; i < orderedTables.Length; i++) 1239 { 1240 DataTable t = orderedTables[i]; 1241 foreach (DataRow r in t.Rows) 1242 { 1243 Debug.Assert(r.Element == null); 1244 XmlBoundElement rowElem = AttachBoundElementToDataRow(r); 1245 1246 switch (r.RowState) 1247 { 1248 case DataRowState.Added: 1249 case DataRowState.Unchanged: 1250 case DataRowState.Modified: 1251 OnAddRow(r); 1252 break; 1253 case DataRowState.Deleted: 1254 // Nothing to do (the row already has an associated element as a fragment 1255 break; 1256 case DataRowState.Detached: 1257 // We should not get rows in this state 1258 Debug.Assert(false); 1259 break; 1260 default: 1261 // Unknown row state 1262 Debug.Assert(false); 1263 break; 1264 } 1265 } 1266 } 1267 } 1268 finally 1269 { 1270 _ignoreDataSetEvents = false; 1271 _ignoreXmlEvents = false; 1272 IsFoliationEnabled = wasFoliationEnabled; 1273 _fAssociateDataRow = true; 1274 } 1275 } 1276 1277 // load all data from tree structre into datarows LoadRows(XmlBoundElement rowElem, XmlNode node)1278 private void LoadRows(XmlBoundElement rowElem, XmlNode node) 1279 { 1280 Debug.Assert(node != null); 1281 1282 XmlBoundElement be = node as XmlBoundElement; 1283 if (be != null) 1284 { 1285 DataTable dt = _mapper.SearchMatchingTableSchema(rowElem, be); 1286 1287 if (dt != null) 1288 { 1289 DataRow r = GetRowFromElement(be); 1290 Debug.Assert(r == null); 1291 // If the rowElement was just created and has an un-initialized 1292 if (be.ElementState == ElementState.None) 1293 be.ElementState = ElementState.WeakFoliation; 1294 r = dt.CreateEmptyRow(); 1295 Bind(r, be); 1296 1297 // the region rowElem is now be 1298 Debug.Assert(be.Row != null); 1299 rowElem = be; 1300 } 1301 } 1302 // recurse down for children 1303 for (XmlNode child = node.FirstChild; child != null; child = child.NextSibling) 1304 LoadRows(rowElem, child); 1305 } 1306 1307 internal DataSetMapper Mapper 1308 { 1309 get 1310 { 1311 return _mapper; 1312 } 1313 } 1314 OnDataRowCreated(object oDataSet, DataRow row)1315 internal void OnDataRowCreated(object oDataSet, DataRow row) 1316 { 1317 Debug.Assert(row.RowState == DataRowState.Detached); 1318 OnNewRow(row); 1319 } 1320 OnClearCalled(object oDataSet, DataTable table)1321 internal void OnClearCalled(object oDataSet, DataTable table) 1322 { 1323 throw new NotSupportedException(SR.DataDom_NotSupport_Clear); 1324 } 1325 OnDataRowCreatedSpecial(object oDataSet, DataRow row)1326 internal void OnDataRowCreatedSpecial(object oDataSet, DataRow row) 1327 { 1328 Debug.Assert(row.RowState == DataRowState.Detached); 1329 1330 // Register the regular events and un-register this one 1331 Bind(true); 1332 // Pass the event to the regular listener 1333 OnNewRow(row); 1334 } 1335 // Called when a new DataRow is created OnNewRow(DataRow row)1336 internal void OnNewRow(DataRow row) 1337 { 1338 Debug.Assert(row.Element == null); 1339 // Allow New state also because we are calling this function from 1340 Debug.Assert(row.RowState == DataRowState.Detached); 1341 1342 AttachBoundElementToDataRow(row); 1343 } 1344 AttachBoundElementToDataRow(DataRow row)1345 private XmlBoundElement AttachBoundElementToDataRow(DataRow row) 1346 { 1347 Debug.Assert(row.Element == null); 1348 DataTable table = row.Table; 1349 // We shoould NOT call CreateElement here, since CreateElement will create and attach a new DataRow to the element 1350 XmlBoundElement rowElement = new XmlBoundElement(string.Empty, table.EncodedTableName, table.Namespace, this); 1351 rowElement.IsEmpty = false; 1352 Bind(row, rowElement); 1353 rowElement.ElementState = ElementState.Defoliated; 1354 return rowElement; 1355 } 1356 NeedXSI_NilAttr(DataRow row)1357 private bool NeedXSI_NilAttr(DataRow row) 1358 { 1359 DataTable tb = row.Table; 1360 Debug.Assert(tb != null); 1361 if (tb._xmlText == null) 1362 return false; 1363 object value = row[tb._xmlText]; 1364 return (Convert.IsDBNull(value)); 1365 } 1366 OnAddRow(DataRow row)1367 private void OnAddRow(DataRow row) 1368 { 1369 // Xml operations in this func should not trigger ROM operations 1370 Debug.Assert(_ignoreXmlEvents == true); 1371 1372 XmlBoundElement rowElement = (XmlBoundElement)(GetElementFromRow(row)); 1373 Debug.Assert(rowElement != null); 1374 1375 if (NeedXSI_NilAttr(row) && !rowElement.IsFoliated) 1376 //we need to foliate it because we need to add one more attribute xsi:nil = true; 1377 ForceFoliation(rowElement, AutoFoliationState); 1378 1379 Debug.Assert(rowElement != null); 1380 DataRow rowDocElem = GetRowFromElement(DocumentElement); 1381 if (rowDocElem != null) 1382 { 1383 DataRow parentRow = GetNestedParent(row); 1384 if (parentRow == null) 1385 DemoteDocumentElement(); 1386 } 1387 EnsureDocumentElement().AppendChild(rowElement); 1388 1389 // Move the children of the row under 1390 FixNestedChildren(row, rowElement); 1391 OnNestedParentChange(row, rowElement, null); 1392 } 1393 OnColumnValueChanged(DataRow row, DataColumn col, XmlBoundElement rowElement)1394 private void OnColumnValueChanged(DataRow row, DataColumn col, XmlBoundElement rowElement) 1395 { 1396 if (IsNotMapped(col)) 1397 { 1398 goto lblDoNestedRelationSync; 1399 } 1400 1401 object value = row[col]; 1402 1403 if (col.ColumnMapping == MappingType.SimpleContent && Convert.IsDBNull(value) && !rowElement.IsFoliated) 1404 { 1405 ForceFoliation(rowElement, ElementState.WeakFoliation); 1406 } 1407 else 1408 { 1409 // no need to sync if not foliated 1410 if (!IsFoliated(rowElement)) 1411 { 1412 #if DEBUG 1413 // If the new value is null, we should be already foliated if there is a DataPointer that points to the column 1414 // (see OnRowChanging, case DataRowAction.Change) 1415 if (Convert.IsDBNull(row[col, DataRowVersion.Current])) 1416 { 1417 try 1418 { 1419 if (_pointers.Count > 0) 1420 { 1421 object pointer = null; 1422 foreach (DictionaryEntry entry in _pointers) 1423 { 1424 pointer = entry.Value; 1425 Debug.Assert((pointer != null) && !((IXmlDataVirtualNode)pointer).IsOnColumn(col)); 1426 } 1427 } 1428 } 1429 catch (Exception e) when (Data.Common.ADP.IsCatchableExceptionType(e)) 1430 { 1431 // We may get an exception if we are in foreach and a new pointer has been added to this.pointers. When this happens, we will skip this check and ignore the exceptions 1432 } 1433 } 1434 #endif 1435 goto lblDoNestedRelationSync; 1436 } 1437 } 1438 1439 if (IsTextOnly(col)) 1440 { 1441 if (Convert.IsDBNull(value)) 1442 { 1443 value = string.Empty; 1444 //make sure that rowElement has Attribute xsi:nil and its value is true 1445 XmlAttribute attr = rowElement.GetAttributeNode(XSI_NIL); 1446 if (attr == null) 1447 { 1448 attr = CreateAttribute(XSI, Keywords.XSI_NIL, Keywords.XSINS); 1449 attr.Value = Keywords.TRUE; 1450 rowElement.SetAttributeNode(attr); 1451 _bHasXSINIL = true; 1452 } 1453 else 1454 attr.Value = Keywords.TRUE; 1455 } 1456 else 1457 { 1458 //make sure that if rowElement has Attribute xsi:nil, its value is false 1459 XmlAttribute attr = rowElement.GetAttributeNode(XSI_NIL); 1460 if (attr != null) 1461 attr.Value = Keywords.FALSE; 1462 } 1463 ReplaceInitialChildText(rowElement, col.ConvertObjectToXml(value)); 1464 goto lblDoNestedRelationSync; 1465 } 1466 1467 // update the attribute that maps to the column 1468 bool fFound = false; 1469 1470 // Find the field node and set it's value 1471 if (col.ColumnMapping == MappingType.Attribute) 1472 { 1473 foreach (XmlAttribute attr in rowElement.Attributes) 1474 { 1475 if (attr.LocalName == col.EncodedColumnName && attr.NamespaceURI == col.Namespace) 1476 { 1477 if (Convert.IsDBNull(value)) 1478 { 1479 attr.OwnerElement.Attributes.Remove(attr); 1480 } 1481 else 1482 { 1483 attr.Value = col.ConvertObjectToXml(value); 1484 } 1485 fFound = true; 1486 break; 1487 } 1488 } 1489 1490 // create new attribute if we didn't find one. 1491 if (!fFound && !Convert.IsDBNull(value)) 1492 { 1493 rowElement.SetAttribute(col.EncodedColumnName, col.Namespace, col.ConvertObjectToXml(value)); 1494 } 1495 } 1496 else 1497 { 1498 // update elements that map to the column... 1499 RegionIterator iter = new RegionIterator(rowElement); 1500 bool fMore = iter.Next(); 1501 while (fMore) 1502 { 1503 if (iter.CurrentNode.NodeType == XmlNodeType.Element) 1504 { 1505 XmlElement e = (XmlElement)iter.CurrentNode; 1506 Debug.Assert(e != null); 1507 //we should skip the subregion 1508 XmlBoundElement be = e as XmlBoundElement; 1509 if (be != null && be.Row != null) 1510 { 1511 fMore = iter.NextRight(); //skip over the sub-region 1512 continue; 1513 } 1514 if (e.LocalName == col.EncodedColumnName && e.NamespaceURI == col.Namespace) 1515 { 1516 fFound = true; 1517 if (Convert.IsDBNull(value)) 1518 { 1519 PromoteNonValueChildren(e); 1520 fMore = iter.NextRight(); 1521 e.ParentNode.RemoveChild(e); 1522 // keep looking for more matching elements 1523 continue; 1524 } 1525 else 1526 { 1527 ReplaceInitialChildText(e, col.ConvertObjectToXml(value)); 1528 //make sure that if the Element has Attribute xsi:nil, its value is false 1529 XmlAttribute attr = e.GetAttributeNode(XSI_NIL); 1530 if (attr != null) 1531 attr.Value = Keywords.FALSE; 1532 // no need to look any further. 1533 goto lblDoNestedRelationSync; 1534 } 1535 } 1536 } 1537 fMore = iter.Next(); 1538 } 1539 1540 // create new element if we didn't find one. 1541 if (!fFound && !Convert.IsDBNull(value)) 1542 { 1543 XmlElement newElem = new XmlBoundElement(string.Empty, col.EncodedColumnName, col.Namespace, this); 1544 newElem.AppendChild(CreateTextNode(col.ConvertObjectToXml(value))); 1545 1546 XmlNode elemBefore = GetColumnInsertAfterLocation(row, col, rowElement); 1547 if (elemBefore != null) 1548 { 1549 rowElement.InsertAfter(newElem, elemBefore); 1550 } 1551 else if (rowElement.FirstChild != null) 1552 { 1553 rowElement.InsertBefore(newElem, rowElement.FirstChild); 1554 } 1555 else 1556 { 1557 rowElement.AppendChild(newElem); 1558 } 1559 } 1560 } 1561 lblDoNestedRelationSync: 1562 // Change the XML to conform to the (potentially) change in parent nested relation 1563 DataRelation relation = GetNestedParentRelation(row); 1564 if (relation != null) 1565 { 1566 Debug.Assert(relation.ChildTable == row.Table); 1567 if (relation.ChildKey.ContainsColumn(col)) 1568 OnNestedParentChange(row, rowElement, col); 1569 } 1570 } 1571 OnColumnChanged(object sender, DataColumnChangeEventArgs args)1572 private void OnColumnChanged(object sender, DataColumnChangeEventArgs args) 1573 { 1574 // You should not be able to make DataRow field changes if the DataRow is deleted 1575 Debug.Assert(args.Row.RowState != DataRowState.Deleted); 1576 1577 if (_ignoreDataSetEvents) 1578 return; 1579 1580 bool wasIgnoreXmlEvents = _ignoreXmlEvents; 1581 _ignoreXmlEvents = true; 1582 bool wasFoliationEnabled = IsFoliationEnabled; 1583 IsFoliationEnabled = false; 1584 1585 try 1586 { 1587 DataRow row = args.Row; 1588 DataColumn col = args.Column; 1589 object oVal = args.ProposedValue; 1590 1591 if (row.RowState == DataRowState.Detached) 1592 { 1593 XmlBoundElement be = row.Element; 1594 Debug.Assert(be != null); 1595 if (be.IsFoliated) 1596 { 1597 // Need to sync changes from ROM to DOM 1598 OnColumnValueChanged(row, col, be); 1599 } 1600 } 1601 } 1602 finally 1603 { 1604 IsFoliationEnabled = wasFoliationEnabled; 1605 _ignoreXmlEvents = wasIgnoreXmlEvents; 1606 } 1607 } 1608 OnColumnValuesChanged(DataRow row, XmlBoundElement rowElement)1609 private void OnColumnValuesChanged(DataRow row, XmlBoundElement rowElement) 1610 { 1611 Debug.Assert(row != null); 1612 Debug.Assert(rowElement != null); 1613 1614 // If user has cascading relationships, then columnChangeList will contains the changed columns only for the last row beeing cascaded 1615 // but there will be multiple ROM events 1616 if (_columnChangeList.Count > 0) 1617 { 1618 if (((DataColumn)(_columnChangeList[0])).Table == row.Table) 1619 { 1620 foreach (DataColumn c in _columnChangeList) 1621 OnColumnValueChanged(row, c, rowElement); 1622 } 1623 else 1624 { 1625 foreach (DataColumn c in row.Table.Columns) 1626 OnColumnValueChanged(row, c, rowElement); 1627 } 1628 } 1629 else 1630 { 1631 foreach (DataColumn c in row.Table.Columns) 1632 OnColumnValueChanged(row, c, rowElement); 1633 } 1634 _columnChangeList.Clear(); 1635 } 1636 OnDeleteRow(DataRow row, XmlBoundElement rowElement)1637 private void OnDeleteRow(DataRow row, XmlBoundElement rowElement) 1638 { 1639 // IgnoreXmlEvents s/b on since we are manipulating the XML tree and we not want this to reflect in ROM view. 1640 Debug.Assert(_ignoreXmlEvents == true); 1641 // Special case when rowElem is document element: we create a new docElem, move the current one as a child of 1642 // the new created docElem, then process as if the docElem is not a rowElem 1643 if (rowElement == DocumentElement) 1644 DemoteDocumentElement(); 1645 1646 PromoteInnerRegions(rowElement); 1647 rowElement.ParentNode.RemoveChild(rowElement); 1648 } 1649 OnDeletingRow(DataRow row, XmlBoundElement rowElement)1650 private void OnDeletingRow(DataRow row, XmlBoundElement rowElement) 1651 { 1652 // Note that this function is beeing called even if ignoreDataSetEvents == true. 1653 1654 // Foliate, so we can be able to preserve the nodes even if the DataRow has no longer values for the crtRecord. 1655 if (IsFoliated(rowElement)) 1656 return; 1657 1658 bool wasIgnoreXmlEvents = IgnoreXmlEvents; 1659 IgnoreXmlEvents = true; 1660 bool wasFoliationEnabled = IsFoliationEnabled; 1661 IsFoliationEnabled = true; 1662 try 1663 { 1664 Foliate(rowElement); 1665 } 1666 finally 1667 { 1668 IsFoliationEnabled = wasFoliationEnabled; 1669 IgnoreXmlEvents = wasIgnoreXmlEvents; 1670 } 1671 } 1672 OnFoliated(XmlNode node)1673 private void OnFoliated(XmlNode node) 1674 { 1675 while (true) 1676 { 1677 try 1678 { 1679 if (_pointers.Count > 0) 1680 { 1681 foreach (DictionaryEntry entry in _pointers) 1682 { 1683 object pointer = entry.Value; 1684 Debug.Assert(pointer != null); 1685 ((IXmlDataVirtualNode)pointer).OnFoliated(node); 1686 } 1687 } 1688 return; 1689 } 1690 catch (Exception e) when (Data.Common.ADP.IsCatchableExceptionType(e)) 1691 { 1692 // This can happens only when some threads are creating navigators (thus modifying this.pointers) while other threads are in the foreach loop. 1693 // Solution is to re-try OnFoliated. 1694 } 1695 } 1696 // You should never get here in regular cases 1697 } 1698 FindAssociatedParentColumn(DataRelation relation, DataColumn childCol)1699 private DataColumn FindAssociatedParentColumn(DataRelation relation, DataColumn childCol) 1700 { 1701 DataColumn[] columns = relation.ChildKey.ColumnsReference; 1702 for (int i = 0; i < columns.Length; i++) 1703 { 1704 if (childCol == columns[i]) 1705 return relation.ParentKey.ColumnsReference[i]; 1706 } 1707 return null; 1708 } 1709 1710 // Change the childElement position in the tree to conform to the parent nested relationship in ROM OnNestedParentChange(DataRow child, XmlBoundElement childElement, DataColumn childCol)1711 private void OnNestedParentChange(DataRow child, XmlBoundElement childElement, DataColumn childCol) 1712 { 1713 Debug.Assert(child.Element == childElement && childElement.Row == child); 1714 // This function is (and s/b) called as a result of ROM changes, therefore XML changes done here should not be sync-ed to ROM 1715 Debug.Assert(_ignoreXmlEvents == true); 1716 #if DEBUG 1717 // In order to check that this move does not change the connected/disconnected state of the node 1718 bool fChildElementConnected = IsConnected(childElement); 1719 #endif 1720 DataRow parentRowInTree; 1721 if (childElement == DocumentElement || childElement.ParentNode == null) 1722 parentRowInTree = null; 1723 else 1724 parentRowInTree = GetRowFromElement((XmlElement)childElement.ParentNode); 1725 DataRow parentRowInRelation = GetNestedParent(child); 1726 1727 if (parentRowInTree != parentRowInRelation) 1728 { 1729 if (parentRowInRelation != null) 1730 { 1731 XmlElement newParent = GetElementFromRow(parentRowInRelation); 1732 newParent.AppendChild(childElement); 1733 } 1734 else 1735 { 1736 // no parent? Maybe the parentRow is during changing or childCol is the ID is set to null ( detached from the parent row ). 1737 DataRelation relation = GetNestedParentRelation(child); 1738 if (childCol == null || relation == null || Convert.IsDBNull(child[childCol])) 1739 { 1740 EnsureNonRowDocumentElement().AppendChild(childElement); 1741 } 1742 else 1743 { 1744 DataColumn colInParent = FindAssociatedParentColumn(relation, childCol); 1745 Debug.Assert(colInParent != null); 1746 object comparedValue = colInParent.ConvertValue(child[childCol]); 1747 if (parentRowInTree._tempRecord != -1 && colInParent.CompareValueTo(parentRowInTree._tempRecord, comparedValue) != 0) 1748 { 1749 EnsureNonRowDocumentElement().AppendChild(childElement); 1750 } 1751 //else do nothing because its original parentRowInRelation will be changed so that this row will still be its child 1752 } 1753 } 1754 } 1755 #if DEBUG 1756 // We should not have changed the connected/disconnected state of the node (since the row state did not change) -- IOW if the original childElem was in dis-connected 1757 // state and corresponded to a detached/deleted row, by adding it to the main tree we become inconsistent (since we have now a deleted/detached row in the main tree) 1758 // Same goes when we remove a node from connected tree to make it a child of a row-node corresponding to a non-live row. 1759 Debug.Assert(fChildElementConnected == IsConnected(childElement)); 1760 Debug.Assert(IsRowLive(child) ? IsConnected(childElement) : !IsConnected(childElement)); 1761 #endif 1762 } 1763 OnNodeChanged(object sender, XmlNodeChangedEventArgs args)1764 private void OnNodeChanged(object sender, XmlNodeChangedEventArgs args) 1765 { 1766 if (_ignoreXmlEvents) 1767 return; 1768 1769 bool wasIgnoreDataSetEvents = _ignoreDataSetEvents; 1770 bool wasIgnoreXmlEvents = _ignoreXmlEvents; 1771 bool wasFoliationEnabled = IsFoliationEnabled; 1772 _ignoreDataSetEvents = true; 1773 _ignoreXmlEvents = true; 1774 IsFoliationEnabled = false; 1775 bool fEnableCascading = DataSet._fEnableCascading; 1776 DataSet._fEnableCascading = false; 1777 1778 try 1779 { 1780 // okay to allow text node value changes when bound. 1781 XmlBoundElement rowElement = null; 1782 1783 Debug.Assert(DataSet.EnforceConstraints == false); 1784 1785 if (_mapper.GetRegion(args.Node, out rowElement)) 1786 { 1787 SynchronizeRowFromRowElement(rowElement); 1788 } 1789 } 1790 finally 1791 { 1792 _ignoreDataSetEvents = wasIgnoreDataSetEvents; 1793 _ignoreXmlEvents = wasIgnoreXmlEvents; 1794 IsFoliationEnabled = wasFoliationEnabled; 1795 DataSet._fEnableCascading = fEnableCascading; 1796 } 1797 } 1798 OnNodeChanging(object sender, XmlNodeChangedEventArgs args)1799 private void OnNodeChanging(object sender, XmlNodeChangedEventArgs args) 1800 { 1801 if (_ignoreXmlEvents) 1802 return; 1803 if (DataSet.EnforceConstraints != false) 1804 throw new InvalidOperationException(SR.DataDom_EnforceConstraintsShouldBeOff); 1805 } 1806 1807 OnNodeInserted(object sender, XmlNodeChangedEventArgs args)1808 private void OnNodeInserted(object sender, XmlNodeChangedEventArgs args) 1809 { 1810 if (_ignoreXmlEvents) 1811 return; 1812 1813 bool wasIgnoreDataSetEvents = _ignoreDataSetEvents; 1814 bool wasIgnoreXmlEvents = _ignoreXmlEvents; 1815 bool wasFoliationEnabled = IsFoliationEnabled; 1816 _ignoreDataSetEvents = true; 1817 _ignoreXmlEvents = true; 1818 IsFoliationEnabled = false; 1819 1820 Debug.Assert(DataSet.EnforceConstraints == false); 1821 1822 bool fEnableCascading = DataSet._fEnableCascading; 1823 DataSet._fEnableCascading = false; 1824 1825 try 1826 { 1827 // Handle both new node inserted and 2nd part of a move operation. 1828 XmlNode node = args.Node; 1829 XmlNode oldParent = args.OldParent; 1830 XmlNode newParent = args.NewParent; 1831 1832 // The code bellow assumes a move operation is fired by DOM in 2 steps: a Remvoe followed by an Insert - this is the 2nd part, the Insert. 1833 Debug.Assert(oldParent == null); 1834 if (IsConnected(newParent)) 1835 { 1836 // Inserting a node to connected tree 1837 OnNodeInsertedInTree(node); 1838 } 1839 else 1840 { 1841 // Inserting a node to disconnected tree 1842 OnNodeInsertedInFragment(node); 1843 } 1844 } 1845 finally 1846 { 1847 _ignoreDataSetEvents = wasIgnoreDataSetEvents; 1848 _ignoreXmlEvents = wasIgnoreXmlEvents; 1849 IsFoliationEnabled = wasFoliationEnabled; 1850 DataSet._fEnableCascading = fEnableCascading; 1851 } 1852 } 1853 OnNodeInserting(object sender, XmlNodeChangedEventArgs args)1854 private void OnNodeInserting(object sender, XmlNodeChangedEventArgs args) 1855 { 1856 if (_ignoreXmlEvents) 1857 return; 1858 if (DataSet.EnforceConstraints != false) 1859 throw new InvalidOperationException(SR.DataDom_EnforceConstraintsShouldBeOff); 1860 } 1861 1862 OnNodeRemoved(object sender, XmlNodeChangedEventArgs args)1863 private void OnNodeRemoved(object sender, XmlNodeChangedEventArgs args) 1864 { 1865 if (_ignoreXmlEvents) 1866 return; 1867 1868 bool wasIgnoreDataSetEvents = _ignoreDataSetEvents; 1869 bool wasIgnoreXmlEvents = _ignoreXmlEvents; 1870 bool wasFoliationEnabled = IsFoliationEnabled; 1871 _ignoreDataSetEvents = true; 1872 _ignoreXmlEvents = true; 1873 IsFoliationEnabled = false; 1874 1875 Debug.Assert(DataSet.EnforceConstraints == false); 1876 1877 bool fEnableCascading = DataSet._fEnableCascading; 1878 DataSet._fEnableCascading = false; 1879 1880 try 1881 { 1882 XmlNode node = args.Node; 1883 XmlNode oldParent = args.OldParent; 1884 Debug.Assert(args.NewParent == null); 1885 1886 if (IsConnected(oldParent)) 1887 { 1888 // Removing from connected tree to disconnected tree 1889 OnNodeRemovedFromTree(node, oldParent); 1890 } 1891 else 1892 { 1893 // Removing from disconnected tree to disconnected tree: just sync the old region 1894 OnNodeRemovedFromFragment(node, oldParent); 1895 } 1896 } 1897 finally 1898 { 1899 _ignoreDataSetEvents = wasIgnoreDataSetEvents; 1900 _ignoreXmlEvents = wasIgnoreXmlEvents; 1901 IsFoliationEnabled = wasFoliationEnabled; 1902 DataSet._fEnableCascading = fEnableCascading; 1903 } 1904 } 1905 OnNodeRemoving(object sender, XmlNodeChangedEventArgs args)1906 private void OnNodeRemoving(object sender, XmlNodeChangedEventArgs args) 1907 { 1908 if (_ignoreXmlEvents) 1909 return; 1910 if (DataSet.EnforceConstraints != false) 1911 throw new InvalidOperationException(SR.DataDom_EnforceConstraintsShouldBeOff); 1912 } 1913 1914 // Node was removed from connected tree to disconnected tree OnNodeRemovedFromTree(XmlNode node, XmlNode oldParent)1915 private void OnNodeRemovedFromTree(XmlNode node, XmlNode oldParent) 1916 { 1917 XmlBoundElement oldRowElem; 1918 1919 // Synchronize values from old region 1920 if (_mapper.GetRegion(oldParent, out oldRowElem)) 1921 SynchronizeRowFromRowElement(oldRowElem); 1922 1923 // Disconnect all regions, starting w/ node (if it is a row-elem) 1924 XmlBoundElement rowElem = node as XmlBoundElement; 1925 if (rowElem != null && rowElem.Row != null) 1926 EnsureDisconnectedDataRow(rowElem); 1927 TreeIterator iter = new TreeIterator(node); 1928 for (bool fMore = iter.NextRowElement(); fMore; fMore = iter.NextRowElement()) 1929 { 1930 rowElem = (XmlBoundElement)(iter.CurrentNode); 1931 EnsureDisconnectedDataRow(rowElem); 1932 } 1933 1934 // Assert that all sub-regions are disconnected 1935 AssertNonLiveRows(node); 1936 } 1937 // Node was removed from the disconnected tree to disconnected tree OnNodeRemovedFromFragment(XmlNode node, XmlNode oldParent)1938 private void OnNodeRemovedFromFragment(XmlNode node, XmlNode oldParent) 1939 { 1940 XmlBoundElement oldRowElem; 1941 1942 if (_mapper.GetRegion(oldParent, out oldRowElem)) 1943 { 1944 // Sync the old region if it is not deleted 1945 DataRow row = oldRowElem.Row; 1946 // Since the old old region was disconnected, then the row can be only Deleted or Detached 1947 Debug.Assert(!IsRowLive(row)); 1948 if (oldRowElem.Row.RowState == DataRowState.Detached) 1949 SynchronizeRowFromRowElement(oldRowElem); 1950 } 1951 1952 // Need to set nested for the sub-regions (if node is a row-elem, we need to set it just for itself) 1953 XmlBoundElement be = node as XmlBoundElement; 1954 if (be != null && be.Row != null) 1955 { 1956 Debug.Assert(!IsRowLive(be.Row)); 1957 SetNestedParentRegion(be, null); 1958 } 1959 else 1960 { 1961 // Set nested parent to null for all child regions 1962 TreeIterator iter = new TreeIterator(node); 1963 for (bool fMore = iter.NextRowElement(); fMore; fMore = iter.NextRightRowElement()) 1964 { 1965 XmlBoundElement rowElemChild = (XmlBoundElement)(iter.CurrentNode); 1966 SetNestedParentRegion(rowElemChild, null); 1967 } 1968 } 1969 1970 // Assert that all sub-regions are disconnected 1971 AssertNonLiveRows(node); 1972 } 1973 1974 OnRowChanged(object sender, DataRowChangeEventArgs args)1975 private void OnRowChanged(object sender, DataRowChangeEventArgs args) 1976 { 1977 if (_ignoreDataSetEvents) 1978 return; 1979 1980 _ignoreXmlEvents = true; 1981 bool wasFoliationEnabled = IsFoliationEnabled; 1982 IsFoliationEnabled = false; 1983 1984 try 1985 { 1986 DataRow row = args.Row; 1987 XmlBoundElement rowElement = row.Element; 1988 // We should have an associated row-elem created when the DataRow was created (or at the load time) 1989 Debug.Assert(rowElement != null); 1990 1991 switch (args.Action) 1992 { 1993 case DataRowAction.Add: 1994 OnAddRow(row); 1995 break; 1996 1997 case DataRowAction.Delete: 1998 OnDeleteRow(row, rowElement); 1999 break; 2000 2001 case DataRowAction.Rollback: 2002 switch (_rollbackState) 2003 { 2004 case DataRowState.Deleted: 2005 OnUndeleteRow(row, rowElement); 2006 UpdateAllColumns(row, rowElement); 2007 break; 2008 2009 case DataRowState.Added: 2010 rowElement.ParentNode.RemoveChild(rowElement); 2011 break; 2012 2013 case DataRowState.Modified: 2014 OnColumnValuesChanged(row, rowElement); 2015 break; 2016 } 2017 break; 2018 2019 case DataRowAction.Change: 2020 OnColumnValuesChanged(row, rowElement); 2021 break; 2022 2023 case DataRowAction.Commit: 2024 if (row.RowState == DataRowState.Detached) 2025 { 2026 //by now, all the descendent of the element that is not of this region should have been promoted already 2027 rowElement.RemoveAll(); 2028 } 2029 break; 2030 default: 2031 break; 2032 } 2033 } 2034 finally 2035 { 2036 IsFoliationEnabled = wasFoliationEnabled; 2037 _ignoreXmlEvents = false; 2038 } 2039 } 2040 OnRowChanging(object sender, DataRowChangeEventArgs args)2041 private void OnRowChanging(object sender, DataRowChangeEventArgs args) 2042 { 2043 // We foliate the region each time the assocaited row gets deleted 2044 DataRow row = args.Row; 2045 if (args.Action == DataRowAction.Delete && row.Element != null) 2046 { 2047 OnDeletingRow(row, row.Element); 2048 return; 2049 } 2050 2051 if (_ignoreDataSetEvents) 2052 return; 2053 2054 bool wasFoliationEnabled = IsFoliationEnabled; 2055 IsFoliationEnabled = false; 2056 2057 try 2058 { 2059 _ignoreXmlEvents = true; 2060 2061 XmlElement rowElement = GetElementFromRow(row); 2062 2063 int nRec1 = -1; 2064 int nRec2 = -1; 2065 2066 if (rowElement != null) 2067 { 2068 switch (args.Action) 2069 { 2070 case DataRowAction.Add: 2071 // DataRow is beeing added to the table (Table.Rows.Add is beeing called) 2072 break; 2073 2074 case DataRowAction.Delete: 2075 // DataRow is beeing deleted 2076 // - state transition from New (AKA PendingInsert) to Detached (AKA Created) 2077 // - state transition from Unchanged to Deleted (AKA PendingDelete) 2078 // - state transition from Modified (AKA PendingChange) to Delete (AKA PendingDelete) 2079 Debug.Assert(false); // This should have been handled above, irrespective of ignoreDataSetEvents value (true or false) 2080 break; 2081 2082 case DataRowAction.Rollback: 2083 // DataRow gets reverted to previous values (by calling DataRow.RejectChanges): 2084 // - state transition from Detached (AKA Created) to Detached (AKA Created) 2085 // - state transition from New (AKA PendingInsert) to Detached (AKA Created) 2086 // - state transition from Modified (AKA PendingChange) to Unchanged 2087 // - state transition from Deleted (AKA PendingDelete) to Unchanged 2088 _rollbackState = row.RowState; 2089 switch (_rollbackState) 2090 { 2091 case DataRowState.Deleted: 2092 break; 2093 2094 case DataRowState.Detached: 2095 break; 2096 2097 case DataRowState.Added: 2098 break; 2099 2100 case DataRowState.Modified: 2101 _columnChangeList.Clear(); 2102 nRec1 = row.GetRecordFromVersion(DataRowVersion.Original); 2103 nRec2 = row.GetRecordFromVersion(DataRowVersion.Current); 2104 foreach (DataColumn c in row.Table.Columns) 2105 { 2106 if (!IsSame(c, nRec1, nRec2)) 2107 _columnChangeList.Add(c); 2108 } 2109 break; 2110 } 2111 break; 2112 2113 case DataRowAction.Change: 2114 // A DataRow field is beeing changed 2115 // - state transition from New (AKA PendingInsert) to New (AKA PendingInsert) 2116 // - state transition from Unchanged to Modified (AKA PendingChange) 2117 // - state transition from Modified (AKA PendingChange) to Modified (AKA PendingChange) 2118 _columnChangeList.Clear(); 2119 nRec1 = row.GetRecordFromVersion(DataRowVersion.Proposed); 2120 nRec2 = row.GetRecordFromVersion(DataRowVersion.Current); 2121 foreach (DataColumn c in row.Table.Columns) 2122 { 2123 object proposedValue = row[c, DataRowVersion.Proposed]; 2124 object currentValue = row[c, DataRowVersion.Current]; 2125 // Foliate if proposedValue is DBNull; this way the DataPointer objects will point to a disconnected fragment after 2126 // the DBNull value is beeing set 2127 if (Convert.IsDBNull(proposedValue) && !Convert.IsDBNull(currentValue)) 2128 { 2129 // Foliate only for non-hidden columns (since hidden cols are not represented in XML) 2130 if (c.ColumnMapping != MappingType.Hidden) 2131 FoliateIfDataPointers(row, rowElement); 2132 } 2133 if (!IsSame(c, nRec1, nRec2)) 2134 _columnChangeList.Add(c); 2135 } 2136 break; 2137 2138 case DataRowAction.Commit: 2139 break; 2140 } 2141 } 2142 } 2143 finally 2144 { 2145 _ignoreXmlEvents = false; 2146 IsFoliationEnabled = wasFoliationEnabled; 2147 } 2148 } 2149 OnDataSetPropertyChanging(object oDataSet, PropertyChangedEventArgs args)2150 private void OnDataSetPropertyChanging(object oDataSet, PropertyChangedEventArgs args) 2151 { 2152 if (args.PropertyName == "DataSetName") 2153 throw new InvalidOperationException(SR.DataDom_DataSetNameChange); 2154 } OnColumnPropertyChanging(object oColumn, PropertyChangedEventArgs args)2155 private void OnColumnPropertyChanging(object oColumn, PropertyChangedEventArgs args) 2156 { 2157 if (args.PropertyName == "ColumnName") 2158 throw new InvalidOperationException(SR.DataDom_ColumnNameChange); 2159 if (args.PropertyName == "Namespace") 2160 throw new InvalidOperationException(SR.DataDom_ColumnNamespaceChange); 2161 if (args.PropertyName == "ColumnMapping") 2162 throw new InvalidOperationException(SR.DataDom_ColumnMappingChange); 2163 } OnTablePropertyChanging(object oTable, PropertyChangedEventArgs args)2164 private void OnTablePropertyChanging(object oTable, PropertyChangedEventArgs args) 2165 { 2166 if (args.PropertyName == "TableName") 2167 throw new InvalidOperationException(SR.DataDom_TableNameChange); 2168 if (args.PropertyName == "Namespace") 2169 throw new InvalidOperationException(SR.DataDom_TableNamespaceChange); 2170 } OnTableColumnsChanging(object oColumnsCollection, CollectionChangeEventArgs args)2171 private void OnTableColumnsChanging(object oColumnsCollection, CollectionChangeEventArgs args) 2172 { 2173 // args.Action is one of CollectionChangeAction.Add, CollectionChangeAction.Remove or CollectionChangeAction.Refresh 2174 // args.Element is one of either the column (for Add and Remove actions or null, if the entire colection of columns is changing) 2175 2176 // Disallow changing the columns collection (since we are subscribed only in populated mode, we allow changes in any state but non-populated mode) 2177 throw new InvalidOperationException(SR.DataDom_TableColumnsChange); 2178 } 2179 OnDataSetTablesChanging(object oTablesCollection, CollectionChangeEventArgs args)2180 private void OnDataSetTablesChanging(object oTablesCollection, CollectionChangeEventArgs args) 2181 { 2182 // args.Action is one of CollectionChangeAction.Add, CollectionChangeAction.Remove or CollectionChangeAction.Refresh 2183 // args.Element is a table 2184 2185 // Disallow changing the tables collection (since we are subscribed only in populated mode, we allow changes in any state but non-populated mode) 2186 throw new InvalidOperationException(SR.DataDom_DataSetTablesChange); 2187 } 2188 OnDataSetRelationsChanging(object oRelationsCollection, CollectionChangeEventArgs args)2189 private void OnDataSetRelationsChanging(object oRelationsCollection, CollectionChangeEventArgs args) 2190 { 2191 // args.Action is one of CollectionChangeAction.Add, CollectionChangeAction.Remove or CollectionChangeAction.Refresh 2192 // args.Element is a DataRelation 2193 2194 // Disallow changing the tables collection if there is data loaded and there are nested relationship that are added/refreshed 2195 DataRelation rel = (DataRelation)(args.Element); 2196 if (rel != null && rel.Nested) 2197 throw new InvalidOperationException(SR.DataDom_DataSetNestedRelationsChange); 2198 2199 // If Add and Remove, we should already been throwing if .Nested == false 2200 Debug.Assert(!(args.Action == CollectionChangeAction.Add || args.Action == CollectionChangeAction.Remove) || rel.Nested == false); 2201 if (args.Action == CollectionChangeAction.Refresh) 2202 { 2203 foreach (DataRelation relTemp in (DataRelationCollection)oRelationsCollection) 2204 { 2205 if (relTemp.Nested) 2206 { 2207 throw new InvalidOperationException(SR.DataDom_DataSetNestedRelationsChange); 2208 } 2209 } 2210 } 2211 } 2212 OnRelationPropertyChanging(object oRelationsCollection, PropertyChangedEventArgs args)2213 private void OnRelationPropertyChanging(object oRelationsCollection, PropertyChangedEventArgs args) 2214 { 2215 if (args.PropertyName == "Nested") 2216 throw new InvalidOperationException(SR.DataDom_DataSetNestedRelationsChange); 2217 } 2218 OnUndeleteRow(DataRow row, XmlElement rowElement)2219 private void OnUndeleteRow(DataRow row, XmlElement rowElement) 2220 { 2221 XmlNode refRow; 2222 XmlElement parent; 2223 2224 // make certain we weren't place somewhere else. 2225 if (rowElement.ParentNode != null) 2226 rowElement.ParentNode.RemoveChild(rowElement); 2227 2228 // Find the parent of RowNode to be inserted 2229 DataRow parentRowInRelation = GetNestedParent(row); 2230 if (parentRowInRelation == null) 2231 { 2232 parent = EnsureNonRowDocumentElement(); 2233 } 2234 else 2235 parent = GetElementFromRow(parentRowInRelation); 2236 2237 if ((refRow = GetRowInsertBeforeLocation(row, rowElement, parent)) != null) 2238 parent.InsertBefore(rowElement, refRow); 2239 else 2240 parent.AppendChild(rowElement); 2241 2242 FixNestedChildren(row, rowElement); 2243 } 2244 2245 // Promote the rowElemChild node/region after prevSibling node (as the next sibling) PromoteChild(XmlNode child, XmlNode prevSibling)2246 private void PromoteChild(XmlNode child, XmlNode prevSibling) 2247 { 2248 // It makes no sense to move rowElemChild on the same level 2249 Debug.Assert(child.ParentNode != prevSibling.ParentNode); 2250 // prevSibling must have a parent, since we want to add a sibling to it 2251 Debug.Assert(prevSibling.ParentNode != null); 2252 Debug.Assert(IsFoliationEnabled == false); 2253 Debug.Assert(IgnoreXmlEvents == true); 2254 // Should not insert after docElem node 2255 Debug.Assert(prevSibling != DocumentElement); 2256 2257 if (child.ParentNode != null) 2258 child.ParentNode.RemoveChild(child); 2259 2260 Debug.Assert(child.ParentNode == null); 2261 prevSibling.ParentNode.InsertAfter(child, prevSibling); 2262 } 2263 2264 // Promote child regions under parent as next siblings of parent PromoteInnerRegions(XmlNode parent)2265 private void PromoteInnerRegions(XmlNode parent) 2266 { 2267 Debug.Assert(parent != null); 2268 Debug.Assert(parent.NodeType != XmlNodeType.Attribute); // We need to get get the grand-parent region 2269 Debug.Assert(parent != DocumentElement); // We cannot promote children of the DocumentElement 2270 2271 XmlNode prevSibling = parent; 2272 XmlBoundElement parentRegionRowElem; 2273 _mapper.GetRegion(parent.ParentNode, out parentRegionRowElem); 2274 2275 TreeIterator iter = new TreeIterator(parent); 2276 bool fMore = iter.NextRowElement(); 2277 while (fMore) 2278 { 2279 Debug.Assert(iter.CurrentNode is XmlBoundElement && ((XmlBoundElement)(iter.CurrentNode)).Row != null); 2280 XmlBoundElement rowElemChild = (XmlBoundElement)(iter.CurrentNode); 2281 fMore = iter.NextRightRowElement(); 2282 PromoteChild(rowElemChild, prevSibling); 2283 SetNestedParentRegion(rowElemChild, parentRegionRowElem); 2284 } 2285 } 2286 PromoteNonValueChildren(XmlNode parent)2287 private void PromoteNonValueChildren(XmlNode parent) 2288 { 2289 Debug.Assert(parent != null); 2290 XmlNode prevSibling = parent; 2291 XmlNode child = parent.FirstChild; 2292 bool bTextLikeNode = true; 2293 XmlNode nextSibling = null; 2294 while (child != null) 2295 { 2296 nextSibling = child.NextSibling; 2297 if (!bTextLikeNode || !IsTextLikeNode(child)) 2298 { 2299 bTextLikeNode = false; 2300 nextSibling = child.NextSibling; 2301 PromoteChild(child, prevSibling); 2302 prevSibling = child; 2303 } 2304 child = nextSibling; 2305 } 2306 } 2307 RemoveInitialTextNodes(XmlNode node)2308 private void RemoveInitialTextNodes(XmlNode node) 2309 { 2310 while (node != null && IsTextLikeNode(node)) 2311 { 2312 XmlNode sibling = node.NextSibling; 2313 node.ParentNode.RemoveChild(node); 2314 node = sibling; 2315 } 2316 } 2317 ReplaceInitialChildText(XmlNode parent, string value)2318 private void ReplaceInitialChildText(XmlNode parent, string value) 2319 { 2320 XmlNode n = parent.FirstChild; 2321 2322 // don't consider whitespace when replacing initial text 2323 while (n != null && n.NodeType == XmlNodeType.Whitespace) 2324 n = n.NextSibling; 2325 2326 if (n != null) 2327 { 2328 if (n.NodeType == XmlNodeType.Text) 2329 n.Value = value; 2330 else 2331 n = parent.InsertBefore(CreateTextNode(value), n); 2332 RemoveInitialTextNodes(n.NextSibling); 2333 } 2334 else 2335 { 2336 parent.AppendChild(CreateTextNode(value)); 2337 } 2338 } 2339 SafeFirstChild(XmlNode n)2340 internal XmlNode SafeFirstChild(XmlNode n) 2341 { 2342 XmlBoundElement be = n as XmlBoundElement; 2343 if (be != null) 2344 return be.SafeFirstChild; 2345 else 2346 //other type of node should be already foliated. 2347 return n.FirstChild; 2348 } 2349 SafeNextSibling(XmlNode n)2350 internal XmlNode SafeNextSibling(XmlNode n) 2351 { 2352 XmlBoundElement be = n as XmlBoundElement; 2353 if (be != null) 2354 return be.SafeNextSibling; 2355 else 2356 //other type of node should be already foliated. 2357 return n.NextSibling; 2358 } 2359 SafePreviousSibling(XmlNode n)2360 internal XmlNode SafePreviousSibling(XmlNode n) 2361 { 2362 XmlBoundElement be = n as XmlBoundElement; 2363 if (be != null) 2364 return be.SafePreviousSibling; 2365 else 2366 //other type of node should be already foliated. 2367 return n.PreviousSibling; 2368 } 2369 SetRowValueToNull(DataRow row, DataColumn col)2370 internal static void SetRowValueToNull(DataRow row, DataColumn col) 2371 { 2372 Debug.Assert(col.ColumnMapping != MappingType.Hidden); 2373 2374 if (!(row.IsNull(col))) 2375 { 2376 row[col] = DBNull.Value; 2377 } 2378 } 2379 SetRowValueFromXmlText(DataRow row, DataColumn col, string xmlText)2380 internal static void SetRowValueFromXmlText(DataRow row, DataColumn col, string xmlText) 2381 { 2382 Debug.Assert(xmlText != null); 2383 Debug.Assert(row.Table.DataSet.EnforceConstraints == false); 2384 object oVal; 2385 try 2386 { 2387 oVal = col.ConvertXmlToObject(xmlText); 2388 // This func does not set the field value to null - call SetRowValueToNull in order to do so 2389 Debug.Assert(oVal != null && !(oVal is DBNull)); 2390 } 2391 catch (Exception e) when (Data.Common.ADP.IsCatchableExceptionType(e)) 2392 { 2393 // Catch data-type errors and set ROM to Unspecified value 2394 SetRowValueToNull(row, col); 2395 return; 2396 } 2397 2398 if (!oVal.Equals(row[col])) 2399 row[col] = oVal; 2400 } 2401 SynchronizeRowFromRowElement(XmlBoundElement rowElement)2402 private void SynchronizeRowFromRowElement(XmlBoundElement rowElement) 2403 { 2404 SynchronizeRowFromRowElement(rowElement, null); 2405 } 2406 // Sync row fields w/ values from rowElem region. 2407 // If rowElemList is != null, all subregions of rowElem are appended to it. SynchronizeRowFromRowElement(XmlBoundElement rowElement, ArrayList rowElemList)2408 private void SynchronizeRowFromRowElement(XmlBoundElement rowElement, ArrayList rowElemList) 2409 { 2410 DataRow row = rowElement.Row; 2411 Debug.Assert(row != null); 2412 2413 // No synchronization needed for deleted rows 2414 if (row.RowState == DataRowState.Deleted) 2415 return; 2416 2417 row.BeginEdit(); 2418 #if DEBUG 2419 try 2420 { 2421 #endif 2422 SynchronizeRowFromRowElementEx(rowElement, rowElemList); 2423 #if DEBUG 2424 } 2425 catch 2426 { 2427 // We should not get any exceptions because we always handle data-type conversion 2428 Debug.Assert(false); 2429 throw; 2430 } 2431 #endif 2432 #if DEBUG 2433 try 2434 { 2435 #endif 2436 row.EndEdit(); 2437 #if DEBUG 2438 } 2439 catch 2440 { 2441 // We should not get any exceptions because DataSet.EnforceConstraints should be always off 2442 Debug.Assert(false); 2443 throw; 2444 } 2445 #endif 2446 } SynchronizeRowFromRowElementEx(XmlBoundElement rowElement, ArrayList rowElemList)2447 private void SynchronizeRowFromRowElementEx(XmlBoundElement rowElement, ArrayList rowElemList) 2448 { 2449 Debug.Assert(rowElement != null); 2450 Debug.Assert(rowElement.Row != null); 2451 Debug.Assert(DataSet.EnforceConstraints == false); 2452 2453 DataRow row = rowElement.Row; 2454 Debug.Assert(row != null); 2455 DataTable table = row.Table; 2456 2457 Hashtable foundColumns = new Hashtable(); 2458 string xsi_attrVal = string.Empty; 2459 2460 RegionIterator iter = new RegionIterator(rowElement); 2461 bool fMore; 2462 // If present, fill up the TextOnly column 2463 DataColumn column = GetTextOnlyColumn(row); 2464 if (column != null) 2465 { 2466 foundColumns[column] = column; 2467 string value; 2468 fMore = iter.NextInitialTextLikeNodes(out value); 2469 if (value.Length == 0 && (((xsi_attrVal = rowElement.GetAttribute(XSI_NIL)) == "1") || xsi_attrVal == "true")) 2470 row[column] = DBNull.Value; 2471 else 2472 SetRowValueFromXmlText(row, column, value); 2473 } 2474 else 2475 fMore = iter.Next(); 2476 2477 // Fill up the columns mapped to an element 2478 while (fMore) 2479 { 2480 XmlElement e = iter.CurrentNode as XmlElement; 2481 if (e == null) 2482 { 2483 fMore = iter.Next(); 2484 continue; 2485 } 2486 2487 XmlBoundElement be = e as XmlBoundElement; 2488 if (be != null && be.Row != null) 2489 { 2490 if (rowElemList != null) 2491 rowElemList.Add(e); 2492 // Skip over sub-regions 2493 fMore = iter.NextRight(); 2494 continue; 2495 } 2496 2497 DataColumn c = _mapper.GetColumnSchemaForNode(rowElement, e); 2498 if (c != null) 2499 { 2500 Debug.Assert(c.Table == row.Table); 2501 if (foundColumns[c] == null) 2502 { 2503 foundColumns[c] = c; 2504 string value; 2505 fMore = iter.NextInitialTextLikeNodes(out value); 2506 if (value.Length == 0 && (((xsi_attrVal = e.GetAttribute(XSI_NIL)) == "1") || xsi_attrVal == "true")) 2507 row[c] = DBNull.Value; 2508 else 2509 SetRowValueFromXmlText(row, c, value); 2510 continue; 2511 } 2512 } 2513 2514 fMore = iter.Next(); 2515 } 2516 2517 // 2518 // Walk the attributes to find attributes that map to columns. 2519 // 2520 foreach (XmlAttribute attr in rowElement.Attributes) 2521 { 2522 DataColumn c = _mapper.GetColumnSchemaForNode(rowElement, attr); 2523 2524 if (c != null) 2525 { 2526 if (foundColumns[c] == null) 2527 { 2528 foundColumns[c] = c; 2529 SetRowValueFromXmlText(row, c, attr.Value); 2530 } 2531 } 2532 } 2533 2534 // Null all columns values that aren't represented in the tree 2535 foreach (DataColumn c in row.Table.Columns) 2536 { 2537 if (foundColumns[c] == null && !IsNotMapped(c)) 2538 { 2539 if (!c.AutoIncrement) 2540 SetRowValueToNull(row, c); 2541 else 2542 c.Init(row._tempRecord); 2543 } 2544 } 2545 } 2546 UpdateAllColumns(DataRow row, XmlBoundElement rowElement)2547 private void UpdateAllColumns(DataRow row, XmlBoundElement rowElement) 2548 { 2549 foreach (DataColumn c in row.Table.Columns) 2550 { 2551 OnColumnValueChanged(row, c, rowElement); 2552 } 2553 } 2554 2555 /// <summary> 2556 /// Initializes a new instance of the XmlDataDocument class. 2557 /// </summary> XmlDataDocument()2558 public XmlDataDocument() : base(new XmlDataImplementation()) 2559 { 2560 Init(); 2561 AttachDataSet(new DataSet()); 2562 _dataSet.EnforceConstraints = false; 2563 } 2564 2565 /// <summary> 2566 /// Initializes a new instance of the XmlDataDocument class with the specified 2567 /// DataSet. 2568 /// </summary> XmlDataDocument(DataSet dataset)2569 public XmlDataDocument(DataSet dataset) : base(new XmlDataImplementation()) 2570 { 2571 Init(dataset); 2572 } 2573 XmlDataDocument(XmlImplementation imp)2574 internal XmlDataDocument(XmlImplementation imp) : base(imp) 2575 { 2576 } 2577 Init()2578 private void Init() 2579 { 2580 _pointers = new Hashtable(); 2581 _countAddPointer = 0; 2582 _columnChangeList = new ArrayList(); 2583 _ignoreDataSetEvents = false; 2584 _isFoliationEnabled = true; 2585 _optimizeStorage = true; 2586 _fDataRowCreatedSpecial = false; 2587 _autoFoliationState = ElementState.StrongFoliation; 2588 _fAssociateDataRow = true; //this needs to be true for newly created elements should have associated datarows 2589 _mapper = new DataSetMapper(); 2590 _foliationLock = new object(); 2591 _ignoreXmlEvents = true; 2592 _attrXml = CreateAttribute("xmlns", "xml", XPathNodePointer.StrReservedXmlns); 2593 _attrXml.Value = XPathNodePointer.StrReservedXml; 2594 _ignoreXmlEvents = false; 2595 } 2596 Init(DataSet ds)2597 private void Init(DataSet ds) 2598 { 2599 if (ds == null) 2600 throw new ArgumentException(SR.DataDom_DataSetNull); 2601 Init(); 2602 if (ds.FBoundToDocument) 2603 throw new ArgumentException(SR.DataDom_MultipleDataSet); 2604 ds.FBoundToDocument = true; 2605 _dataSet = ds; 2606 Bind(true); 2607 } 2608 IsConnected(XmlNode node)2609 private bool IsConnected(XmlNode node) 2610 { 2611 while (true) 2612 { 2613 if (node == null) 2614 return false; 2615 if (node == this) 2616 return true; 2617 2618 XmlAttribute attr = node as XmlAttribute; 2619 if (attr != null) 2620 node = attr.OwnerElement; 2621 else 2622 node = node.ParentNode; 2623 } 2624 } IsRowLive(DataRow row)2625 private bool IsRowLive(DataRow row) 2626 { 2627 return (row.RowState & (DataRowState.Added | DataRowState.Unchanged | DataRowState.Modified)) != 0; 2628 } SetNestedParentRow(DataRow childRow, DataRow parentRow)2629 private static void SetNestedParentRow(DataRow childRow, DataRow parentRow) 2630 { 2631 DataRelation rel = GetNestedParentRelation(childRow); 2632 //we should not set this row's parentRow if the table doesn't match. 2633 if (rel != null) 2634 { 2635 if (parentRow == null || rel.ParentKey.Table != parentRow.Table) 2636 childRow.SetParentRow(null, rel); 2637 else 2638 childRow.SetParentRow(parentRow, rel); 2639 } 2640 } 2641 2642 // A node (node) was inserted into the main tree (connected) from oldParent==null state OnNodeInsertedInTree(XmlNode node)2643 private void OnNodeInsertedInTree(XmlNode node) 2644 { 2645 XmlBoundElement be; 2646 ArrayList rowElemList = new ArrayList(); 2647 if (_mapper.GetRegion(node, out be)) 2648 { 2649 if (be == node) 2650 { 2651 OnRowElementInsertedInTree(be, rowElemList); 2652 } 2653 else 2654 { 2655 OnNonRowElementInsertedInTree(node, be, rowElemList); 2656 } 2657 } 2658 else 2659 { 2660 // We only need to sync the embedded sub-regions 2661 TreeIterator iter = new TreeIterator(node); 2662 for (bool fMore = iter.NextRowElement(); fMore; fMore = iter.NextRightRowElement()) 2663 rowElemList.Add(iter.CurrentNode); 2664 } 2665 2666 // Process subregions, so they make transition from disconnected to connected tree 2667 while (rowElemList.Count > 0) 2668 { 2669 Debug.Assert(rowElemList[0] != null && rowElemList[0] is XmlBoundElement); 2670 XmlBoundElement subRowElem = (XmlBoundElement)(rowElemList[0]); 2671 rowElemList.RemoveAt(0); 2672 // Expect rowElem to have a DataTable schema, since it is a sub-region 2673 Debug.Assert(subRowElem != null); 2674 OnRowElementInsertedInTree(subRowElem, rowElemList); 2675 } 2676 2677 // Assert that all sub-regions are assoc w/ "live" rows 2678 AssertLiveRows(node); 2679 } 2680 // "node" was inserting into a disconnected tree from oldParent==null state OnNodeInsertedInFragment(XmlNode node)2681 private void OnNodeInsertedInFragment(XmlNode node) 2682 { 2683 XmlBoundElement be; 2684 if (_mapper.GetRegion(node, out be)) 2685 { 2686 if (be == node) 2687 { 2688 Debug.Assert(!IsRowLive(be.Row)); 2689 SetNestedParentRegion(be); 2690 } 2691 else 2692 { 2693 ArrayList rowElemList = new ArrayList(); 2694 OnNonRowElementInsertedInFragment(node, be, rowElemList); 2695 // Set nested parent for the 1st level subregions (they should already be associated w/ Deleted or Detached rows) 2696 while (rowElemList.Count > 0) 2697 { 2698 Debug.Assert(rowElemList[0] != null && rowElemList[0] is XmlBoundElement); 2699 XmlBoundElement subRowElem = (XmlBoundElement)(rowElemList[0]); 2700 rowElemList.RemoveAt(0); 2701 SetNestedParentRegion(subRowElem, be); 2702 } 2703 } 2704 2705 // Check to make sure all sub-regions are disconnected 2706 AssertNonLiveRows(node); 2707 2708 return; 2709 } 2710 2711 // Nothing to do, since the node belongs to no region 2712 2713 // Check to make sure all sub-regions are disconnected 2714 AssertNonLiveRows(node); 2715 } 2716 2717 // A row-elem was inserted into the connected tree (connected) from oldParent==null state OnRowElementInsertedInTree(XmlBoundElement rowElem, ArrayList rowElemList)2718 private void OnRowElementInsertedInTree(XmlBoundElement rowElem, ArrayList rowElemList) 2719 { 2720 Debug.Assert(rowElem.Row != null); 2721 2722 DataRow row = rowElem.Row; 2723 DataRowState rowState = row.RowState; 2724 2725 switch (rowState) 2726 { 2727 case DataRowState.Detached: 2728 #if DEBUG 2729 try 2730 { 2731 Debug.Assert(row.Table.DataSet.EnforceConstraints == false); 2732 #endif 2733 row.Table.Rows.Add(row); 2734 SetNestedParentRegion(rowElem); 2735 #if DEBUG 2736 } 2737 catch 2738 { 2739 // We should not get any exceptions here 2740 Debug.Assert(false); 2741 throw; 2742 } 2743 #endif 2744 // Add all sub-regions to the list if the caller needs this 2745 if (rowElemList != null) 2746 { 2747 RegionIterator iter = new RegionIterator(rowElem); 2748 for (bool fMore = iter.NextRowElement(); fMore; fMore = iter.NextRightRowElement()) 2749 rowElemList.Add(iter.CurrentNode); 2750 } 2751 break; 2752 case DataRowState.Deleted: 2753 #if DEBUG 2754 try 2755 { 2756 Debug.Assert(row.Table.DataSet.EnforceConstraints == false); 2757 #endif 2758 // Change the row status to be alive (unchanged) 2759 row.RejectChanges(); 2760 // Set ROM from XML 2761 SynchronizeRowFromRowElement(rowElem, rowElemList); 2762 // Set nested parent data row according to where is the row positioned in the tree 2763 SetNestedParentRegion(rowElem); 2764 #if DEBUG 2765 } 2766 catch 2767 { 2768 // We should not get any exceptions here 2769 Debug.Assert(false); 2770 throw; 2771 } 2772 #endif 2773 break; 2774 default: 2775 // Handle your case above 2776 Debug.Assert(false); 2777 break; 2778 } 2779 Debug.Assert(IsRowLive(rowElem.Row)); 2780 } 2781 2782 // Disconnect the DataRow associated w/ the rowElem region EnsureDisconnectedDataRow(XmlBoundElement rowElem)2783 private void EnsureDisconnectedDataRow(XmlBoundElement rowElem) 2784 { 2785 Debug.Assert(rowElem.Row != null); 2786 2787 DataRow row = rowElem.Row; 2788 DataRowState rowState = row.RowState; 2789 2790 switch (rowState) 2791 { 2792 case DataRowState.Detached: 2793 #if DEBUG 2794 try 2795 { 2796 Debug.Assert(row.Table.DataSet.EnforceConstraints == false); 2797 #endif 2798 SetNestedParentRegion(rowElem); 2799 #if DEBUG 2800 } 2801 catch 2802 { 2803 // We should not get any exceptions here 2804 Debug.Assert(false); 2805 throw; 2806 } 2807 #endif 2808 break; 2809 2810 case DataRowState.Deleted: 2811 // Nothing to do: moving a region associated w/ a deleted row to another disconnected tree is a NO-OP. 2812 break; 2813 2814 case DataRowState.Unchanged: 2815 case DataRowState.Modified: 2816 EnsureFoliation(rowElem, ElementState.WeakFoliation); 2817 row.Delete(); 2818 break; 2819 2820 case DataRowState.Added: 2821 EnsureFoliation(rowElem, ElementState.WeakFoliation); 2822 row.Delete(); 2823 SetNestedParentRegion(rowElem); 2824 break; 2825 2826 default: 2827 // Handle your case above 2828 Debug.Assert(false); 2829 break; 2830 } 2831 2832 Debug.Assert(!IsRowLive(rowElem.Row)); 2833 } 2834 2835 2836 // A non-row-elem was inserted into the connected tree (connected) from oldParent==null state OnNonRowElementInsertedInTree(XmlNode node, XmlBoundElement rowElement, ArrayList rowElemList)2837 private void OnNonRowElementInsertedInTree(XmlNode node, XmlBoundElement rowElement, ArrayList rowElemList) 2838 { 2839 // non-row-elem is beeing inserted 2840 DataRow row = rowElement.Row; 2841 // Region should already have an associated data row (otherwise how was the original row-elem inserted ?) 2842 Debug.Assert(row != null); 2843 SynchronizeRowFromRowElement(rowElement); 2844 if (rowElemList != null) 2845 { 2846 TreeIterator iter = new TreeIterator(node); 2847 for (bool fMore = iter.NextRowElement(); fMore; fMore = iter.NextRightRowElement()) 2848 rowElemList.Add(iter.CurrentNode); 2849 } 2850 } 2851 2852 // A non-row-elem was inserted into disconnected tree (fragment) from oldParent==null state (i.e. was disconnected) OnNonRowElementInsertedInFragment(XmlNode node, XmlBoundElement rowElement, ArrayList rowElemList)2853 private void OnNonRowElementInsertedInFragment(XmlNode node, XmlBoundElement rowElement, ArrayList rowElemList) 2854 { 2855 // non-row-elem is beeing inserted 2856 DataRow row = rowElement.Row; 2857 // Region should already have an associated data row (otherwise how was the original row-elem inserted ?) 2858 Debug.Assert(row != null); 2859 // Since oldParent == null, the only 2 row states should have been Detached or Deleted 2860 Debug.Assert(row.RowState == DataRowState.Detached || row.RowState == DataRowState.Deleted); 2861 2862 if (row.RowState == DataRowState.Detached) 2863 SynchronizeRowFromRowElementEx(rowElement, rowElemList); 2864 // Nothing to do if the row is deleted (there is no sync-ing from XML to ROM for deleted rows) 2865 } 2866 SetNestedParentRegion(XmlBoundElement childRowElem)2867 private void SetNestedParentRegion(XmlBoundElement childRowElem) 2868 { 2869 Debug.Assert(childRowElem.Row != null); 2870 2871 XmlBoundElement parentRowElem; 2872 _mapper.GetRegion(childRowElem.ParentNode, out parentRowElem); 2873 SetNestedParentRegion(childRowElem, parentRowElem); 2874 } SetNestedParentRegion(XmlBoundElement childRowElem, XmlBoundElement parentRowElem)2875 private void SetNestedParentRegion(XmlBoundElement childRowElem, XmlBoundElement parentRowElem) 2876 { 2877 DataRow childRow = childRowElem.Row; 2878 if (parentRowElem == null) 2879 { 2880 SetNestedParentRow(childRow, null); 2881 return; 2882 } 2883 2884 DataRow parentRow = parentRowElem.Row; 2885 Debug.Assert(parentRow != null); 2886 // We should set it only if there is a nested relationship between this child and parent regions 2887 DataRelation[] relations = childRow.Table.NestedParentRelations; 2888 if (relations.Length != 0 && relations[0].ParentTable == parentRow.Table) // just backward compatable 2889 { 2890 SetNestedParentRow(childRow, parentRow); 2891 } 2892 else 2893 { 2894 SetNestedParentRow(childRow, null); 2895 } 2896 } 2897 IsTextNode(XmlNodeType nt)2898 internal static bool IsTextNode(XmlNodeType nt) 2899 { 2900 switch (nt) 2901 { 2902 case XmlNodeType.Text: 2903 case XmlNodeType.CDATA: 2904 case XmlNodeType.Whitespace: 2905 case XmlNodeType.SignificantWhitespace: 2906 return true; 2907 default: 2908 return false; 2909 } 2910 } 2911 2912 /* 2913 internal static bool IsWhiteSpace(char ch) { 2914 switch ( ch ) { 2915 case '\u0009' : 2916 case '\u000a' : 2917 case '\u000d' : 2918 case '\u0020' : 2919 return true; 2920 default : 2921 return false; 2922 } 2923 } 2924 2925 internal static bool IsOnlyWhitespace( string str ) { 2926 if (str != null) { 2927 for (int index = 0; index < str.Length; index ++) { 2928 if (! IsWhiteSpace(str[index])) 2929 return false; 2930 } 2931 } 2932 return true; 2933 } 2934 */ 2935 CreateNavigator(XmlNode node)2936 protected override XPathNavigator CreateNavigator(XmlNode node) 2937 { 2938 Debug.Assert(node.OwnerDocument == this || node == this); 2939 if (XPathNodePointer.s_xmlNodeType_To_XpathNodeType_Map[(int)(node.NodeType)] == -1) 2940 return null; 2941 if (IsTextNode(node.NodeType)) 2942 { 2943 XmlNode parent = node.ParentNode; 2944 if (parent != null && parent.NodeType == XmlNodeType.Attribute) 2945 return null; 2946 else 2947 { 2948 #if DEBUG 2949 //if current node is a text node, its parent node has to be foliated 2950 XmlBoundElement be = node.ParentNode as XmlBoundElement; 2951 if (be != null) 2952 Debug.Assert(be.IsFoliated); 2953 #endif 2954 XmlNode prevSib = node.PreviousSibling; 2955 while (prevSib != null && IsTextNode(prevSib.NodeType)) 2956 { 2957 node = prevSib; 2958 prevSib = SafePreviousSibling(node); 2959 } 2960 } 2961 } 2962 return new DataDocumentXPathNavigator(this, node); 2963 } 2964 2965 [System.Diagnostics.Conditional("DEBUG")] AssertLiveRows(XmlNode node)2966 private void AssertLiveRows(XmlNode node) 2967 { 2968 bool wasFoliationEnabled = IsFoliationEnabled; 2969 IsFoliationEnabled = false; 2970 try 2971 { 2972 XmlBoundElement rowElement = node as XmlBoundElement; 2973 if (rowElement != null && rowElement.Row != null) 2974 Debug.Assert(IsRowLive(rowElement.Row)); 2975 TreeIterator iter = new TreeIterator(node); 2976 for (bool fMore = iter.NextRowElement(); fMore; fMore = iter.NextRowElement()) 2977 { 2978 rowElement = iter.CurrentNode as XmlBoundElement; 2979 Debug.Assert(rowElement.Row != null); 2980 Debug.Assert(IsRowLive(rowElement.Row)); 2981 } 2982 } 2983 finally 2984 { 2985 IsFoliationEnabled = wasFoliationEnabled; 2986 } 2987 } 2988 [System.Diagnostics.Conditional("DEBUG")] AssertNonLiveRows(XmlNode node)2989 private void AssertNonLiveRows(XmlNode node) 2990 { 2991 bool wasFoliationEnabled = IsFoliationEnabled; 2992 IsFoliationEnabled = false; 2993 try 2994 { 2995 XmlBoundElement rowElement = node as XmlBoundElement; 2996 if (rowElement != null && rowElement.Row != null) 2997 Debug.Assert(!IsRowLive(rowElement.Row)); 2998 TreeIterator iter = new TreeIterator(node); 2999 for (bool fMore = iter.NextRowElement(); fMore; fMore = iter.NextRowElement()) 3000 { 3001 rowElement = iter.CurrentNode as XmlBoundElement; 3002 Debug.Assert(rowElement.Row != null); 3003 Debug.Assert(!IsRowLive(rowElement.Row)); 3004 } 3005 } 3006 finally 3007 { 3008 IsFoliationEnabled = wasFoliationEnabled; 3009 } 3010 } 3011 GetElementById(string elemId)3012 public override XmlElement GetElementById(string elemId) 3013 { 3014 throw new NotSupportedException(SR.DataDom_NotSupport_GetElementById); 3015 } GetElementsByTagName(string name)3016 public override XmlNodeList GetElementsByTagName(string name) 3017 { 3018 // Retrieving nodes from the returned nodelist may cause foliation which causes new nodes to be created, 3019 // so the System.Xml iterator will throw if this happens during iteration. To avoid this, foliate everything 3020 // before iteration, so iteration will not cause foliation (and as a result of this, creation of new nodes). 3021 XmlNodeList tempNodeList = base.GetElementsByTagName(name); 3022 3023 int tempint = tempNodeList.Count; 3024 return tempNodeList; 3025 } 3026 3027 // after adding Namespace support foir datatable, DataSet does not guarantee that infered tabels would be in the same sequence as they rae in XML, because 3028 // of Namespace. if a table is in different namespace than its children and DataSet, that table would efinetely be added to DataSet after its children. Its By Design 3029 // so in order to maintain backward compatability, we reorder the copy of the datatable collection and use it OrderTables(DataSet ds)3030 private DataTable[] OrderTables(DataSet ds) 3031 { 3032 DataTable[] retValue = null; 3033 if (ds == null || ds.Tables.Count == 0) 3034 { 3035 retValue = Array.Empty<DataTable>(); 3036 } 3037 else if (TablesAreOrdered(ds)) 3038 { 3039 retValue = new DataTable[ds.Tables.Count]; 3040 ds.Tables.CopyTo(retValue, 0); 3041 // XDD assumes PArent table exist before its child, if it does not we won't be handle the case 3042 // same as Everett 3043 } 3044 3045 if (null == retValue) 3046 { 3047 retValue = new DataTable[ds.Tables.Count]; 3048 List<DataTable> tableList = new List<DataTable>(); 3049 // first take the root tables that have no parent 3050 foreach (DataTable dt in ds.Tables) 3051 { 3052 if (dt.ParentRelations.Count == 0) 3053 { 3054 tableList.Add(dt); 3055 } 3056 } 3057 3058 if (tableList.Count > 0) 3059 { // if we have some table inside; 3060 foreach (DataTable dt in ds.Tables) 3061 { 3062 if (IsSelfRelatedDataTable(dt)) 3063 { 3064 tableList.Add(dt); 3065 } 3066 } 3067 for (int readPos = 0; readPos < tableList.Count; readPos++) 3068 { 3069 Debug.Assert(tableList[readPos] != null, "Temp Array is not supposed to reach to null"); 3070 foreach (DataRelation r in tableList[readPos].ChildRelations) 3071 { 3072 DataTable childTable = r.ChildTable; 3073 if (!tableList.Contains(childTable)) 3074 tableList.Add(childTable); 3075 } 3076 } 3077 tableList.CopyTo(retValue); 3078 } 3079 else 3080 {//there will not be any in case just if we have circular relation dependency, just copy as they are in tablecollection use CopyTo of the collection 3081 ds.Tables.CopyTo(retValue, 0); 3082 } 3083 } 3084 return retValue; 3085 } IsSelfRelatedDataTable(DataTable rootTable)3086 private bool IsSelfRelatedDataTable(DataTable rootTable) 3087 { 3088 List<DataTable> tableList = new List<DataTable>(); 3089 bool retValue = false; 3090 foreach (DataRelation r in rootTable.ChildRelations) 3091 { 3092 DataTable childTable = r.ChildTable; 3093 if (childTable == rootTable) 3094 { 3095 retValue = true; 3096 break; 3097 } 3098 else if (!tableList.Contains(childTable)) 3099 { 3100 tableList.Add(childTable); 3101 } 3102 } 3103 if (!retValue) 3104 { 3105 for (int counter = 0; counter < tableList.Count; counter++) 3106 { 3107 foreach (DataRelation r in tableList[counter].ChildRelations) 3108 { 3109 DataTable childTable = r.ChildTable; 3110 if (childTable == rootTable) 3111 { 3112 retValue = true; 3113 break; 3114 } 3115 else if (!tableList.Contains(childTable)) 3116 { 3117 tableList.Add(childTable); 3118 } 3119 } 3120 if (retValue) 3121 { 3122 break; 3123 } 3124 } 3125 } 3126 return retValue; 3127 } TablesAreOrdered(DataSet ds)3128 private bool TablesAreOrdered(DataSet ds) 3129 { 3130 foreach (DataTable dt in ds.Tables) 3131 { 3132 if (dt.Namespace != ds.Namespace) 3133 { 3134 return false; 3135 } 3136 } 3137 return true; 3138 } 3139 } 3140 } 3141