|
//------------------------------------------------------------------------------
// <copyright file="XmlDataDocument.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="true" primary="false">Microsoft</owner>
//------------------------------------------------------------------------------
namespace System.Xml {
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Runtime.Versioning;
using System.Xml.XPath;
/// <devdoc>
/// <para>
/// Represents an entire document. An XmlDataDocument can contain XML
/// data or relational data (DataSet).
/// </para>
/// </devdoc>
[Obsolete("XmlDataDocument class will be removed in a future release.")]
[System.Security.Permissions.HostProtectionAttribute(Synchronization=true)]
public 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 );
}
}
/// <devdoc>
/// <para>
/// Creates an element with the specified Prefix, LocalName, and
/// NamespaceURI.
/// </para>
/// </devdoc>
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 );
}
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public override XmlEntityReference CreateEntityReference(String name) {
throw new NotSupportedException( Res.GetString(Res.DataDom_NotSupport_EntRef ) );
}
/// <devdoc>
/// <para>Gets a DataSet that provides a relational representation of the data in this
/// XmlDataDocument.</para>
/// </devdoc>
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;
}
/// <devdoc>
/// <para>
/// <SPAN>Retrieves the
/// DataRow associated with the specified XmlElement.</SPAN>
/// </para>
/// </devdoc>
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;
}
/// <devdoc>
/// <para><SPAN>Retrieves
/// the XmlElement associated with the specified DataRow.</SPAN></para>
/// </devdoc>
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;
}
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
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;
}
/// <devdoc>
/// <para>Loads the XML document from the specified file.</para>
/// </devdoc>
[ResourceExposure(ResourceScope.Machine)]
[ResourceConsumption(ResourceScope.Machine)]
public override void Load(string filename) {
this.bForceExpandEntity = true;
base.Load( filename );
this.bForceExpandEntity = false;
}
/// <devdoc>
/// <para>Loads the XML document from the specified Stream.</para>
/// </devdoc>
public override void Load( Stream inStream ) {
this.bForceExpandEntity = true;
base.Load( inStream );
this.bForceExpandEntity = false;
}
/// <devdoc>
/// <para>Loads the XML document from the specified TextReader.</para>
/// </devdoc>
public override void Load( TextReader txtReader ) {
this.bForceExpandEntity = true;
base.Load( txtReader );
this.bForceExpandEntity = false;
}
/// <devdoc>
/// <para>Loads the XML document from the specified XmlReader.</para>
/// </devdoc>
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 );
}
}
/// <devdoc>
/// <para>
/// Initializes a new instance of the XmlDataDocument class.
/// </para>
/// </devdoc>
public XmlDataDocument(): base(new XmlDataImplementation()) {
Init();
AttachDataSet( new DataSet() );
this.dataSet.EnforceConstraints = false;
}
/// <devdoc>
/// <para>
/// Initializes a new instance of the XmlDataDocument class with the specified
/// DataSet.
/// </para>
/// </devdoc>
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;
}
*/
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
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<DataTable> tableList = new List<DataTable>();
// 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<DataTable> tableList = new List<DataTable>();
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;
}
}
}
|