-
Notifications
You must be signed in to change notification settings - Fork 4.6k
/
XmlDataDocument.cs
3212 lines (2877 loc) · 131 KB
/
XmlDataDocument.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Xml.XPath;
namespace System.Xml
{
/// <summary>
/// Represents an entire document. An XmlDataDocument can contain XML
/// data or relational data (DataSet).
/// </summary>
[Obsolete("XmlDataDocument has been deprecated and is not supported.")]
public class XmlDataDocument : XmlDocument
{
private const string RequiresUnreferencedCodeMessage = "XmlDataDocument is used for serialization and deserialization. Members from serialized types may be trimmed if not referenced directly.";
private DataSet _dataSet;
private 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.
private int _countAddPointer; // Approximate count of how many times AddPointer was called since the last time we removed the unused pointer objects from pointers hashtable.
private ArrayList _columnChangeList;
private DataRowState _rollbackState;
private bool _fBoundToDataSet; // true if our permanent event listeners are registered to receive DataSet events
private bool _fBoundToDocument; // true if our permanent event listeners are registered to receive XML events. Note that both fBoundToDataSet and fBoundToDataSet should be both true or false.
private bool _fDataRowCreatedSpecial; // true if our special event listener is registered to receive DataRowCreated events. Note that we either have special listeners subsribed or permanent ones (i.e. fDataRowCreatedSpecial and fBoundToDocument/fBoundToDataSet cannot be both true).
private bool _ignoreXmlEvents; // true if XML events should not be processed
private bool _ignoreDataSetEvents; // true if DataSet events should not be processed
private bool _isFoliationEnabled; // true if we should create and reveal the virtual nodes, false if we should reveal only the physical stored nodes
private bool _optimizeStorage; // false if we should only have foilated regions.
private ElementState _autoFoliationState; // When XmlBoundElement will foliate because of member functions, this will contain the foliation mode: usually this is
// ElementState.StrongFoliation, however when foliation occurs due to DataDocumentNavigator operations (InsertNode for example),
// it is usually ElementState.WeakFoliation
private 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.
private object _foliationLock;
internal const string XSI_NIL = "xsi:nil";
internal const string XSI = "xsi";
private bool _bForceExpandEntity;
internal XmlAttribute _attrXml;
internal bool _bLoadFromDataSet;
internal bool _bHasXSINIL;
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.Fail("Pointer not present");
#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)
[MemberNotNull(nameof(_dataSet))]
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
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(SR.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.
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
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.
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
internal void SyncTree(XmlNode node)
{
XmlBoundElement? be;
DataSetMapper.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 _autoFoliationState; }
set { _autoFoliationState = value; }
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
private void BindForLoad()
{
Debug.Assert(_ignoreXmlEvents);
_ignoreDataSetEvents = true;
_mapper.SetupMapping(this, _dataSet);
if (_dataSet.Tables.Count > 0)
{
//at least one table
LoadDataSetFromTree();
}
BindListeners();
_ignoreDataSetEvents = false;
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
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)
{
_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.
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
private void BindSpecialListeners()
{
Debug.Assert(_fDataRowCreatedSpecial == false);
Debug.Assert(_fBoundToDataSet == false && _fBoundToDocument == false);
_dataSet.DataRowCreated += new DataRowCreatedEventHandler(OnDataRowCreatedSpecial);
_fDataRowCreatedSpecial = true;
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
private void UnBindSpecialListeners()
{
Debug.Assert(_fDataRowCreatedSpecial);
_dataSet.DataRowCreated -= new DataRowCreatedEventHandler(OnDataRowCreatedSpecial);
_fDataRowCreatedSpecial = false;
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
private void BindListeners()
{
BindToDocument();
BindToDataSet();
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
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(OnDataSetTablesChanging);
_dataSet.Relations.CollectionChanging += new CollectionChangeEventHandler(OnDataSetRelationsChanging);
_dataSet.DataRowCreated += new DataRowCreatedEventHandler(OnDataRowCreated);
_dataSet.PropertyChanging += new PropertyChangedEventHandler(OnDataSetPropertyChanging);
//this is the hack for this release, should change it in the future
_dataSet.ClearFunctionCalled += new DataSetClearEventhandler(OnClearCalled);
if (_dataSet.Tables.Count > 0)
{
foreach (DataTable t in _dataSet.Tables)
{
BindToTable(t);
}
}
foreach (DataRelation rel in _dataSet.Relations)
{
rel.PropertyChanging += new PropertyChangedEventHandler(OnRelationPropertyChanging);
}
_fBoundToDataSet = true;
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
private void BindToDocument()
{
if (!_fBoundToDocument)
{
NodeInserting += new XmlNodeChangedEventHandler(OnNodeInserting);
NodeInserted += new XmlNodeChangedEventHandler(OnNodeInserted);
NodeRemoving += new XmlNodeChangedEventHandler(OnNodeRemoving);
NodeRemoved += new XmlNodeChangedEventHandler(OnNodeRemoved);
NodeChanging += new XmlNodeChangedEventHandler(OnNodeChanging);
NodeChanged += new XmlNodeChangedEventHandler(OnNodeChanged);
_fBoundToDocument = true;
}
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
private void BindToTable(DataTable t)
{
t.ColumnChanged += new DataColumnChangeEventHandler(OnColumnChanged);
t.RowChanging += new DataRowChangeEventHandler(OnRowChanging);
t.RowChanged += new DataRowChangeEventHandler(OnRowChanged);
t.RowDeleting += new DataRowChangeEventHandler(OnRowChanging);
t.RowDeleted += new DataRowChangeEventHandler(OnRowChanged);
t.PropertyChanging += new PropertyChangedEventHandler(OnTablePropertyChanging);
t.Columns.CollectionChanging += new CollectionChangeEventHandler(OnTableColumnsChanging);
foreach (DataColumn col in t.Columns)
{
// Hook column properties changes, so we can react properly to ROM changes.
col.PropertyChanging += new PropertyChangedEventHandler(OnColumnPropertyChanging);
}
}
/// <summary>
/// Creates an element with the specified Prefix, LocalName, and
/// NamespaceURI.
/// </summary>
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "This whole class is unsafe. Constructors are marked as such.")]
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
prefix ??= string.Empty;
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 being 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);
}
public override XmlEntityReference CreateEntityReference(string name)
{
throw new NotSupportedException(SR.DataDom_NotSupport_EntRef);
}
/// <summary>
/// Gets a DataSet that provides a relational representation of the data in this
/// XmlDataDocument.
/// </summary>
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))
{
return;
}
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;
}
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
private XmlElement EnsureDocumentElement()
{
XmlElement? docelem = DocumentElement;
if (docelem == null)
{
string docElemName = XmlConvert.EncodeLocalName(DataSet.DataSetName);
if (string.IsNullOrEmpty(docElemName))
{
docElemName = "Xml";
}
docelem = new XmlBoundElement(string.Empty, docElemName, DataSet.Namespace ?? string.Empty, this);
AppendChild(docelem);
}
return docelem;
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
private XmlElement EnsureNonRowDocumentElement()
{
XmlElement? docElem = DocumentElement;
if (docElem == null)
return EnsureDocumentElement();
DataRow? rowDocElem = GetRowFromElement(docElem);
if (rowDocElem == null)
return docElem;
return DemoteDocumentElement();
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
private XmlElement DemoteDocumentElement()
{
// Changes of Xml here should not affect ROM
Debug.Assert(_ignoreXmlEvents);
// There should be no reason to call this function if docElem is not a rowElem
Debug.Assert(GetRowFromElement(DocumentElement) != null);
// Remove the DocumentElement and create a new one
XmlElement oldDocElem = 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 correctly hooked up and the mapper is correctly set-up.
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
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
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
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 (DataSetMapper.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;
}
}
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
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
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
private void FoliateIfDataPointers(DataRow row, XmlElement rowElement)
{
if (!IsFoliated(rowElement) && HasPointers(rowElement))
{
bool wasFoliationEnabled = IsFoliationEnabled;
IsFoliationEnabled = true;
try
{
Foliate(rowElement);
}
finally
{
IsFoliationEnabled = wasFoliationEnabled;
}
}
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
private void EnsureFoliation(XmlBoundElement rowElem, ElementState foliation)
{
if (rowElem.IsFoliated) //perf reason, avoid unnecessary lock.
return;
ForceFoliation(rowElem, foliation);
}
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
private void ForceFoliation(XmlBoundElement node, ElementState newState)
{
lock (_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);
}
priorNode ??= newNode;
}
}
}
else
{
if (col.ColumnMapping == MappingType.SimpleContent)
{
XmlAttribute attr = CreateAttribute(XSI, Keywords.XSI_NIL, Keywords.XSINS);
attr.Value = Keywords.TRUE;
node.SetAttributeNode(attr);
_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;
// 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 (DataSetMapper.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;
}
/// <summary>
/// Retrieves the DataRow associated with the specified XmlElement.
/// </summary>
public DataRow? GetRowFromElement(XmlElement? e)
{
return DataSetMapper.GetRowFromElement(e);
}
private XmlNode? GetRowInsertBeforeLocation(DataRow row, XmlElement rowElement, XmlNode parentElement)
{
DataRow refRow = row;
int i;
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 null;
}
/// <summary>
/// Retrieves the XmlElement associated with the specified DataRow.
/// </summary>
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) when (Data.Common.ADP.IsCatchableExceptionType(e))
{
// This can happens only when some threads are creating navigators (thus modifying this.pointers) while other threads are in the foreach loop.
}
}
//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 _ignoreDataSetEvents; }
set { _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.
[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
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;