Code:
/ FX-1434 / FX-1434 / 1.0 / untmp / whidbey / REDBITS / ndp / fx / src / Data / System / NewXml / XmlDataDocument.cs / 1 / XmlDataDocument.cs
//------------------------------------------------------------------------------ //// Copyright (c) Microsoft Corporation. All rights reserved. // //[....] //[....] //[....] //----------------------------------------------------------------------------- namespace System.Xml { using System; using System.IO; using System.Collections; using System.Collections.Generic; using System.Data; using System.Diagnostics; using System.ComponentModel; using System.Xml.XPath; ////// [System.Security.Permissions.HostProtectionAttribute(Synchronization=true)] #if WINFSInternalOnly internal #else public #endif class XmlDataDocument: XmlDocument { DataSet dataSet; DataSetMapper mapper; 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 // that there is at most one thread in AddPointer we are OK. int countAddPointer; // Approximate count of how many times AddPointer was called since the last time we removed the unused pointer objects from pointers hashtable. ArrayList columnChangeList; DataRowState rollbackState; bool fBoundToDataSet; // true if our permanent event listeners are registered to receive DataSet events 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. 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). bool ignoreXmlEvents; // true if XML events should not be processed bool ignoreDataSetEvents; // true if DataSet events should not be processed bool isFoliationEnabled; // true if we should create and reveal the virtual nodes, false if we should reveal only the physical stored nodes bool optimizeStorage; // false if we should only have foilated regions. ElementState autoFoliationState; // When XmlBoundElement will foliate because of memeber functions, this will contain the foliation mode: usually this is // ElementState.StrongFoliation, however when foliation occurs due to DataDocumentNavigator operations (InsertNode for example), // it it usually ElementState.WeakFoliation bool fAssociateDataRow; // if true, CreateElement will create and associate data rows w/ the newly created XmlBoundElement. // If false, then CreateElement will just create the XmlBoundElement nodes. This is usefull for Loading case, // when CreateElement is called by DOM. object foliationLock; internal const string XSI_NIL = "xsi:nil"; internal const string XSI = "xsi"; private bool bForceExpandEntity = false; internal XmlAttribute attrXml = null; internal bool bLoadFromDataSet = false; internal bool bHasXSINIL = false; internal void AddPointer( IXmlDataVirtualNode pointer ) { Debug.Assert( pointers.ContainsValue(pointer) == false ); lock ( pointers ) { countAddPointer++; if ( countAddPointer >= 5 ) { // 5 is choosed to be small enough to not affect perf, but high enough so we will not scan all the time ArrayList al = new ArrayList(); foreach( DictionaryEntry entry in pointers ) { IXmlDataVirtualNode temp = (IXmlDataVirtualNode)(entry.Value); Debug.Assert( temp != null ); if ( ! temp.IsInUse() ) al.Add( temp ); } for (int i = 0; i < al.Count; i++ ) { pointers.Remove( al[ i ] ); } countAddPointer = 0; } pointers[pointer] = pointer; } } [System.Diagnostics.Conditional("DEBUG")] internal void AssertPointerPresent( IXmlDataVirtualNode pointer ) { #if DEBUG object val = pointers[pointer]; if ( val != ( object ) pointer ) Debug.Assert( false ); #endif } // This function attaches the DataSet to XmlDataDocument // We also register a special listener (OnDataRowCreatedSpecial) to DataSet, so we know when we should setup all regular listeners (OnDataRowCreated, OnColumnChanging, etc). // We need to do this because of the following scenario: // - XmlDataDocument doc = new XmlDataDocument(); // - DataSet ds = doc.DataSet; // doc.DataSet creates a data-set, however does not sets-up the regular listeners. // - ds.ReadXmlSchema(); // since there are regular listeners in doc that track ds schema changes, doc does not know about the new tables/columns/etc // - ds.ReadXmlData(); // ds is now filled, however doc has no content (since there were no listeners for the new created DataRow's) // 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 // (we rely on the fact that DataRowCreated is a DataSet wide event, rather than a DataTable event) private void AttachDataSet( DataSet ds ) { // You should not have already an associated dataset Debug.Assert( dataSet == null ); Debug.Assert( ds != null ); if ( ds.FBoundToDocument ) throw new ArgumentException( Res.GetString(Res.DataDom_MultipleDataSet) ); ds.FBoundToDocument = true; dataSet = ds; // Register the special listener to catch the first DataRow event(s) BindSpecialListeners(); } // after loading, all detached DataRows are synchronized with the xml tree and inserted to their tables // or after setting the innerxml, synchronize the rows and if created new and detached, will be inserted. internal void SyncRows( DataRow parentRow, XmlNode node, bool fAddRowsToTable) { XmlBoundElement be = node as XmlBoundElement; if ( be != null ) { DataRow r = be.Row; if (r!=null && be.ElementState == ElementState.Defoliated) return; //no need of syncRow if ( r != null ) { // get all field values. SynchronizeRowFromRowElement( be ); // defoliate if possible be.ElementState = ElementState.WeakFoliation; DefoliateRegion( be ); if ( parentRow != null ) SetNestedParentRow( r, parentRow ); if ( fAddRowsToTable && r.RowState == DataRowState.Detached ) r.Table.Rows.Add( r ); parentRow = r; } } // Attach all rows from children nodes for ( XmlNode child = node.FirstChild; child != null; child = child.NextSibling ) SyncRows( parentRow, child, fAddRowsToTable ); } // All detached DataRows are synchronized with the xml tree and inserted to their tables. // Synchronize the rows and if created new and detached, will be inserted. internal void SyncTree( XmlNode node) { XmlBoundElement be = null; mapper.GetRegion( node, out be ) ; DataRow parentRow = null; bool fAddRowsToTable = IsConnected(node) ; if ( be != null ) { DataRow r = be.Row; if (r!=null && be.ElementState == ElementState.Defoliated) return; //no need of syncRow if ( r != null ) { // get all field values. SynchronizeRowFromRowElement( be ); // defoliation will not be done on the node which is not RowElement, in case of node is externally being used if ( node == be ) { // defoliate if possible be.ElementState = ElementState.WeakFoliation; DefoliateRegion( be ); } if ( fAddRowsToTable && r.RowState == DataRowState.Detached ) r.Table.Rows.Add( r ); parentRow = r; } } // Attach all rows from children nodes for ( XmlNode child = node.FirstChild; child != null; child = child.NextSibling ) SyncRows( parentRow, child, fAddRowsToTable ); } internal ElementState AutoFoliationState { get { return this.autoFoliationState; } set { this.autoFoliationState = value; } } private void BindForLoad() { Debug.Assert( ignoreXmlEvents == true ); ignoreDataSetEvents = true; mapper.SetupMapping( this, dataSet ); if ( dataSet.Tables.Count > 0 ) { //at least one table LoadDataSetFromTree( ); } BindListeners(); ignoreDataSetEvents = false; } private void Bind( bool fLoadFromDataSet ) { // If we have a DocumentElement then it is illegal to call this func to load from data-set Debug.Assert( DocumentElement == null || ! fLoadFromDataSet ); ignoreDataSetEvents = true; ignoreXmlEvents = true; // Do the mapping. This could be a successive mapping in case of this scenario: xd = XmlDataDocument( emptyDataSet ); xd.Load( "file.xml" ); mapper.SetupMapping(this, dataSet); if ( DocumentElement != null ) { LoadDataSetFromTree(); BindListeners(); } else if ( fLoadFromDataSet ) { this.bLoadFromDataSet = true; LoadTreeFromDataSet( DataSet ); BindListeners(); } ignoreDataSetEvents = false; ignoreXmlEvents = false; } internal void Bind( DataRow r, XmlBoundElement e ) { r.Element = e; e.Row = r; } // Binds special listeners to catch the 1st data-row created. When the 1st DataRow is created, XmlDataDocument will automatically bind all regular listeners. private void BindSpecialListeners() { Debug.Assert( fDataRowCreatedSpecial == false ); Debug.Assert( fBoundToDataSet == false && fBoundToDocument == false ); dataSet.DataRowCreated += new DataRowCreatedEventHandler( this.OnDataRowCreatedSpecial ); fDataRowCreatedSpecial = true; } private void UnBindSpecialListeners() { Debug.Assert( fDataRowCreatedSpecial == true ); dataSet.DataRowCreated -= new DataRowCreatedEventHandler( this.OnDataRowCreatedSpecial ); fDataRowCreatedSpecial = false; } private void BindListeners() { BindToDocument(); BindToDataSet(); } private void BindToDataSet() { // We could be already bound to DataSet in this scenario: // xd = new XmlDataDocument( dataSetThatHasNoData ); xd.Load( "foo.xml" ); // so we must not rebound again to it. if ( fBoundToDataSet ) { Debug.Assert( dataSet != null ); return; } // Unregister the DataRowCreatedSpecial notification if ( fDataRowCreatedSpecial ) UnBindSpecialListeners(); dataSet.Tables.CollectionChanging += new CollectionChangeEventHandler( this.OnDataSetTablesChanging ); dataSet.Relations.CollectionChanging += new CollectionChangeEventHandler( this.OnDataSetRelationsChanging ); dataSet.DataRowCreated += new DataRowCreatedEventHandler( this.OnDataRowCreated ); dataSet.PropertyChanging += new PropertyChangedEventHandler( this.OnDataSetPropertyChanging ); //this is the hack for this release, should change it in the future dataSet.ClearFunctionCalled += new DataSetClearEventhandler( this.OnClearCalled ); if ( dataSet.Tables.Count > 0 ) { foreach( DataTable t in dataSet.Tables ) { BindToTable( t ); } } foreach ( DataRelation rel in dataSet.Relations ) { rel.PropertyChanging += new PropertyChangedEventHandler( this.OnRelationPropertyChanging ); } fBoundToDataSet = true; } private void BindToDocument() { if ( !fBoundToDocument ) { NodeInserting += new XmlNodeChangedEventHandler( this.OnNodeInserting ) ; NodeInserted += new XmlNodeChangedEventHandler( this.OnNodeInserted ) ; NodeRemoving += new XmlNodeChangedEventHandler( this.OnNodeRemoving ) ; NodeRemoved += new XmlNodeChangedEventHandler( this.OnNodeRemoved ) ; NodeChanging += new XmlNodeChangedEventHandler( this.OnNodeChanging ) ; NodeChanged += new XmlNodeChangedEventHandler( this.OnNodeChanged ) ; fBoundToDocument = true; } } private void BindToTable( DataTable t ) { // t.ColumnChanging += new DataColumnChangeEventHandler( this.OnColumnChanging ); t.ColumnChanged += new DataColumnChangeEventHandler( this.OnColumnChanged ); t.RowChanging += new DataRowChangeEventHandler( this.OnRowChanging ); t.RowChanged += new DataRowChangeEventHandler( this.OnRowChanged ); t.RowDeleting += new DataRowChangeEventHandler( this.OnRowChanging); t.RowDeleted += new DataRowChangeEventHandler( this.OnRowChanged ); t.PropertyChanging += new PropertyChangedEventHandler( this.OnTablePropertyChanging ); t.Columns.CollectionChanging += new CollectionChangeEventHandler( this.OnTableColumnsChanging ); foreach( DataColumn col in t.Columns ) { // Hook column properties changes, so we can react properly to ROM changes. col.PropertyChanging += new PropertyChangedEventHandler( this.OnColumnPropertyChanging ); } } ////// Represents an entire document. An XmlDataDocument can contain XML /// data or relational data (DataSet). /// ////// public override XmlElement CreateElement( string prefix, string localName, string namespaceURI) { // // There are three states for the document: // - 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 // is created we should subscribe the permenent listeners. // - special listeners OFF, permanent listeners ON: this is when the data doc is loaded (from dataset or XML file) and synchronization takes place. // - 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, // not based on listening to events. #if DEBUG // Cannot have both special and permananent listeners ON if ( fDataRowCreatedSpecial ) Debug.Assert( (fBoundToDataSet == false) && (fBoundToDocument == false) ); // fBoundToDataSet and fBoundToDocument should have the same value Debug.Assert( fBoundToDataSet ? fBoundToDocument : (! fBoundToDocument) ); #endif if ( prefix == null ) prefix = String.Empty; if ( namespaceURI == null ) namespaceURI = String.Empty; if ( ! fAssociateDataRow ) { // Loading state: create just the XmlBoundElement: the LoadTreeFromDataSet/LoadDataSetFromTree will take care of synchronization return new XmlBoundElement( prefix, localName, namespaceURI, this ); } // 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/ // creation of this element EnsurePopulatedMode(); Debug.Assert( fDataRowCreatedSpecial == false ); // Loaded state: create a DataRow, this in turn will create and associate the XmlBoundElement, which we will return. DataTable dt = mapper.SearchMatchingTableSchema( localName, namespaceURI ); if ( dt != null ) { DataRow row = dt.CreateEmptyRow(); // We need to make sure all fields are DBNull foreach( DataColumn col in dt.Columns ) { if ( col.ColumnMapping != MappingType.Hidden ) SetRowValueToNull( row, col ); } XmlBoundElement be = row.Element; Debug.Assert( be != null ); be.Prefix = prefix; return be; } // No matching table schema for this element: just create the element return new XmlBoundElement( prefix, localName, namespaceURI, this ); } ////// Creates an element with the specified Prefix, LocalName, and /// NamespaceURI. /// ////// public override XmlEntityReference CreateEntityReference(String name) { throw new NotSupportedException( Res.GetString(Res.DataDom_NotSupport_EntRef ) ); } ///[To be supplied.] ////// public DataSet DataSet { get { return dataSet; } } private void DefoliateRegion( XmlBoundElement rowElem ) { // You must pass a row element (which s/b associated w/ a DataRow) Debug.Assert( rowElem.Row != null ); if ( !optimizeStorage ) return; if ( rowElem.ElementState != ElementState.WeakFoliation ) return; if ( !mapper.IsRegionRadical( rowElem ) ) { //Console.WriteLine("Region is not radical: "+rowElem.GetHashCode()); return; } //Console.WriteLine("Defoliating Region: " + rowElem.GetHashCode()); bool saveIgnore = IgnoreXmlEvents; IgnoreXmlEvents = true; rowElem.ElementState = ElementState.Defoliating; try { // drop all attributes rowElem.RemoveAllAttributes(); XmlNode node = rowElem.FirstChild; while ( node != null ) { XmlNode next = node.NextSibling; XmlBoundElement be = node as XmlBoundElement; if ( be != null && be.Row != null ) break; // The node must be mapped to a column (since the region is radically structured) Debug.Assert( mapper.GetColumnSchemaForNode( rowElem, node ) != null ); rowElem.RemoveChild( node ); node = next; } #if DEBUG // All subsequent siblings must be sub-regions for ( ; node != null; node = node.NextSibling ) { Debug.Assert( (node is XmlBoundElement) && (((XmlBoundElement)node).Row != null) ); } #endif rowElem.ElementState = ElementState.Defoliated; } finally { IgnoreXmlEvents = saveIgnore; } } private XmlElement EnsureDocumentElement() { XmlElement docelem = DocumentElement; if ( docelem == null ) { string docElemName = XmlConvert.EncodeLocalName( this.DataSet.DataSetName ); if ( docElemName == null || docElemName.Length == 0 ) docElemName = "Xml"; string ns = this.DataSet.Namespace; if ( ns == null ) ns = String.Empty; docelem = new XmlBoundElement( string.Empty, docElemName, ns, this ); AppendChild( docelem ); } return docelem; } private XmlElement EnsureNonRowDocumentElement() { XmlElement docElem = this.DocumentElement; if ( docElem == null ) return EnsureDocumentElement(); DataRow rowDocElem = GetRowFromElement( docElem ); if ( rowDocElem == null ) return docElem; return DemoteDocumentElement(); } private XmlElement DemoteDocumentElement() { // Changes of Xml here should not affect ROM Debug.Assert( this.ignoreXmlEvents == true ); // There should be no reason to call this function if docElem is not a rowElem Debug.Assert( this.GetRowFromElement( this.DocumentElement ) != null ); // Remove the DocumentElement and create a new one XmlElement oldDocElem = this.DocumentElement; RemoveChild( oldDocElem ); XmlElement docElem = EnsureDocumentElement(); docElem.AppendChild( oldDocElem ); // We should have only one child now Debug.Assert( docElem.LastChild == docElem.FirstChild ); return docElem; } // This function ensures that the special listeners are un-subscribed, the permanent listeners are subscribed and // CreateElement will attach DataRows to newly created XmlBoundElement. // It should be called when we have special listeners hooked and we need to change from the special-listeners mode to the // populated/permanenet mode where all listeners are corectly hooked up and the mapper is correctly set-up. private void EnsurePopulatedMode() { // Unbind special listeners, bind permanent ones, setup the mapping, etc #if DEBUG bool fDataRowCreatedSpecialOld = fDataRowCreatedSpecial; bool fAssociateDataRowOld = fAssociateDataRow; #endif if ( fDataRowCreatedSpecial ) { UnBindSpecialListeners(); // If a special listener was ON, we should not have had an already set-up mapper or permanent listeners subscribed Debug.Assert( ! mapper.IsMapped() ); Debug.Assert( ! fBoundToDocument ); Debug.Assert( ! fBoundToDataSet ); mapper.SetupMapping( this, dataSet); BindListeners(); // CreateElement should now create associate DataRows w/ new XmlBoundElement nodes // We should do this ONLY if we switch from special listeners to permanent listeners. The reason is // that DataDocumentNavigator wants to put XmlDataDocument in a batch mode, where CreateElement will just // create a XmlBoundElement (see DataDocumentNavigator.CloneTree) fAssociateDataRow = true; } Debug.Assert( fDataRowCreatedSpecial == false ); Debug.Assert( mapper.IsMapped() ); Debug.Assert( fBoundToDataSet && fBoundToDocument ); #if DEBUG // In case we EnsurePopulatedMode was called on an already populated mode, we should NOT change fAssociateDataRow if ( fDataRowCreatedSpecialOld == false ) Debug.Assert( fAssociateDataRowOld == fAssociateDataRow ); #endif } // Move regions that are marked in ROM as nested children of row/rowElement as last children in XML fragment private void FixNestedChildren(DataRow row, XmlElement rowElement) { foreach( DataRelation dr in GetNestedChildRelations(row) ) { foreach( DataRow r in row.GetChildRows(dr) ) { XmlElement childElem = r.Element; // childElem can be null when we create XML from DataSet (XmlDataDocument( DataSet ) is called) and we insert rowElem of the parentRow before // we insert the rowElem of children rows. if ( childElem != null ) { #if DEBUG bool fIsChildConnected = IsConnected( childElem ); #endif if ( childElem.ParentNode != rowElement ) { childElem.ParentNode.RemoveChild( childElem ); rowElement.AppendChild( childElem ); } #if DEBUG // We should not have changed the connected/disconnected state of the node (since the row state did not change) Debug.Assert( fIsChildConnected == IsConnected( childElem ) ); Debug.Assert( IsRowLive( r ) ? IsConnected( childElem ) : ! IsConnected( childElem ) ); #endif } } } } // This function accepts node params that are not row-elements. In this case, calling this function is a no-op internal void Foliate( XmlBoundElement node, ElementState newState ) { Debug.Assert( newState == ElementState.WeakFoliation || newState == ElementState.StrongFoliation ); #if DEBUG // 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) // Do this only when we are not loading if ( IsFoliationEnabled ) { if( newState == ElementState.StrongFoliation && node.Row == null ) { XmlBoundElement rowElem; ElementState rowElemState = ElementState.None; if ( mapper.GetRegion( node, out rowElem ) ) { rowElemState = rowElem.ElementState; Debug.Assert( rowElemState == ElementState.StrongFoliation || rowElemState == ElementState.WeakFoliation ); } // Add a no-op, so we can still debug in the assert fails #pragma warning disable 1717 // assignment to self rowElemState = rowElemState; #pragma warning restore 1717 } } #endif if ( IsFoliationEnabled ) { if ( node.ElementState == ElementState.Defoliated ) { ForceFoliation( node, newState ); } else if ( node.ElementState == ElementState.WeakFoliation && newState == ElementState.StrongFoliation ) { // Node must be a row-elem Debug.Assert( node.Row != null ); node.ElementState = newState; } } } private void Foliate( XmlElement element ) { if ( element is XmlBoundElement ) ((XmlBoundElement)element).Foliate( ElementState.WeakFoliation ); } // Foliate rowElement region if there are DataPointers that points into it private void FoliateIfDataPointers( DataRow row, XmlElement rowElement ) { if ( !IsFoliated( rowElement) && HasPointers( rowElement ) ) { bool wasFoliationEnabled = IsFoliationEnabled; IsFoliationEnabled = true; try { Foliate( rowElement ); } finally { IsFoliationEnabled = wasFoliationEnabled; } } } private void EnsureFoliation( XmlBoundElement rowElem, ElementState foliation ) { if ( rowElem.IsFoliated ) //perf reason, avoid unecessary lock. return; ForceFoliation( rowElem, foliation); } private void ForceFoliation( XmlBoundElement node, ElementState newState ) { lock ( this.foliationLock ) { if ( node.ElementState != ElementState.Defoliated ) // The region was foliated by an other thread while this thread was locked return; // Node must be a row-elem associated w/ a non-deleted row Debug.Assert( node.Row != null ); Debug.Assert( node.Row.RowState != DataRowState.Deleted ); node.ElementState = ElementState.Foliating; bool saveIgnore = IgnoreXmlEvents; IgnoreXmlEvents = true; try { XmlNode priorNode = null; DataRow row = node.Row; // create new attrs & elements for row // For detached rows: we are in sync w/ temp values // For non-detached rows: we are in sync w/ the current values // For deleted rows: we never sync DataRowVersion rowVersion = ( row.RowState == DataRowState.Detached ) ? DataRowVersion.Proposed : DataRowVersion.Current; foreach( DataColumn col in row.Table.Columns ) { if ( !IsNotMapped(col) ) { object value = row[col, rowVersion]; if ( ! Convert.IsDBNull( value ) ) { if ( col.ColumnMapping == MappingType.Attribute ) { node.SetAttribute( col.EncodedColumnName, col.Namespace, col.ConvertObjectToXml( value ) ); } else { XmlNode newNode = null; if ( col.ColumnMapping == MappingType.Element ) { newNode = new XmlBoundElement( string.Empty, col.EncodedColumnName, col.Namespace, this ); newNode.AppendChild( CreateTextNode( col.ConvertObjectToXml( value ) ) ); if ( priorNode != null ) { node.InsertAfter(newNode, priorNode); } else if ( node.FirstChild != null ) { node.InsertBefore( newNode, node.FirstChild ); } else { node.AppendChild( newNode ); } priorNode = newNode; } else { Debug.Assert( col.ColumnMapping == MappingType.SimpleContent ); newNode = CreateTextNode( col.ConvertObjectToXml( value ) ); if ( node.FirstChild != null ) node.InsertBefore( newNode, node.FirstChild ); else node.AppendChild( newNode ); if ( priorNode == null ) priorNode = newNode; } } } else { if ( col.ColumnMapping == MappingType.SimpleContent ) { XmlAttribute attr = CreateAttribute( XSI, Keywords.XSI_NIL, Keywords.XSINS ); attr.Value = Keywords.TRUE; node.SetAttributeNode( attr ); this.bHasXSINIL = true; } } } } } finally { IgnoreXmlEvents = saveIgnore; node.ElementState = newState; } // update all live pointers OnFoliated( node ); } } //Determine best radical insert position for inserting column elements private XmlNode GetColumnInsertAfterLocation( DataRow row, DataColumn col, XmlBoundElement rowElement ) { XmlNode prev = null; XmlNode node = null; // text only columns appear first if ( IsTextOnly( col ) ) return null; // insert location must be after free text for ( node = rowElement.FirstChild; node != null; prev = node, node = node.NextSibling ) { if ( !IsTextLikeNode( node ) ) break; } for ( ; node != null; prev = node, node = node.NextSibling ) { // insert location must be before any non-element nodes if ( node.NodeType != XmlNodeType.Element ) break; XmlElement e = node as XmlElement; // insert location must be before any non-mapped elements or separate regions if ( mapper.GetRowFromElement( e ) != null ) break; object schema = mapper.GetColumnSchemaForNode( rowElement, node ); if ( schema == null || !(schema is DataColumn) ) break; // insert location must be before any columns logically after this column if ( ((DataColumn)schema).Ordinal > col.Ordinal ) break; } return prev; } private ArrayList GetNestedChildRelations(DataRow row) { ArrayList list = new ArrayList(); foreach( DataRelation r in row.Table.ChildRelations ) { if ( r.Nested ) list.Add(r); } return list; } private DataRow GetNestedParent(DataRow row) { DataRelation relation = GetNestedParentRelation(row); if ( relation != null ) return row.GetParentRow(relation); return null; } private static DataRelation GetNestedParentRelation( DataRow row ) { DataRelation [] relations = row.Table.NestedParentRelations; if (relations.Length == 0) return null; return relations[0]; } private DataColumn GetTextOnlyColumn( DataRow row ) { #if DEBUG { // Make sure there is at most only one text column, and the text column (if present) is the one reported by row.Table.XmlText DataColumnCollection columns = row.Table.Columns; int cCols = columns.Count; int cTextCols = 0; for ( int iCol = 0; iCol < cCols; iCol++ ) { DataColumn c = columns[iCol]; if ( IsTextOnly( c ) ) { Debug.Assert( c == row.Table.XmlText ); ++cTextCols; } } Debug.Assert( cTextCols == 0 || cTextCols == 1 ); if ( cTextCols == 0 ) Debug.Assert( row.Table.XmlText == null ); } #endif return row.Table.XmlText; } ///Gets a DataSet that provides a relational representation of the data in this /// XmlDataDocument. ////// public DataRow GetRowFromElement( XmlElement e ) { return mapper.GetRowFromElement( e ); } private XmlNode GetRowInsertBeforeLocation(DataRow row, XmlElement rowElement, XmlNode parentElement) { DataRow refRow = row; int i = 0; int pos; // Find position // int pos = row.Table.Rows[row]; for (i = 0; i < row.Table.Rows.Count; i++) if (row == row.Table.Rows[i]) break; pos = i; DataRow parentRow = GetNestedParent(row); for (i = pos + 1; i < row.Table.Rows.Count; i++) { refRow = row.Table.Rows[i]; if (GetNestedParent(refRow) == parentRow && GetElementFromRow(refRow).ParentNode == parentElement) break; } if (i < row.Table.Rows.Count) return GetElementFromRow(refRow); else return(XmlNode) null; } ////// Retrieves the /// DataRow associated with the specified XmlElement. /// ////// public XmlElement GetElementFromRow( DataRow r ) { XmlBoundElement be = r.Element; // Debug.Assert( be != null ); return be; } internal bool HasPointers( XmlNode node ) { while ( true ) { try { if ( pointers.Count > 0 ) { object pointer = null; foreach( DictionaryEntry entry in pointers ) { pointer = entry.Value; Debug.Assert( pointer != null ); if ( ((IXmlDataVirtualNode)pointer).IsOnNode( node ) ) return true; } } return false; } catch (Exception e) { // This can happens only when some threads are creating navigators (thus modifying this.pointers) while other threads are in the foreach loop. // Solution is to re-try HasPointers. // if (!System.Data.Common.ADP.IsCatchableExceptionType (e)) { throw; } } } //should never get to this point due to while (true) loop } internal bool IgnoreXmlEvents { get { return ignoreXmlEvents;} set { ignoreXmlEvents = value;} } internal bool IgnoreDataSetEvents { get { return this.ignoreDataSetEvents; } set { this.ignoreDataSetEvents = value; } } private bool IsFoliated( XmlElement element ) { if ( element is XmlBoundElement ) { return((XmlBoundElement)element).IsFoliated; } return true; } private bool IsFoliated( XmlBoundElement be ) { return be.IsFoliated; } internal bool IsFoliationEnabled { get { return isFoliationEnabled; } set { isFoliationEnabled = value; } } // This creates a tree and synchronize ROM w/ the created tree. // 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. // It takes advantage of the fAssociateDataRow flag for populated mode, which allows creation of XmlBoundElement w/o associating DataRow objects. internal XmlNode CloneTree( DataPointer other ) { EnsurePopulatedMode(); bool oldIgnoreDataSetEvents = ignoreDataSetEvents; bool oldIgnoreXmlEvents = ignoreXmlEvents; bool oldFoliationEnabled = IsFoliationEnabled; bool oldAssociateDataRow = fAssociateDataRow; // Caller should ensure that the EnforceConstraints == false. See 60486 for more info about why this was changed from DataSet.EnforceConstraints = false to an assert. Debug.Assert( DataSet.EnforceConstraints == false ); XmlNode newNode; try { ignoreDataSetEvents = true; ignoreXmlEvents = true; IsFoliationEnabled = false; fAssociateDataRow = false; // Create the diconnected tree based on the other navigator newNode = CloneTreeInternal( other ); Debug.Assert( newNode != null ); // Synchronize DataSet from XML LoadRows( null, newNode ); SyncRows( null, newNode, false ); } finally { ignoreDataSetEvents = oldIgnoreDataSetEvents; ignoreXmlEvents = oldIgnoreXmlEvents; IsFoliationEnabled = oldFoliationEnabled; fAssociateDataRow = oldAssociateDataRow; } return newNode; } private XmlNode CloneTreeInternal( DataPointer other ) { Debug.Assert( ignoreDataSetEvents == true ); Debug.Assert( ignoreXmlEvents == true ); Debug.Assert( IsFoliationEnabled == false ); // Create the diconnected tree based on the other navigator XmlNode newNode = CloneNode( other ); DataPointer dp = new DataPointer( other ); try { dp.AddPointer(); if (newNode.NodeType == XmlNodeType.Element) { int cAttributes = dp.AttributeCount; for ( int i = 0; i < cAttributes; i++ ) { dp.MoveToOwnerElement(); if( dp.MoveToAttribute(i) ) { newNode.Attributes.Append( (XmlAttribute)CloneTreeInternal(dp) ); } } dp.MoveTo( other ); } // for ( bool fMore = dp.MoveToFirstChild(); fMore; fMore = dp.MoveToNextSibling() ) newNode.AppendChild( CloneTreeInternal( dp ) ); } finally { dp.SetNoLongerUse(); } return newNode; } ///Retrieves /// the XmlElement associated with the specified DataRow. ////// public override XmlNode CloneNode( bool deep ) { XmlDataDocument clone = (XmlDataDocument)(base.CloneNode(false)); clone.Init(this.DataSet.Clone()); clone.dataSet.EnforceConstraints = this.dataSet.EnforceConstraints; Debug.Assert( clone.FirstChild == null ); if ( deep ) { DataPointer dp = new DataPointer( this, this ); try { dp.AddPointer(); for ( bool fMore = dp.MoveToFirstChild(); fMore; fMore = dp.MoveToNextSibling() ) { XmlNode cloneNode; if ( dp.NodeType == XmlNodeType.Element ) cloneNode = clone.CloneTree( dp ); else cloneNode = clone.CloneNode( dp ); clone.AppendChild( cloneNode ); } } finally{ dp.SetNoLongerUse(); } } return clone; } private XmlNode CloneNode( DataPointer dp ) { switch (dp.NodeType) { //for the nodes without value and have no children case XmlNodeType.DocumentFragment: return this.CreateDocumentFragment(); case XmlNodeType.DocumentType: // return this.CreateDocumentType( dp.Name, dp.PublicId, dp.SystemId, dp.InternalSubset ); case XmlNodeType.XmlDeclaration: return this.CreateXmlDeclaration( dp.Version, dp.Encoding, dp.Standalone ); //case XmlNodeType.Notation: -- notation should not be able to be accessed by XmlNavigator //return this.CreateNotation(dp.Name, dp.PublicId, dp.SystemId ); //for the nodes with value but no children case XmlNodeType.Text: return this.CreateTextNode( dp.Value ); case XmlNodeType.CDATA: return this.CreateCDataSection( dp.Value ); case XmlNodeType.ProcessingInstruction: return this.CreateProcessingInstruction( dp.Name, dp.Value ); case XmlNodeType.Comment: return this.CreateComment( dp.Value ); case XmlNodeType.Whitespace: return this.CreateWhitespace( dp.Value ); case XmlNodeType.SignificantWhitespace: return this.CreateSignificantWhitespace( dp.Value ); //for the nodes that don't have values, but might have children -- only clone the node and leave the children untouched case XmlNodeType.Element: return this.CreateElement(dp.Prefix, dp.LocalName, dp.NamespaceURI); case XmlNodeType.Attribute: return this.CreateAttribute(dp.Prefix, dp.LocalName, dp.NamespaceURI); case XmlNodeType.EntityReference: return this.CreateEntityReference( dp.Name ); } throw new InvalidOperationException( Res.GetString(Res.DataDom_CloneNode, dp.NodeType.ToString() ) ); } internal static bool IsTextLikeNode( XmlNode n ) { switch ( n.NodeType ) { case XmlNodeType.Text: case XmlNodeType.CDATA: case XmlNodeType.Whitespace: case XmlNodeType.SignificantWhitespace: return true; case XmlNodeType.EntityReference: Debug.Assert( false ); return false; default: return false; } } internal bool IsNotMapped( DataColumn c ) { return DataSetMapper.IsNotMapped( c ); } private bool IsSame( DataColumn c, int recNo1, int recNo2 ) { if ( c.Compare( recNo1, recNo2 ) == 0 ) return true; return false; } internal bool IsTextOnly( DataColumn c ) { return c.ColumnMapping == MappingType.SimpleContent; } ///[To be supplied.] ////// public override void Load( string filename ) { this.bForceExpandEntity = true; base.Load( filename ); this.bForceExpandEntity = false; } ///Loads the XML document from the specified file. ////// public override void Load( Stream inStream ) { this.bForceExpandEntity = true; base.Load( inStream ); this.bForceExpandEntity = false; } ///Loads the XML document from the specified Stream. ////// public override void Load( TextReader txtReader ) { this.bForceExpandEntity = true; base.Load( txtReader ); this.bForceExpandEntity = false; } ///Loads the XML document from the specified TextReader. ////// public override void Load( XmlReader reader ) { if ( this.FirstChild != null ) throw new InvalidOperationException( Res.GetString(Res.DataDom_MultipleLoad ) ); try { ignoreXmlEvents = true; // Unhook the DataRowCreatedSpecial listener, since we no longer base on the first created DataRow to do the Bind if ( fDataRowCreatedSpecial ) UnBindSpecialListeners(); // We should NOT create DataRow objects when calling XmlDataDocument.CreateElement fAssociateDataRow = false; // Foliation s/b disabled isFoliationEnabled = false; //now if we load from file we need to set the ExpandEntity flag to ExpandEntities if ( this.bForceExpandEntity ) { Debug.Assert( reader is XmlTextReader ); ((XmlTextReader)reader).EntityHandling = EntityHandling.ExpandEntities; } base.Load( reader ); BindForLoad(); } finally { ignoreXmlEvents = false; isFoliationEnabled = true; autoFoliationState = ElementState.StrongFoliation; fAssociateDataRow = true; } } private void LoadDataSetFromTree() { ignoreDataSetEvents = true; ignoreXmlEvents = true; bool wasFoliationEnabled = IsFoliationEnabled; IsFoliationEnabled = false; bool saveEnforce = dataSet.EnforceConstraints; dataSet.EnforceConstraints = false; try { Debug.Assert( DocumentElement != null ); LoadRows( null, DocumentElement ); SyncRows( null, DocumentElement, true ); dataSet.EnforceConstraints = saveEnforce; } finally { ignoreDataSetEvents = false; ignoreXmlEvents = false; IsFoliationEnabled = wasFoliationEnabled; } } private void LoadTreeFromDataSet( DataSet ds ) { ignoreDataSetEvents = true; ignoreXmlEvents = true; bool wasFoliationEnabled = IsFoliationEnabled; IsFoliationEnabled = false; this.fAssociateDataRow = false; DataTable [] orderedTables = OrderTables(ds); // this is to fix WebData 103397 // problem is after we add support for Namespace for DataTable, when infering we do not guarantee that table would be // in the same sequence that they were in XML because of namespace, some would be on different schema, so since they // wont be in the same sequence as in XML, we may end up with having a child table, before its parent (which is not doable // with XML; and this happend because they are in different namespace) // this kind of problems are known and please see comment in "OnNestedParentChange" // 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 try { for(int i = 0; i < orderedTables.Length; i++) { DataTable t = orderedTables[i]; foreach( DataRow r in t.Rows ) { Debug.Assert( r.Element == null ); XmlBoundElement rowElem = AttachBoundElementToDataRow( r ); // switch ( r.RowState ) { case DataRowState.Added: case DataRowState.Unchanged: case DataRowState.Modified: // OnAddRow( r ); break; case DataRowState.Deleted: // Nothing to do (the row already has an associated element as a fragment break; case DataRowState.Detached: // We should not get rows in this state Debug.Assert( false ); break; default: // Unknown row state Debug.Assert( false ); break; } } } } finally { ignoreDataSetEvents = false; ignoreXmlEvents = false; IsFoliationEnabled = wasFoliationEnabled; this.fAssociateDataRow = true; } } // load all data from tree structre into datarows private void LoadRows( XmlBoundElement rowElem, XmlNode node ) { Debug.Assert( node != null ); XmlBoundElement be = node as XmlBoundElement; if ( be != null ) { DataTable dt = mapper.SearchMatchingTableSchema( rowElem, be ); if ( dt != null ) { DataRow r = GetRowFromElement( be ); Debug.Assert( r == null ); // If the rowElement was just created and has an un-initialized if ( be.ElementState == ElementState.None ) be.ElementState = ElementState.WeakFoliation; r = dt.CreateEmptyRow(); Bind( r, be ); // the region rowElem is now be Debug.Assert( be.Row != null ); rowElem = be; } } // recurse down for children for ( XmlNode child = node.FirstChild; child != null; child = child.NextSibling ) LoadRows( rowElem, child ); } internal DataSetMapper Mapper { get { return mapper; } } internal void OnDataRowCreated( object oDataSet, DataRow row ) { Debug.Assert( row.RowState == DataRowState.Detached ); OnNewRow( row ); } internal void OnClearCalled( object oDataSet, DataTable table ) { throw new NotSupportedException( Res.GetString(Res.DataDom_NotSupport_Clear ) ); } internal void OnDataRowCreatedSpecial( object oDataSet, DataRow row ) { Debug.Assert( row.RowState == DataRowState.Detached ); // Register the regular events and un-register this one Bind(true); // Pass the event to the regular listener OnNewRow( row ); } // Called when a new DataRow is created internal void OnNewRow( DataRow row ) { Debug.Assert( row.Element == null ); // Allow New state also because we are calling this function from Debug.Assert( row.RowState == DataRowState.Detached ); AttachBoundElementToDataRow( row ); } private XmlBoundElement AttachBoundElementToDataRow( DataRow row ) { Debug.Assert( row.Element == null ); DataTable table = row.Table; // We shoould NOT call CreateElement here, since CreateElement will create and attach a new DataRow to the element XmlBoundElement rowElement = new XmlBoundElement( string.Empty, table.EncodedTableName, table.Namespace, this ); rowElement.IsEmpty = false; Bind( row, rowElement ); rowElement.ElementState = ElementState.Defoliated; return rowElement; } private bool NeedXSI_NilAttr( DataRow row ) { DataTable tb = row.Table; Debug.Assert( tb != null ) ; if ( tb.xmlText == null ) return false; object value = row[tb.xmlText]; return ( Convert.IsDBNull( value ) ); } private void OnAddRow( DataRow row ) { // Xml operations in this func should not trigger ROM operations Debug.Assert( this.ignoreXmlEvents == true ); XmlBoundElement rowElement = (XmlBoundElement)(GetElementFromRow( row )); Debug.Assert( rowElement != null ); if ( NeedXSI_NilAttr( row ) && !rowElement.IsFoliated ) //we need to foliate it because we need to add one more attribute xsi:nil = true; ForceFoliation( rowElement, AutoFoliationState ); Debug.Assert( rowElement != null ); DataRow rowDocElem = GetRowFromElement( this.DocumentElement ); if ( rowDocElem != null ) { DataRow parentRow = GetNestedParent( row ); if ( parentRow == null ) DemoteDocumentElement(); } EnsureDocumentElement().AppendChild( rowElement ); // Move the children of the row under FixNestedChildren( row, rowElement ); OnNestedParentChange( row, rowElement, null ); } private void OnColumnValueChanged( DataRow row, DataColumn col, XmlBoundElement rowElement ) { // if ( IsNotMapped(col) ) goto lblDoNestedRelationSync; object value = row[col]; if ( col.ColumnMapping == MappingType.SimpleContent && Convert.IsDBNull( value ) && !rowElement.IsFoliated ) ForceFoliation( rowElement, ElementState.WeakFoliation); else { // no need to sync if not foliated if ( !IsFoliated( rowElement ) ) { #if DEBUG // If the new value is null, we should be already foliated if there is a DataPointer that points to the column // (see OnRowChanging, case DataRowAction.Change) if ( Convert.IsDBNull( row[col, DataRowVersion.Current] ) ) { try { if ( pointers.Count > 0 ) { object pointer = null; foreach( DictionaryEntry entry in pointers ) { pointer = entry.Value; Debug.Assert( (pointer != null) && ! ((IXmlDataVirtualNode)pointer).IsOnColumn( col ) ); } } } catch (Exception e) { // if (!System.Data.Common.ADP.IsCatchableExceptionType (e)) { throw; } // 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 } } #endif goto lblDoNestedRelationSync; } } if ( IsTextOnly( col ) ) { if ( Convert.IsDBNull( value ) ) { value = String.Empty; //make sure that rowElement has Attribute xsi:nil and its value is true XmlAttribute attr = rowElement.GetAttributeNode(XSI_NIL); if ( attr == null ) { attr = CreateAttribute( XSI, Keywords.XSI_NIL, Keywords.XSINS ); attr.Value = Keywords.TRUE; rowElement.SetAttributeNode( attr ); this.bHasXSINIL = true; } else attr.Value = Keywords.TRUE; } else { //make sure that if rowElement has Attribute xsi:nil, its value is false XmlAttribute attr = rowElement.GetAttributeNode(XSI_NIL); if ( attr != null ) attr.Value = Keywords.FALSE; } ReplaceInitialChildText( rowElement, col.ConvertObjectToXml( value ) ); goto lblDoNestedRelationSync; } // update the attribute that maps to the column bool fFound = false; // Find the field node and set it's value if (col.ColumnMapping == MappingType.Attribute) { foreach( XmlAttribute attr in rowElement.Attributes ) { if ( attr.LocalName == col.EncodedColumnName && attr.NamespaceURI == col.Namespace ) { if ( Convert.IsDBNull( value ) ) { attr.OwnerElement.Attributes.Remove( attr ); } else { attr.Value = col.ConvertObjectToXml( value ); } fFound = true; break; } } // create new attribute if we didn't find one. if ( !fFound && ! Convert.IsDBNull( value ) ) { rowElement.SetAttribute( col.EncodedColumnName, col.Namespace, col.ConvertObjectToXml( value ) ); } } else { // update elements that map to the column... RegionIterator iter = new RegionIterator( (XmlBoundElement)rowElement ); bool fMore = iter.Next(); while ( fMore ) { if ( iter.CurrentNode.NodeType == XmlNodeType.Element ) { XmlElement e = (XmlElement) iter.CurrentNode; Debug.Assert( e != null ); //we should skip the subregion XmlBoundElement be = e as XmlBoundElement; if ( be != null && be.Row != null ) { fMore = iter.NextRight(); //skip over the sub-region continue; } if ( e.LocalName == col.EncodedColumnName && e.NamespaceURI == col.Namespace ) { fFound = true; if ( Convert.IsDBNull( value ) ) { PromoteNonValueChildren( e ); fMore = iter.NextRight(); e.ParentNode.RemoveChild( e ); // keep looking for more matching elements continue; } else { ReplaceInitialChildText( e, col.ConvertObjectToXml( value ) ); //make sure that if the Element has Attribute xsi:nil, its value is false XmlAttribute attr = e.GetAttributeNode(XSI_NIL); if ( attr != null ) attr.Value = Keywords.FALSE; // no need to look any further. goto lblDoNestedRelationSync; } } } fMore = iter.Next(); } // create new element if we didn't find one. if ( !fFound && ! Convert.IsDBNull( value ) ) { XmlElement newElem = new XmlBoundElement( string.Empty, col.EncodedColumnName, col.Namespace, this ); newElem.AppendChild( CreateTextNode( col.ConvertObjectToXml( value ) ) ); XmlNode elemBefore = GetColumnInsertAfterLocation( row, col, rowElement ); if ( elemBefore != null ) { rowElement.InsertAfter( newElem, elemBefore ); } else if ( rowElement.FirstChild != null ) { rowElement.InsertBefore( newElem, rowElement.FirstChild ); } else { rowElement.AppendChild( newElem ); } } } lblDoNestedRelationSync: // Change the XML to conform to the (potentially) change in parent nested relation DataRelation relation = GetNestedParentRelation(row); if ( relation != null ) { Debug.Assert( relation.ChildTable == row.Table ); if ( relation.ChildKey.ContainsColumn( col ) ) OnNestedParentChange( row, rowElement, col ); } } private void OnColumnChanged( object sender, DataColumnChangeEventArgs args ) { // You should not be able to make DataRow field changes if the DataRow is deleted Debug.Assert( args.Row.RowState != DataRowState.Deleted ); if ( ignoreDataSetEvents ) return; bool wasIgnoreXmlEvents = ignoreXmlEvents; ignoreXmlEvents = true; bool wasFoliationEnabled = IsFoliationEnabled; IsFoliationEnabled = false; try { DataRow row = args.Row; DataColumn col = args.Column; object oVal = args.ProposedValue; if ( row.RowState == DataRowState.Detached ) { XmlBoundElement be = row.Element; Debug.Assert( be != null ); if ( be.IsFoliated ) { // Need to sync changes from ROM to DOM OnColumnValueChanged( row, col, be ); } } } finally { IsFoliationEnabled = wasFoliationEnabled; ignoreXmlEvents = wasIgnoreXmlEvents; } } private void OnColumnValuesChanged( DataRow row, XmlBoundElement rowElement ) { Debug.Assert( row != null ); Debug.Assert( rowElement != null ); // If user has cascading relationships, then columnChangeList will contains the changed columns only for the last row beeing cascaded // but there will be multiple ROM events if ( columnChangeList.Count > 0 ) { if ( ((DataColumn)(columnChangeList[0])).Table == row.Table ) { foreach( DataColumn c in columnChangeList ) OnColumnValueChanged( row, c, rowElement ); } else { foreach( DataColumn c in row.Table.Columns ) OnColumnValueChanged( row, c, rowElement ); } } else { foreach( DataColumn c in row.Table.Columns ) OnColumnValueChanged( row, c, rowElement ); } columnChangeList.Clear(); } private void OnDeleteRow( DataRow row, XmlBoundElement rowElement ) { // IgnoreXmlEvents s/b on since we are manipulating the XML tree and we not want this to reflect in ROM view. Debug.Assert( this.ignoreXmlEvents == true ); // Special case when rowElem is document element: we create a new docElem, move the current one as a child of // the new created docElem, then process as if the docElem is not a rowElem if ( rowElement == this.DocumentElement ) DemoteDocumentElement(); PromoteInnerRegions( rowElement ); rowElement.ParentNode.RemoveChild( rowElement ); } private void OnDeletingRow( DataRow row, XmlBoundElement rowElement ) { // Note that this function is beeing called even if ignoreDataSetEvents == true. // Foliate, so we can be able to preserve the nodes even if the DataRow has no longer values for the crtRecord. if ( IsFoliated( rowElement ) ) return; bool wasIgnoreXmlEvents = IgnoreXmlEvents; IgnoreXmlEvents = true; bool wasFoliationEnabled = IsFoliationEnabled; IsFoliationEnabled = true; try { Foliate( rowElement ); } finally { IsFoliationEnabled = wasFoliationEnabled; IgnoreXmlEvents = wasIgnoreXmlEvents; } } private void OnFoliated( XmlNode node ) { while ( true ) { try { if ( pointers.Count > 0 ) { foreach( DictionaryEntry entry in pointers ) { object pointer = entry.Value; Debug.Assert( pointer != null ); ((IXmlDataVirtualNode)pointer).OnFoliated( node ); } } return; } catch (Exception e) { // if (!System.Data.Common.ADP.IsCatchableExceptionType (e)) { throw; } // This can happens only when some threads are creating navigators (thus modifying this.pointers) while other threads are in the foreach loop. // Solution is to re-try OnFoliated. } } // You should never get here in regular cases } DataColumn FindAssociatedParentColumn( DataRelation relation, DataColumn childCol ) { DataColumn[] columns = relation.ChildKey.ColumnsReference; for (int i = 0; i < columns.Length; i++) { if ( childCol == columns[i] ) return relation.ParentKey.ColumnsReference[i]; } return null; } // Change the childElement position in the tree to conform to the parent nested relationship in ROM private void OnNestedParentChange(DataRow child, XmlBoundElement childElement, DataColumn childCol) { Debug.Assert( child.Element == childElement && childElement.Row == child ); // 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 Debug.Assert( ignoreXmlEvents == true ); #if DEBUG // In order to check that this move does not change the connected/disconnected state of the node bool fChildElementConnected = IsConnected( childElement ); #endif DataRow parentRowInTree; if ( childElement == this.DocumentElement || childElement.ParentNode == null ) parentRowInTree = null; else parentRowInTree = GetRowFromElement( (XmlElement) childElement.ParentNode ); DataRow parentRowInRelation = GetNestedParent(child); if ( parentRowInTree != parentRowInRelation ) { if ( parentRowInRelation != null ) { // XmlElement newParent = GetElementFromRow( parentRowInRelation ); newParent.AppendChild( childElement ); } else { // no parent? Maybe the parentRow is during changing or childCol is the ID is set to null ( detached from the parent row ). DataRelation relation = GetNestedParentRelation(child); if ( childCol == null || relation == null || Convert.IsDBNull(child[childCol]) ) { EnsureNonRowDocumentElement().AppendChild( childElement ); } else { DataColumn colInParent = FindAssociatedParentColumn( relation, childCol ); Debug.Assert( colInParent != null ); object comparedValue = colInParent.ConvertValue(child[childCol]); if (parentRowInTree.tempRecord != -1 && colInParent.CompareValueTo( parentRowInTree.tempRecord, comparedValue ) != 0 ) { EnsureNonRowDocumentElement().AppendChild( childElement ); } //else do nothing because its original parentRowInRelation will be changed so that this row will still be its child } } } #if DEBUG // 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 // 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) // 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. Debug.Assert( fChildElementConnected == IsConnected( childElement ) ); Debug.Assert( IsRowLive( child ) ? IsConnected( childElement ) : ! IsConnected( childElement ) ); #endif } private void OnNodeChanged( object sender, XmlNodeChangedEventArgs args ) { if ( ignoreXmlEvents ) return; bool wasIgnoreDataSetEvents = ignoreDataSetEvents; bool wasIgnoreXmlEvents = ignoreXmlEvents; bool wasFoliationEnabled = IsFoliationEnabled; ignoreDataSetEvents = true; ignoreXmlEvents = true; IsFoliationEnabled = false; bool fEnableCascading = DataSet.fEnableCascading; DataSet.fEnableCascading = false; try { // okay to allow text node value changes when bound. XmlBoundElement rowElement = null; Debug.Assert( DataSet.EnforceConstraints == false ); if ( mapper.GetRegion( args.Node, out rowElement ) ) { SynchronizeRowFromRowElement( rowElement ); } } finally { ignoreDataSetEvents = wasIgnoreDataSetEvents; ignoreXmlEvents = wasIgnoreXmlEvents; IsFoliationEnabled = wasFoliationEnabled; DataSet.fEnableCascading = fEnableCascading; } } private void OnNodeChanging( object sender, XmlNodeChangedEventArgs args ) { if( ignoreXmlEvents ) return; if ( DataSet.EnforceConstraints != false ) throw new InvalidOperationException( Res.GetString(Res.DataDom_EnforceConstraintsShouldBeOff ) ); } private void OnNodeInserted( object sender, XmlNodeChangedEventArgs args ) { if ( ignoreXmlEvents ) return; bool wasIgnoreDataSetEvents = ignoreDataSetEvents; bool wasIgnoreXmlEvents = ignoreXmlEvents; bool wasFoliationEnabled = IsFoliationEnabled; ignoreDataSetEvents = true; ignoreXmlEvents = true; IsFoliationEnabled = false; Debug.Assert( DataSet.EnforceConstraints == false ); bool fEnableCascading = DataSet.fEnableCascading; DataSet.fEnableCascading = false; try { // Handle both new node inserted and 2nd part of a move operation. // XmlNode node = args.Node; XmlNode oldParent = args.OldParent; XmlNode newParent = args.NewParent; // 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. Debug.Assert( oldParent == null ); if ( IsConnected( newParent ) ) { // Inserting a node to connected tree OnNodeInsertedInTree( node ); } else { // Inserting a node to disconnected tree OnNodeInsertedInFragment( node ); } } finally { ignoreDataSetEvents = wasIgnoreDataSetEvents; ignoreXmlEvents = wasIgnoreXmlEvents; IsFoliationEnabled = wasFoliationEnabled; DataSet.fEnableCascading = fEnableCascading; } } private void OnNodeInserting( object sender, XmlNodeChangedEventArgs args ) { if ( ignoreXmlEvents ) return; if ( DataSet.EnforceConstraints != false ) throw new InvalidOperationException( Res.GetString(Res.DataDom_EnforceConstraintsShouldBeOff ) ); } private void OnNodeRemoved( object sender, XmlNodeChangedEventArgs args ) { if ( ignoreXmlEvents ) return; bool wasIgnoreDataSetEvents = ignoreDataSetEvents; bool wasIgnoreXmlEvents = ignoreXmlEvents; bool wasFoliationEnabled = IsFoliationEnabled; ignoreDataSetEvents = true; ignoreXmlEvents = true; IsFoliationEnabled = false; Debug.Assert( DataSet.EnforceConstraints == false ); bool fEnableCascading = DataSet.fEnableCascading; DataSet.fEnableCascading = false; try { XmlNode node = args.Node; XmlNode oldParent = args.OldParent; Debug.Assert( args.NewParent == null ); if ( IsConnected( oldParent ) ) { // Removing from connected tree to disconnected tree OnNodeRemovedFromTree( node, oldParent ); } else { // Removing from disconnected tree to disconnected tree: just sync the old region OnNodeRemovedFromFragment( node, oldParent ); } } finally { ignoreDataSetEvents = wasIgnoreDataSetEvents; ignoreXmlEvents = wasIgnoreXmlEvents; IsFoliationEnabled = wasFoliationEnabled; DataSet.fEnableCascading = fEnableCascading; } } private void OnNodeRemoving( object sender, XmlNodeChangedEventArgs args ) { if ( ignoreXmlEvents ) return; if ( DataSet.EnforceConstraints != false ) throw new InvalidOperationException( Res.GetString(Res.DataDom_EnforceConstraintsShouldBeOff ) ); } // Node was removed from connected tree to disconnected tree private void OnNodeRemovedFromTree( XmlNode node, XmlNode oldParent ) { XmlBoundElement oldRowElem; // Synchronize values from old region if ( mapper.GetRegion( oldParent, out oldRowElem ) ) SynchronizeRowFromRowElement( oldRowElem ); // Disconnect all regions, starting w/ node (if it is a row-elem) XmlBoundElement rowElem = node as XmlBoundElement; if ( rowElem != null && rowElem.Row != null ) EnsureDisconnectedDataRow( rowElem ); TreeIterator iter = new TreeIterator( node ); for ( bool fMore = iter.NextRowElement(); fMore; fMore = iter.NextRowElement() ) { rowElem = (XmlBoundElement)(iter.CurrentNode); EnsureDisconnectedDataRow( rowElem ); } // Assert that all sub-regions are disconnected AssertNonLiveRows( node ); } // Node was removed from the disconnected tree to disconnected tree private void OnNodeRemovedFromFragment( XmlNode node, XmlNode oldParent ) { XmlBoundElement oldRowElem; if ( mapper.GetRegion( oldParent, out oldRowElem ) ) { // Sync the old region if it is not deleted DataRow row = oldRowElem.Row; // Since the old old region was disconnected, then the row can be only Deleted or Detached Debug.Assert( ! IsRowLive( row ) ); if ( oldRowElem.Row.RowState == DataRowState.Detached ) SynchronizeRowFromRowElement( oldRowElem ); } // Need to set nested for the sub-regions (if node is a row-elem, we need to set it just for itself) XmlBoundElement be = node as XmlBoundElement; if ( be != null && be.Row != null ) { Debug.Assert( ! IsRowLive( be.Row ) ); SetNestedParentRegion( be, null ); } else { // Set nested parent to null for all child regions TreeIterator iter = new TreeIterator( node ); for ( bool fMore = iter.NextRowElement(); fMore; fMore = iter.NextRightRowElement() ) { XmlBoundElement rowElemChild = (XmlBoundElement)(iter.CurrentNode); SetNestedParentRegion( rowElemChild, null ); } } // Assert that all sub-regions are disconnected AssertNonLiveRows( node ); } private void OnRowChanged( object sender, DataRowChangeEventArgs args ) { if ( ignoreDataSetEvents ) return; ignoreXmlEvents = true; bool wasFoliationEnabled = IsFoliationEnabled; IsFoliationEnabled = false; try { DataRow row = args.Row; XmlBoundElement rowElement = row.Element; // We should have an associated row-elem created when the DataRow was created (or at the load time) Debug.Assert( rowElement != null ); switch ( args.Action ) { case DataRowAction.Add: // OnAddRow( row ); break; case DataRowAction.Delete: OnDeleteRow( row, rowElement ); break; case DataRowAction.Rollback: switch ( rollbackState ) { case DataRowState.Deleted: OnUndeleteRow( row, rowElement ); UpdateAllColumns( row, rowElement ); break; case DataRowState.Added: rowElement.ParentNode.RemoveChild( rowElement ); break; case DataRowState.Modified: OnColumnValuesChanged( row, rowElement ); break; } break; case DataRowAction.Change: OnColumnValuesChanged( row, rowElement ); break; case DataRowAction.Commit: if ( row.RowState == DataRowState.Detached ) { //by now, all the descendent of the element that is not of this region should have been promoted already rowElement.RemoveAll(); } break; default: //Console.WriteLine("Other Event"); break; } } finally { IsFoliationEnabled = wasFoliationEnabled; ignoreXmlEvents = false; } } private void OnRowChanging( object sender, DataRowChangeEventArgs args ) { // We foliate the region each time the assocaited row gets deleted DataRow row = args.Row; if ( args.Action == DataRowAction.Delete && row.Element != null ) { OnDeletingRow( row, row.Element ); return; } if ( ignoreDataSetEvents ) return; bool wasFoliationEnabled = IsFoliationEnabled; IsFoliationEnabled = false; try { ignoreXmlEvents = true; XmlElement rowElement = GetElementFromRow( row ); int nRec1 = -1; int nRec2 = -1; if ( rowElement != null ) { switch ( args.Action ) { case DataRowAction.Add: // DataRow is beeing added to the table (Table.Rows.Add is beeing called) break; case DataRowAction.Delete: // DataRow is beeing deleted // - state transition from New (AKA PendingInsert) to Detached (AKA Created) // - state transition from Unchanged to Deleted (AKA PendingDelete) // - state transition from Modified (AKA PendingChange) to Delete (AKA PendingDelete) Debug.Assert( false ); // This should have been handled above, irrespective of ignoreDataSetEvents value (true or false) break; case DataRowAction.Rollback: // DataRow gets reverted to previous values (by calling DataRow.RejectChanges): // - state transition from Detached (AKA Created) to Detached (AKA Created) // - state transition from New (AKA PendingInsert) to Detached (AKA Created) // - state transition from Modified (AKA PendingChange) to Unchanged // - state transition from Deleted (AKA PendingDelete) to Unchanged rollbackState = row.RowState; switch ( rollbackState ) { case DataRowState.Deleted: break; case DataRowState.Detached: break; case DataRowState.Added: break; case DataRowState.Modified: columnChangeList.Clear(); nRec1 = row.GetRecordFromVersion(DataRowVersion.Original); nRec2 = row.GetRecordFromVersion(DataRowVersion.Current); foreach( DataColumn c in row.Table.Columns ) { if ( !IsSame( c, nRec1, nRec2 ) ) columnChangeList.Add(c); } break; } break; case DataRowAction.Change: // A DataRow field is beeing changed // - state transition from New (AKA PendingInsert) to New (AKA PendingInsert) // - state transition from Unchanged to Modified (AKA PendingChange) // - state transition from Modified (AKA PendingChange) to Modified (AKA PendingChange) // columnChangeList.Clear(); nRec1 = row.GetRecordFromVersion( DataRowVersion.Proposed ); nRec2 = row.GetRecordFromVersion( DataRowVersion.Current ); foreach( DataColumn c in row.Table.Columns ) { object proposedValue = row[c, DataRowVersion.Proposed]; object currentValue = row[c, DataRowVersion.Current]; // Foliate if proposedValue is DBNull; this way the DataPointer objects will point to a disconnected fragment after // the DBNull value is beeing set if ( Convert.IsDBNull( proposedValue ) && ! Convert.IsDBNull( currentValue ) ) { // Foliate only for non-hidden columns (since hidden cols are not represented in XML) if ( c.ColumnMapping != MappingType.Hidden ) FoliateIfDataPointers( row, rowElement ); } if ( !IsSame( c, nRec1, nRec2 ) ) columnChangeList.Add(c); } break; case DataRowAction.Commit: break; } } } finally { ignoreXmlEvents = false; IsFoliationEnabled = wasFoliationEnabled; } } private void OnDataSetPropertyChanging( object oDataSet, PropertyChangedEventArgs args ) { if ( args.PropertyName == "DataSetName" ) throw new InvalidOperationException( Res.GetString(Res.DataDom_DataSetNameChange ) ); // } private void OnColumnPropertyChanging( object oColumn, PropertyChangedEventArgs args ) { if ( args.PropertyName == "ColumnName" ) throw new InvalidOperationException( Res.GetString(Res.DataDom_ColumnNameChange ) ); if ( args.PropertyName == "Namespace" ) throw new InvalidOperationException( Res.GetString(Res.DataDom_ColumnNamespaceChange ) ); if ( args.PropertyName == "ColumnMapping" ) throw new InvalidOperationException( Res.GetString(Res.DataDom_ColumnMappingChange ) ); } private void OnTablePropertyChanging( object oTable, PropertyChangedEventArgs args ) { if ( args.PropertyName == "TableName" ) throw new InvalidOperationException( Res.GetString(Res.DataDom_TableNameChange ) ); if ( args.PropertyName == "Namespace" ) throw new InvalidOperationException( Res.GetString(Res.DataDom_TableNamespaceChange ) ); } private void OnTableColumnsChanging( object oColumnsCollection, CollectionChangeEventArgs args ) { // args.Action is one of CollectionChangeAction.Add, CollectionChangeAction.Remove or CollectionChangeAction.Refresh // args.Element is one of either the column (for Add and Remove actions or null, if the entire colection of columns is changing) // Disallow changing the columns collection (since we are subscribed only in populated mode, we allow changes in any state but non-populated mode) throw new InvalidOperationException( Res.GetString(Res.DataDom_TableColumnsChange ) ); } private void OnDataSetTablesChanging( object oTablesCollection, CollectionChangeEventArgs args ) { // args.Action is one of CollectionChangeAction.Add, CollectionChangeAction.Remove or CollectionChangeAction.Refresh // args.Element is a table (dont know if it can be null: // Disallow changing the tables collection (since we are subscribed only in populated mode, we allow changes in any state but non-populated mode) throw new InvalidOperationException( Res.GetString(Res.DataDom_DataSetTablesChange ) ); } private void OnDataSetRelationsChanging( object oRelationsCollection, CollectionChangeEventArgs args ) { // args.Action is one of CollectionChangeAction.Add, CollectionChangeAction.Remove or CollectionChangeAction.Refresh // args.Element is a DataRelation (dont know if it can be null: // Disallow changing the tables collection if there is data loaded and there are nested relationship that are added/refreshed DataRelation rel = (DataRelation)(args.Element); if ( rel != null && rel.Nested ) throw new InvalidOperationException( Res.GetString(Res.DataDom_DataSetNestedRelationsChange ) ); // If Add and Remove, we should already been throwing if .Nested == false Debug.Assert( ! (args.Action == CollectionChangeAction.Add || args.Action == CollectionChangeAction.Remove) || rel.Nested == false ); if ( args.Action == CollectionChangeAction.Refresh ) { foreach ( DataRelation relTemp in (DataRelationCollection)oRelationsCollection ) { if ( relTemp.Nested ) { throw new InvalidOperationException( Res.GetString(Res.DataDom_DataSetNestedRelationsChange ) ); } } } } private void OnRelationPropertyChanging( object oRelationsCollection, PropertyChangedEventArgs args ) { if ( args.PropertyName == "Nested" ) throw new InvalidOperationException( Res.GetString(Res.DataDom_DataSetNestedRelationsChange ) ); } private void OnUndeleteRow( DataRow row, XmlElement rowElement ) { XmlNode refRow; XmlElement parent; // make certain we weren't place somewhere else. if ( rowElement.ParentNode != null ) rowElement.ParentNode.RemoveChild( rowElement ); // Find the parent of RowNode to be inserted DataRow parentRowInRelation = GetNestedParent(row); if (parentRowInRelation == null) { parent = EnsureNonRowDocumentElement(); } else parent = GetElementFromRow(parentRowInRelation); if ((refRow = GetRowInsertBeforeLocation(row, rowElement, parent)) != null) parent.InsertBefore(rowElement, refRow); else parent.AppendChild( rowElement ); FixNestedChildren(row, rowElement); } // Promote the rowElemChild node/region after prevSibling node (as the next sibling) private void PromoteChild( XmlNode child, XmlNode prevSibling ) { // It makes no sense to move rowElemChild on the same level Debug.Assert( child.ParentNode != prevSibling.ParentNode ); // prevSibling must have a parent, since we want to add a sibling to it Debug.Assert( prevSibling.ParentNode != null ); Debug.Assert( IsFoliationEnabled == false ); Debug.Assert( IgnoreXmlEvents == true ); // Should not insert after docElem node Debug.Assert( prevSibling != this.DocumentElement ); if ( child.ParentNode != null ) child.ParentNode.RemoveChild( child ); Debug.Assert( child.ParentNode == null ); prevSibling.ParentNode.InsertAfter( child, prevSibling ); } // Promote child regions under parent as next siblings of parent private void PromoteInnerRegions( XmlNode parent ) { Debug.Assert( parent != null ); Debug.Assert( parent.NodeType != XmlNodeType.Attribute ); // We need to get get the grand-parent region Debug.Assert( parent != DocumentElement ); // We cannot promote children of the DocumentElement XmlNode prevSibling = parent; XmlBoundElement parentRegionRowElem; mapper.GetRegion( parent.ParentNode, out parentRegionRowElem ); TreeIterator iter = new TreeIterator( parent ); bool fMore = iter.NextRowElement(); while ( fMore ) { Debug.Assert( iter.CurrentNode is XmlBoundElement && ((XmlBoundElement)(iter.CurrentNode)).Row != null ); XmlBoundElement rowElemChild = (XmlBoundElement)(iter.CurrentNode); fMore = iter.NextRightRowElement(); PromoteChild( rowElemChild, prevSibling ); SetNestedParentRegion( rowElemChild, parentRegionRowElem ); } } private void PromoteNonValueChildren( XmlNode parent ) { Debug.Assert( parent != null ); XmlNode prevSibling = parent; XmlNode child = parent.FirstChild; bool bTextLikeNode = true; XmlNode nextSibling = null; while ( child != null ) { nextSibling = child.NextSibling; if (!bTextLikeNode || !IsTextLikeNode(child)) { bTextLikeNode = false; nextSibling = child.NextSibling; PromoteChild( child, prevSibling ); prevSibling = child; } child = nextSibling; } } private void RemoveInitialTextNodes( XmlNode node ) { while ( node != null && IsTextLikeNode( node ) ) { XmlNode sibling = node.NextSibling; node.ParentNode.RemoveChild( node ); node = sibling; } } private void ReplaceInitialChildText( XmlNode parent, string value ) { XmlNode n = parent.FirstChild; // don't consider whitespace when replacing initial text while ( n != null && n.NodeType == XmlNodeType.Whitespace ) n = n.NextSibling; if ( n != null ) { if ( n.NodeType == XmlNodeType.Text ) n.Value = value; else n = parent.InsertBefore( CreateTextNode( value ), n ); RemoveInitialTextNodes( n.NextSibling ); } else { parent.AppendChild( CreateTextNode( value ) ); } } internal XmlNode SafeFirstChild( XmlNode n ) { XmlBoundElement be = n as XmlBoundElement; if ( be != null ) return be.SafeFirstChild; else //other type of node should be already foliated. return n.FirstChild; } internal XmlNode SafeNextSibling( XmlNode n ) { XmlBoundElement be = n as XmlBoundElement; if ( be != null ) return be.SafeNextSibling; else //other type of node should be already foliated. return n.NextSibling; } internal XmlNode SafePreviousSibling( XmlNode n ) { XmlBoundElement be = n as XmlBoundElement; if ( be != null ) return be.SafePreviousSibling; else //other type of node should be already foliated. return n.PreviousSibling; } internal static void SetRowValueToNull( DataRow row, DataColumn col ) { Debug.Assert( col.ColumnMapping != MappingType.Hidden ); Debug.Assert( row.Table.DataSet.EnforceConstraints == false ); // if ( ! ( row.IsNull( col ) ) ) row[ col ] = Convert.DBNull; } internal static void SetRowValueFromXmlText( DataRow row, DataColumn col, string xmlText ) { Debug.Assert( xmlText != null ); Debug.Assert( row.Table.DataSet.EnforceConstraints == false ); object oVal; try { oVal = col.ConvertXmlToObject( xmlText ); // This func does not set the field value to null - call SetRowValueToNull in order to do so Debug.Assert( oVal != null && ! ( oVal is DBNull ) ); } catch (Exception e) { // if (!System.Data.Common.ADP.IsCatchableExceptionType (e)) { throw; } // Catch data-type errors and set ROM to Unspecified value SetRowValueToNull( row, col ); return; } if ( ! oVal.Equals( row[col] ) ) row[ col ] = oVal; } private void SynchronizeRowFromRowElement( XmlBoundElement rowElement ) { SynchronizeRowFromRowElement( rowElement, null ); } // Sync row fields w/ values from rowElem region. // If rowElemList is != null, all subregions of rowElem are appended to it. private void SynchronizeRowFromRowElement( XmlBoundElement rowElement, ArrayList rowElemList ) { DataRow row = rowElement.Row; Debug.Assert( row != null ); // No synchronization needed for deleted rows if ( row.RowState == DataRowState.Deleted ) return; row.BeginEdit(); #if DEBUG try { #endif SynchronizeRowFromRowElementEx( rowElement, rowElemList ); #if DEBUG } catch { // We should not get any exceptions because we always handle data-type conversion Debug.Assert( false ); throw; } #endif #if DEBUG try { #endif row.EndEdit(); #if DEBUG } catch { // We should not get any exceptions because DataSet.EnforceConstraints should be always off // Debug.Assert( false ); throw; } #endif } private void SynchronizeRowFromRowElementEx( XmlBoundElement rowElement, ArrayList rowElemList ) { Debug.Assert( rowElement != null ); Debug.Assert( rowElement.Row != null ); Debug.Assert( this.DataSet.EnforceConstraints == false ); DataRow row = rowElement.Row; Debug.Assert( row != null ); DataTable table = row.Table; // if not foliated, already synch'd // if ( !IsFoliated(rowElement) ) // return; //Debug.Assert( IsFoliated(rowElement) ); // If foliated we should not get the event (should be handled directly by DataPointer) Hashtable foundColumns = new Hashtable(); string xsi_attrVal = string.Empty; RegionIterator iter = new RegionIterator( rowElement ); bool fMore; // If present, fill up the TextOnly column DataColumn column = GetTextOnlyColumn( row ); if ( column != null ) { foundColumns[column] = column; string value; fMore = iter.NextInitialTextLikeNodes( out value ); if ( value.Length == 0 && ( ( (xsi_attrVal = rowElement.GetAttribute(XSI_NIL) ) == "1" ) || xsi_attrVal == "true" ) ) row[column] = Convert.DBNull; else SetRowValueFromXmlText( row, column, value ); } else fMore = iter.Next(); // Fill up the columns mapped to an element while ( fMore ) { XmlElement e = iter.CurrentNode as XmlElement; if ( e == null ) { fMore = iter.Next(); continue; } XmlBoundElement be = e as XmlBoundElement; if ( be != null && be.Row != null ) { if ( rowElemList != null ) rowElemList.Add( e ); // Skip over sub-regions fMore = iter.NextRight(); continue; } DataColumn c = mapper.GetColumnSchemaForNode( rowElement, e ); if ( c != null ) { Debug.Assert( c.Table == row.Table ); if ( foundColumns[c] == null ) { foundColumns[c] = c; string value; fMore = iter.NextInitialTextLikeNodes( out value ); if ( value.Length == 0 && ( ( (xsi_attrVal = e.GetAttribute(XSI_NIL) ) == "1" ) || xsi_attrVal == "true" ) ) row[c] = Convert.DBNull; else SetRowValueFromXmlText( row, c, value ); continue; } } fMore = iter.Next(); } // // Walk the attributes to find attributes that map to columns. // foreach( XmlAttribute attr in rowElement.Attributes ) { DataColumn c = mapper.GetColumnSchemaForNode( rowElement, attr ); if ( c != null ) { if ( foundColumns[c] == null ) { foundColumns[c] = c; SetRowValueFromXmlText( row, c, attr.Value ); } } } // Null all columns values that aren't represented in the tree foreach( DataColumn c in row.Table.Columns ) { if ( foundColumns[c] == null && !IsNotMapped(c) ) { if (!c.AutoIncrement) SetRowValueToNull( row, c ); else c.Init(row.tempRecord); } } } private void UpdateAllColumns( DataRow row, XmlBoundElement rowElement ) { foreach( DataColumn c in row.Table.Columns ) { OnColumnValueChanged( row, c, rowElement ); } } ///Loads the XML document from the specified XmlReader. ////// public XmlDataDocument(): base(new XmlDataImplementation()) { Init(); AttachDataSet( new DataSet() ); this.dataSet.EnforceConstraints = false; } ////// Initializes a new instance of the XmlDataDocument class. /// ////// public XmlDataDocument( DataSet dataset ): base(new XmlDataImplementation()) { Init( dataset ); } internal XmlDataDocument( XmlImplementation imp ) : base( imp ) { } private void Init() { this.pointers = new Hashtable(); this.countAddPointer = 0; this.columnChangeList = new ArrayList(); this.ignoreDataSetEvents = false; this.isFoliationEnabled = true; this.optimizeStorage = true; this.fDataRowCreatedSpecial = false; autoFoliationState = ElementState.StrongFoliation; fAssociateDataRow = true; //this needs to be true for newly created elements should have associated datarows mapper = new DataSetMapper(); this.foliationLock = new object(); this.ignoreXmlEvents = true; this.attrXml = CreateAttribute( "xmlns", "xml", XPathNodePointer.s_strReservedXmlns ); this.attrXml.Value = XPathNodePointer.s_strReservedXml; this.ignoreXmlEvents = false; } private void Init( DataSet ds ) { if ( ds == null ) throw new ArgumentException(Res.GetString(Res.DataDom_DataSetNull)); Init(); if ( ds.FBoundToDocument ) throw new ArgumentException( Res.GetString(Res.DataDom_MultipleDataSet) ); ds.FBoundToDocument = true; this.dataSet = ds; Bind(true); } private bool IsConnected( XmlNode node ) { while ( true ) { if ( node == null ) return false; if ( node == this ) return true; XmlAttribute attr = node as XmlAttribute; if ( attr != null ) node = attr.OwnerElement; else node = node.ParentNode; } } private bool IsRowLive( DataRow row ) { return ( row.RowState & ( DataRowState.Added | DataRowState.Unchanged | DataRowState.Modified ) ) != 0; } private static void SetNestedParentRow( DataRow childRow, DataRow parentRow ) { DataRelation rel = GetNestedParentRelation( childRow ); //we should not set this row's parentRow if the table doesn't match. if ( rel != null ) { if ( parentRow == null || rel.ParentKey.Table != parentRow.Table ) childRow.SetParentRow( null, rel ); else childRow.SetParentRow( parentRow, rel ); } } // A node (node) was inserted into the main tree (connected) from oldParent==null state private void OnNodeInsertedInTree( XmlNode node ) { XmlBoundElement be; ArrayList rowElemList = new ArrayList(); if ( mapper.GetRegion( node, out be ) ) { // if ( be == node ) { OnRowElementInsertedInTree( be, rowElemList ); } else { OnNonRowElementInsertedInTree( node, be, rowElemList ); } } else { // We only need to sync the embedded sub-regions TreeIterator iter = new TreeIterator( node ); for (bool fMore = iter.NextRowElement(); fMore; fMore = iter.NextRightRowElement() ) rowElemList.Add( iter.CurrentNode ); } // Process subregions, so they make transition from disconnected to connected tree while ( rowElemList.Count > 0 ) { Debug.Assert(rowElemList[0] != null && rowElemList[0] is XmlBoundElement); XmlBoundElement subRowElem = (XmlBoundElement)(rowElemList[0]); rowElemList.RemoveAt( 0 ); // Expect rowElem to have a DataTable schema, since it is a sub-region Debug.Assert( subRowElem != null ); OnRowElementInsertedInTree( subRowElem, rowElemList ); } // Assert that all sub-regions are assoc w/ "live" rows AssertLiveRows( node ); } // "node" was inserting into a disconnected tree from oldParent==null state private void OnNodeInsertedInFragment( XmlNode node ) { XmlBoundElement be; if ( mapper.GetRegion( node, out be ) ) { if ( be == node ) { Debug.Assert( ! IsRowLive( be.Row ) ); SetNestedParentRegion( be ); } else { ArrayList rowElemList = new ArrayList(); OnNonRowElementInsertedInFragment( node, be, rowElemList ); // Set nested parent for the 1st level subregions (they should already be associated w/ Deleted or Detached rows) while ( rowElemList.Count > 0 ) { Debug.Assert(rowElemList[0] != null && rowElemList[0] is XmlBoundElement); XmlBoundElement subRowElem = (XmlBoundElement)(rowElemList[0]); rowElemList.RemoveAt( 0 ); SetNestedParentRegion( subRowElem, be ); } } // Check to make sure all sub-regions are disconnected AssertNonLiveRows( node ); return; } // Nothing to do, since the node belongs to no region // Check to make sure all sub-regions are disconnected AssertNonLiveRows( node ); } // A row-elem was inserted into the connected tree (connected) from oldParent==null state private void OnRowElementInsertedInTree( XmlBoundElement rowElem, ArrayList rowElemList ) { Debug.Assert( rowElem.Row != null ); DataRow row = rowElem.Row; DataRowState rowState = row.RowState; switch( rowState ) { case DataRowState.Detached: #if DEBUG try { Debug.Assert( row.Table.DataSet.EnforceConstraints == false ); #endif row.Table.Rows.Add( row ); SetNestedParentRegion( rowElem ); #if DEBUG } catch { // We should not get any exceptions here Debug.Assert( false ); throw; } #endif // Add all sub-regions to the list if the caller needs this if ( rowElemList != null ) { RegionIterator iter = new RegionIterator( rowElem ); for (bool fMore = iter.NextRowElement(); fMore; fMore = iter.NextRightRowElement() ) rowElemList.Add( iter.CurrentNode ); } break; case DataRowState.Deleted: #if DEBUG try { Debug.Assert( row.Table.DataSet.EnforceConstraints == false ); #endif // Change the row status to be alive (unchanged) row.RejectChanges(); // Set ROM from XML SynchronizeRowFromRowElement( rowElem, rowElemList ); // Set nested parent data row according to where is the row positioned in the tree SetNestedParentRegion( rowElem ); #if DEBUG } catch { // We should not get any exceptions here Debug.Assert( false ); throw; } #endif break; default: // Handle your case above // Debug.Assert( false ); break; } Debug.Assert( IsRowLive( rowElem.Row ) ); } // Disconnect the DataRow associated w/ the rowElem region private void EnsureDisconnectedDataRow( XmlBoundElement rowElem ) { Debug.Assert( rowElem.Row != null ); DataRow row = rowElem.Row; DataRowState rowState = row.RowState; switch( rowState ) { case DataRowState.Detached: #if DEBUG try { Debug.Assert( row.Table.DataSet.EnforceConstraints == false ); #endif SetNestedParentRegion( rowElem ); #if DEBUG } catch { // We should not get any exceptions here Debug.Assert( false ); throw; } #endif break; case DataRowState.Deleted: // Nothing to do: moving a region associated w/ a deleted row to another disconnected tree is a NO-OP. break; case DataRowState.Unchanged: case DataRowState.Modified: EnsureFoliation( rowElem, ElementState.WeakFoliation ); row.Delete(); break; case DataRowState.Added: EnsureFoliation( rowElem, ElementState.WeakFoliation ); row.Delete(); SetNestedParentRegion( rowElem ); break; default: // Handle your case above // Debug.Assert( false ); break; } Debug.Assert( ! IsRowLive( rowElem.Row ) ); } // A non-row-elem was inserted into the connected tree (connected) from oldParent==null state private void OnNonRowElementInsertedInTree( XmlNode node, XmlBoundElement rowElement, ArrayList rowElemList ) { // non-row-elem is beeing inserted DataRow row = rowElement.Row; // Region should already have an associated data row (otherwise how was the original row-elem inserted ?) Debug.Assert( row != null ); SynchronizeRowFromRowElement( rowElement ); if ( rowElemList != null ) { TreeIterator iter = new TreeIterator( node ); for (bool fMore = iter.NextRowElement(); fMore; fMore = iter.NextRightRowElement() ) rowElemList.Add( iter.CurrentNode ); } } // A non-row-elem was inserted into disconnected tree (fragment) from oldParent==null state (i.e. was disconnected) private void OnNonRowElementInsertedInFragment( XmlNode node, XmlBoundElement rowElement, ArrayList rowElemList ) { // non-row-elem is beeing inserted DataRow row = rowElement.Row; // Region should already have an associated data row (otherwise how was the original row-elem inserted ?) Debug.Assert( row != null ); // Since oldParent == null, the only 2 row states should have been Detached or Deleted Debug.Assert( row.RowState == DataRowState.Detached || row.RowState == DataRowState.Deleted ); if ( row.RowState == DataRowState.Detached ) SynchronizeRowFromRowElementEx( rowElement, rowElemList ); // Nothing to do if the row is deleted (there is no sync-ing from XML to ROM for deleted rows) } private void SetNestedParentRegion( XmlBoundElement childRowElem ) { Debug.Assert( childRowElem.Row != null ); XmlBoundElement parentRowElem; mapper.GetRegion( childRowElem.ParentNode, out parentRowElem ); SetNestedParentRegion( childRowElem, parentRowElem ); } private void SetNestedParentRegion( XmlBoundElement childRowElem, XmlBoundElement parentRowElem ) { DataRow childRow = childRowElem.Row; if ( parentRowElem == null ) { SetNestedParentRow( childRow, null ); return; } DataRow parentRow = parentRowElem.Row; Debug.Assert( parentRow != null ); // We should set it only if there is a nested relationship between this child and parent regions DataRelation [] relations = childRow.Table.NestedParentRelations; if (relations.Length != 0 && relations[0].ParentTable == parentRow.Table ) // just backward compatable // SetNestedParentRow( childRow, parentRow ); else SetNestedParentRow( childRow, null ); } internal static bool IsTextNode( XmlNodeType nt ) { switch( nt ) { case XmlNodeType.Text: case XmlNodeType.CDATA: case XmlNodeType.Whitespace: case XmlNodeType.SignificantWhitespace: return true; default: return false; } } /* internal static bool IsWhiteSpace(char ch) { switch ( ch ) { case '\u0009' : case '\u000a' : case '\u000d' : case '\u0020' : return true; default : return false; } } internal static bool IsOnlyWhitespace( string str ) { if (str != null) { for (int index = 0; index < str.Length; index ++) { if (! IsWhiteSpace(str[index])) return false; } } return true; } */ ////// Initializes a new instance of the XmlDataDocument class with the specified /// DataSet. /// ////// protected override XPathNavigator CreateNavigator(XmlNode node) { Debug.Assert( node.OwnerDocument == this || node == this ); if ( XPathNodePointer.xmlNodeType_To_XpathNodeType_Map[(int)(node.NodeType)] == -1 ) return null; if ( IsTextNode( node.NodeType ) ) { XmlNode parent = node.ParentNode; if ( parent != null && parent.NodeType == XmlNodeType.Attribute ) return null; else { #if DEBUG //if current node is a text node, its parent node has to be foliated XmlBoundElement be = node.ParentNode as XmlBoundElement; if ( be != null ) Debug.Assert( be.IsFoliated ); #endif XmlNode prevSib = node.PreviousSibling; while ( prevSib != null && IsTextNode( prevSib.NodeType ) ) { node = prevSib; prevSib = SafePreviousSibling( node ); } } } return new DataDocumentXPathNavigator( this, node ); } [System.Diagnostics.Conditional("DEBUG")] private void AssertLiveRows( XmlNode node ) { bool wasFoliationEnabled = IsFoliationEnabled; IsFoliationEnabled = false; try { XmlBoundElement rowElement = node as XmlBoundElement; if ( rowElement != null && rowElement.Row != null ) Debug.Assert( IsRowLive( rowElement.Row ) ); TreeIterator iter = new TreeIterator( node ); for (bool fMore = iter.NextRowElement(); fMore; fMore = iter.NextRowElement() ) { rowElement = iter.CurrentNode as XmlBoundElement; Debug.Assert( rowElement.Row != null ); Debug.Assert( IsRowLive( rowElement.Row ) ); } } finally { IsFoliationEnabled = wasFoliationEnabled; } } [System.Diagnostics.Conditional("DEBUG")] private void AssertNonLiveRows( XmlNode node ) { bool wasFoliationEnabled = IsFoliationEnabled; IsFoliationEnabled = false; try { XmlBoundElement rowElement = node as XmlBoundElement; if ( rowElement != null && rowElement.Row != null ) Debug.Assert( ! IsRowLive( rowElement.Row ) ); TreeIterator iter = new TreeIterator( node ); for (bool fMore = iter.NextRowElement(); fMore; fMore = iter.NextRowElement() ) { rowElement = iter.CurrentNode as XmlBoundElement; Debug.Assert( rowElement.Row != null ); Debug.Assert( ! IsRowLive( rowElement.Row ) ); } } finally { IsFoliationEnabled = wasFoliationEnabled; } } public override XmlElement GetElementById( string elemId ) { throw new NotSupportedException( Res.GetString(Res.DataDom_NotSupport_GetElementById ) ); } public override XmlNodeList GetElementsByTagName(string name) { // Retrieving nodes from the returned nodelist may cause foliation which causes new nodes to be created, // so the System.Xml iterator will throw if this happens during iteration. To avoid this, foliate everything // before iteration, so iteration will not cause foliation (and as a result of this, creation of new nodes). XmlNodeList tempNodeList = base.GetElementsByTagName(name); int tempint = tempNodeList.Count; return tempNodeList; } // Webdata 103397 // 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 // 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 // so in order to maintain backward compatability, we reorder the copy of the datatable collection and use it private DataTable[] OrderTables(DataSet ds) { DataTable[] retValue = null; if (ds == null ||ds.Tables.Count == 0) { retValue = new DataTable[0]; } else if (TablesAreOrdered(ds)) { retValue = new DataTable[ds.Tables.Count]; ds.Tables.CopyTo(retValue, 0); // XDD assumes PArent table exist before its child, if it does not we wont be handle the case // same as Everett } if (null == retValue) { retValue = new DataTable[ds.Tables.Count]; List[To be supplied.] ///tableList = new List (); // first take the root tables that have no parent foreach(DataTable dt in ds.Tables) { if (dt.ParentRelations.Count == 0) { tableList.Add(dt); } } if (tableList.Count > 0) { // if we have some table inside; foreach(DataTable dt in ds.Tables) { if (IsSelfRelatedDataTable(dt)) { tableList.Add(dt); } } for(int readPos = 0 ; readPos < tableList.Count; readPos ++) { Debug.Assert(tableList[readPos] != null, "Temp Array is not supposed to reach to null"); foreach(DataRelation r in tableList[readPos].ChildRelations) { DataTable childTable = r.ChildTable; if (!tableList.Contains(childTable)) tableList.Add(childTable); } } tableList.CopyTo(retValue); } else {//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 ds.Tables.CopyTo(retValue, 0); } } return retValue; } private bool IsSelfRelatedDataTable(DataTable rootTable) { List tableList = new List (); bool retValue = false; foreach(DataRelation r in rootTable.ChildRelations) { DataTable childTable = r.ChildTable; if (childTable == rootTable) { retValue = true; break; } else if (!tableList.Contains(childTable)) { tableList.Add(childTable); } } if (!retValue) { for(int counter = 0 ; counter < tableList.Count; counter++) { foreach(DataRelation r in tableList[counter].ChildRelations) { DataTable childTable = r.ChildTable; if (childTable == rootTable) { retValue = true; break; } else if (!tableList.Contains(childTable)) { tableList.Add(childTable); } } if (retValue){ break; } } } return retValue; } private bool TablesAreOrdered(DataSet ds) { foreach(DataTable dt in ds.Tables){ if (dt.Namespace != ds.Namespace) { return false; } } return true; } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- PixelShader.cs
- GregorianCalendar.cs
- _NegoState.cs
- dsa.cs
- VerificationException.cs
- TargetException.cs
- MetadataArtifactLoaderXmlReaderWrapper.cs
- SynchronizationLockException.cs
- NumericUpDown.cs
- StrokeCollection.cs
- PerfCounters.cs
- Vector3DConverter.cs
- InputBinding.cs
- ChannelEndpointElementCollection.cs
- ValidationErrorCollection.cs
- AlternateViewCollection.cs
- CapabilitiesSection.cs
- SafeEventHandle.cs
- UnsupportedPolicyOptionsException.cs
- ModelPerspective.cs
- Exception.cs
- HelpOperationInvoker.cs
- FileSystemInfo.cs
- AdornedElementPlaceholder.cs
- Knowncolors.cs
- KeyboardEventArgs.cs
- XmlAnyElementAttribute.cs
- HtmlControlPersistable.cs
- Point.cs
- DispatchWrapper.cs
- PropertyMetadata.cs
- CharacterHit.cs
- PropertySegmentSerializationProvider.cs
- TransportChannelFactory.cs
- Missing.cs
- AssertFilter.cs
- FieldValue.cs
- Scripts.cs
- CroppedBitmap.cs
- PassportAuthentication.cs
- MessageBox.cs
- SqlResolver.cs
- SmtpDigestAuthenticationModule.cs
- Token.cs
- NumberFunctions.cs
- DynamicValueConverter.cs
- DataGridTableCollection.cs
- FormsAuthenticationUser.cs
- RepeatInfo.cs
- SchemaSetCompiler.cs
- ZipIOBlockManager.cs
- LowerCaseStringConverter.cs
- RemotingConfiguration.cs
- DropSourceBehavior.cs
- Properties.cs
- LocalizableAttribute.cs
- QuaternionKeyFrameCollection.cs
- AttributeQuery.cs
- RSAOAEPKeyExchangeDeformatter.cs
- Point.cs
- TableLayoutRowStyleCollection.cs
- BuildProviderCollection.cs
- XmlMtomWriter.cs
- InfoCardTraceRecord.cs
- Config.cs
- ParseChildrenAsPropertiesAttribute.cs
- HtmlElementCollection.cs
- PartialCachingControl.cs
- IntersectQueryOperator.cs
- CodeDirectiveCollection.cs
- DebugView.cs
- CollectionViewGroupRoot.cs
- _LoggingObject.cs
- DataGridViewUtilities.cs
- WindowPattern.cs
- ExponentialEase.cs
- DesignerDataStoredProcedure.cs
- CompiledQueryCacheKey.cs
- Helper.cs
- WindowsIdentity.cs
- AccessViolationException.cs
- DataStreamFromComStream.cs
- HyperLink.cs
- ListenerConnectionModeReader.cs
- ActivityValidator.cs
- RealizationContext.cs
- SliderAutomationPeer.cs
- WebConfigurationManager.cs
- TableSectionStyle.cs
- TrackingCondition.cs
- ListBoxItemAutomationPeer.cs
- ThrowHelper.cs
- XmlHelper.cs
- WindowsTokenRoleProvider.cs
- SqlReorderer.cs
- Encoding.cs
- MobileControlsSectionHelper.cs
- GridPattern.cs
- OrderedDictionaryStateHelper.cs
- StyleReferenceConverter.cs