2 // System.Data/DataSet.cs
5 // Christopher Podurgiel <cpodurgiel@msn.com>
6 // Daniel Morgan <danmorg@sc.rr.com>
7 // Rodrigo Moya <rodrigo@ximian.com>
8 // Stuart Caborn <stuart.caborn@virgin.net>
9 // Tim Coleman (tim@timcoleman.com)
10 // Ville Palo <vi64pa@koti.soon.fi>
11 // Atsushi Enomoto <atsushi@ximian.com>
13 // (C) Ximian, Inc. 2002
14 // Copyright (C) Tim Coleman, 2002, 2003
18 using System.Collections;
19 using System.ComponentModel;
20 using System.Globalization;
21 using System.Threading;
23 using System.Runtime.Serialization;
25 using System.Xml.Schema;
26 using System.Xml.Serialization;
27 using System.Data.Common;
29 namespace System.Data {
32 [DefaultProperty ("DataSetName")]
34 public class DataSet : MarshalByValueComponent, IListSource,
35 ISupportInitialize, ISerializable, IXmlSerializable
37 private string dataSetName;
38 private string _namespace = "";
39 private string prefix;
40 private bool caseSensitive;
41 private bool enforceConstraints = true;
42 private DataTableCollection tableCollection;
43 private DataRelationCollection relationCollection;
44 private PropertyCollection properties;
45 private DataViewManager defaultView;
46 private CultureInfo locale = System.Threading.Thread.CurrentThread.CurrentCulture;
47 internal XmlDataDocument _xmlDataDocument = null;
51 public DataSet () : this ("NewDataSet")
55 public DataSet (string name)
58 tableCollection = new DataTableCollection (this);
59 relationCollection = new DataRelationCollection.DataSetRelationCollection (this);
60 properties = new PropertyCollection ();
61 this.prefix = String.Empty;
63 this.Locale = CultureInfo.CurrentCulture;
67 protected DataSet (SerializationInfo info, StreamingContext context) : this ()
69 throw new NotImplementedException ();
72 #endregion // Constructors
74 #region Public Properties
76 [DataCategory ("Data")]
77 [DataSysDescription ("Indicates whether comparing strings within the DataSet is case sensitive.")]
78 [DefaultValue (false)]
79 public bool CaseSensitive {
84 caseSensitive = value;
86 foreach (DataTable table in Tables) {
87 foreach (Constraint c in table.Constraints)
88 c.AssertConstraint ();
94 [DataCategory ("Data")]
95 [DataSysDescription ("The name of this DataSet.")]
97 public string DataSetName {
98 get { return dataSetName; }
99 set { dataSetName = value; }
102 [DataSysDescription ("Indicates a custom \"view\" of the data contained by the DataSet. This view allows filtering, searching, and navigating through the custom data view.")]
104 public DataViewManager DefaultViewManager {
106 if (defaultView == null)
107 defaultView = new DataViewManager (this);
112 [DataSysDescription ("Indicates whether constraint rules are to be followed.")]
113 [DefaultValue (true)]
114 public bool EnforceConstraints {
115 get { return enforceConstraints; }
117 if (value != enforceConstraints) {
118 enforceConstraints = value;
120 foreach (DataTable table in Tables) {
121 // first assert all unique constraints
122 foreach (UniqueConstraint uc in table.Constraints.UniqueConstraints)
123 uc.AssertConstraint ();
124 // then assert all foreign keys
125 foreach (ForeignKeyConstraint fk in table.Constraints.ForeignKeyConstraints)
126 fk.AssertConstraint ();
134 [DataCategory ("Data")]
135 [DataSysDescription ("The collection that holds custom user information.")]
136 public PropertyCollection ExtendedProperties {
137 get { return properties; }
141 [DataSysDescription ("Indicates that the DataSet has errors.")]
142 public bool HasErrors {
145 for (int i = 0; i < Tables.Count; i++) {
146 if (Tables[i].HasErrors)
153 [DataCategory ("Data")]
154 [DataSysDescription ("Indicates a locale under which to compare strings within the DataSet.")]
155 public CultureInfo Locale {
160 if (locale == null || !locale.Equals (value)) {
161 // TODO: check if the new locale is valid
162 // TODO: update locale of all tables
168 public void Merge (DataRow[] rows)
170 Merge (rows, false, MissingSchemaAction.Add);
173 public void Merge (DataSet dataSet)
175 Merge (dataSet, false, MissingSchemaAction.Add);
178 public void Merge (DataTable table)
180 Merge (table, false, MissingSchemaAction.Add);
183 public void Merge (DataSet dataSet, bool preserveChanges)
185 Merge (dataSet, preserveChanges, MissingSchemaAction.Add);
189 public void Merge (DataRow[] rows, bool preserveChanges, MissingSchemaAction missingSchemaAction)
192 throw new ArgumentNullException ("rows");
193 if (!IsLegalSchemaAction (missingSchemaAction))
194 throw new ArgumentOutOfRangeException ("missingSchemaAction");
196 MergeManager.Merge (this, rows, preserveChanges, missingSchemaAction);
200 public void Merge (DataSet dataSet, bool preserveChanges, MissingSchemaAction missingSchemaAction)
203 throw new ArgumentNullException ("dataSet");
204 if (!IsLegalSchemaAction (missingSchemaAction))
205 throw new ArgumentOutOfRangeException ("missingSchemaAction");
207 MergeManager.Merge (this, dataSet, preserveChanges, missingSchemaAction);
211 public void Merge (DataTable table, bool preserveChanges, MissingSchemaAction missingSchemaAction)
214 throw new ArgumentNullException ("table");
215 if (!IsLegalSchemaAction (missingSchemaAction))
216 throw new ArgumentOutOfRangeException ("missingSchemaAction");
218 MergeManager.Merge (this, table, preserveChanges, missingSchemaAction);
221 private static bool IsLegalSchemaAction (MissingSchemaAction missingSchemaAction)
223 if (missingSchemaAction == MissingSchemaAction.Add || missingSchemaAction == MissingSchemaAction.AddWithKey
224 || missingSchemaAction == MissingSchemaAction.Error || missingSchemaAction == MissingSchemaAction.Ignore)
229 [DataCategory ("Data")]
230 [DataSysDescription ("Indicates the XML uri namespace for the root element pointed at by this DataSet.")]
232 public string Namespace {
233 get { return _namespace; }
235 //TODO - trigger an event if this happens?
237 value = String.Empty;
238 if (value != this._namespace)
239 RaisePropertyChanging ("Namespace");
244 [DataCategory ("Data")]
245 [DataSysDescription ("Indicates the prefix of the namespace used for this DataSet.")]
247 public string Prefix {
248 get { return prefix; }
251 value = String.Empty;
252 // Prefix cannot contain any special characters other than '_' and ':'
253 for (int i = 0; i < value.Length; i++) {
254 if (!(Char.IsLetterOrDigit (value [i])) && (value [i] != '_') && (value [i] != ':'))
255 throw new DataException ("Prefix '" + value + "' is not valid, because it contains special characters.");
260 value = string.Empty;
262 if (value != this.prefix)
263 RaisePropertyChanging ("Prefix");
268 [DataCategory ("Data")]
269 [DataSysDescription ("The collection that holds the relations for this DatSet.")]
270 [DesignerSerializationVisibility (DesignerSerializationVisibility.Content)]
271 public DataRelationCollection Relations {
273 return relationCollection;
278 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
279 public override ISite Site {
282 throw new NotImplementedException ();
287 throw new NotImplementedException ();
291 [DataCategory ("Data")]
292 [DataSysDescription ("The collection that holds the tables for this DataSet.")]
293 [DesignerSerializationVisibility (DesignerSerializationVisibility.Content)]
294 public DataTableCollection Tables {
295 get { return tableCollection; }
298 #endregion // Public Properties
300 #region Public Methods
303 public void AcceptChanges ()
305 foreach (DataTable tempTable in tableCollection)
306 tempTable.AcceptChanges ();
311 if (_xmlDataDocument != null)
312 throw new NotSupportedException ("Clear function on dataset and datatable is not supported on XmlDataDocument.");
313 for (int t = 0; t < tableCollection.Count; t++) {
314 tableCollection[t].Clear ();
318 public virtual DataSet Clone ()
320 DataSet Copy = new DataSet ();
321 CopyProperties (Copy);
323 foreach (DataTable Table in Tables) {
324 Copy.Tables.Add (Table.Clone ());
327 //Copy Relationships between tables after existance of tables
328 //and setting properties correctly
329 CopyRelations (Copy);
334 // Copies both the structure and data for this DataSet.
335 public DataSet Copy ()
337 DataSet Copy = new DataSet ();
338 CopyProperties (Copy);
340 // Copy DatSet's tables
341 foreach (DataTable Table in Tables)
342 Copy.Tables.Add (Table.Copy ());
344 //Copy Relationships between tables after existance of tables
345 //and setting properties correctly
346 CopyRelations (Copy);
351 private void CopyProperties (DataSet Copy)
353 Copy.CaseSensitive = CaseSensitive;
354 //Copy.Container = Container
355 Copy.DataSetName = DataSetName;
356 //Copy.DefaultViewManager
358 Copy.EnforceConstraints = EnforceConstraints;
359 if(ExtendedProperties.Count > 0) {
360 // Cannot copy extended properties directly as the property does not have a set accessor
361 Array tgtArray = Array.CreateInstance( typeof (object), ExtendedProperties.Count);
362 ExtendedProperties.Keys.CopyTo (tgtArray, 0);
363 for (int i=0; i < ExtendedProperties.Count; i++)
364 Copy.ExtendedProperties.Add (tgtArray.GetValue (i), ExtendedProperties[tgtArray.GetValue (i)]);
366 Copy.Locale = Locale;
367 Copy.Namespace = Namespace;
368 Copy.Prefix = Prefix;
369 //Copy.Site = Site; // FIXME : Not sure of this.
374 private void CopyRelations (DataSet Copy)
377 //Creation of the relation contains some of the properties, and the constructor
378 //demands these values. instead changing the DataRelation constructor and behaviour the
379 //parameters are pre-configured and sent to the most general constructor
381 foreach (DataRelation MyRelation in this.Relations) {
382 string pTable = MyRelation.ParentTable.TableName;
383 string cTable = MyRelation.ChildTable.TableName;
384 DataColumn[] P_DC = new DataColumn[MyRelation.ParentColumns.Length];
385 DataColumn[] C_DC = new DataColumn[MyRelation.ChildColumns.Length];
388 foreach (DataColumn DC in MyRelation.ParentColumns) {
389 P_DC[i]=Copy.Tables[pTable].Columns[DC.ColumnName];
395 foreach (DataColumn DC in MyRelation.ChildColumns) {
396 C_DC[i]=Copy.Tables[cTable].Columns[DC.ColumnName];
400 DataRelation cRel = new DataRelation (MyRelation.RelationName, P_DC, C_DC);
401 //cRel.ChildColumns = MyRelation.ChildColumns;
402 //cRel.ChildTable = MyRelation.ChildTable;
403 //cRel.ExtendedProperties = cRel.ExtendedProperties;
404 //cRel.Nested = MyRelation.Nested;
405 //cRel.ParentColumns = MyRelation.ParentColumns;
406 //cRel.ParentTable = MyRelation.ParentTable;
408 Copy.Relations.Add (cRel);
415 public DataSet GetChanges ()
417 return GetChanges (DataRowState.Added | DataRowState.Deleted | DataRowState.Modified);
421 public DataSet GetChanges (DataRowState rowStates)
423 if (!HasChanges (rowStates))
426 DataSet copySet = Clone ();
427 Hashtable addedRows = new Hashtable ();
429 IEnumerator tableEnumerator = Tables.GetEnumerator ();
432 while (tableEnumerator.MoveNext ()) {
433 origTable = (DataTable)tableEnumerator.Current;
434 copyTable = copySet.Tables[origTable.TableName];
436 // Look for relations that have this table as child
437 IEnumerator relations = origTable.ParentRelations.GetEnumerator ();
439 IEnumerator rowEnumerator = origTable.Rows.GetEnumerator ();
440 while (rowEnumerator.MoveNext ()) {
441 DataRow row = (DataRow)rowEnumerator.Current;
443 if (row.IsRowChanged (rowStates))
444 AddChangedRow (addedRows, copySet, copyTable, relations, row);
450 void AddChangedRow (Hashtable addedRows, DataSet copySet, DataTable copyTable, IEnumerator relations, DataRow row)
452 if (addedRows.ContainsKey (row)) return;
455 while (relations.MoveNext ()) {
456 DataRow parentRow = row.GetParentRow ((DataRelation) relations.Current);
457 if (parentRow == null || addedRows.ContainsKey (parentRow)) continue;
458 DataTable parentCopyTable = copySet.Tables [parentRow.Table.TableName];
459 AddChangedRow (addedRows, copySet, parentCopyTable, parentRow.Table.ParentRelations.GetEnumerator (), parentRow);
462 DataRow newRow = copyTable.NewRow ();
463 copyTable.Rows.Add (newRow);
464 row.CopyValuesToRow (newRow);
465 newRow.XmlRowID = row.XmlRowID;
466 addedRows.Add (row,row);
471 public DataTableReader GetDataReader (DataTable[] dataTables)
473 throw new NotImplementedException ();
477 public DataTableReader GetDataReader ()
479 throw new NotImplementedException ();
483 public string GetXml ()
485 StringWriter Writer = new StringWriter ();
486 WriteXml (Writer, XmlWriteMode.IgnoreSchema);
487 return Writer.ToString ();
490 public string GetXmlSchema ()
492 StringWriter Writer = new StringWriter ();
493 WriteXmlSchema (Writer);
494 return Writer.ToString ();
498 public bool HasChanges ()
500 return HasChanges (DataRowState.Added | DataRowState.Deleted | DataRowState.Modified);
504 public bool HasChanges (DataRowState rowState)
506 if (((int)rowState & 0xffffffe0) != 0)
507 throw new ArgumentOutOfRangeException ("rowState");
509 DataTableCollection tableCollection = Tables;
511 DataRowCollection rowCollection;
514 for (int i = 0; i < tableCollection.Count; i++) {
515 table = tableCollection[i];
516 rowCollection = table.Rows;
517 for (int j = 0; j < rowCollection.Count; j++) {
518 row = rowCollection[j];
519 if ((row.RowState & rowState) != 0)
527 [MonoTODO ("Consider ignored namespace array")]
528 public void InferXmlSchema (XmlReader reader, string[] nsArray)
533 XmlDataLoader Loader = new XmlDataLoader (this);
534 Loader.LoadData (reader, XmlReadMode.InferSchema);
537 public void InferXmlSchema (Stream stream, string[] nsArray)
539 InferXmlSchema (new XmlTextReader (stream), nsArray);
542 public void InferXmlSchema (TextReader reader, string[] nsArray)
544 InferXmlSchema (new XmlTextReader (reader), nsArray);
547 public void InferXmlSchema (string fileName, string[] nsArray)
549 XmlTextReader reader = new XmlTextReader (fileName);
551 InferXmlSchema (reader, nsArray);
559 public void Load (IDataReader reader, LoadOption loadOption, DataTable[] tables)
561 throw new NotImplementedException ();
565 public void Load (IDataReader reader, LoadOption loadOption, string[] tables)
567 throw new NotImplementedException ();
571 public virtual void RejectChanges ()
574 bool oldEnforceConstraints = this.EnforceConstraints;
575 this.EnforceConstraints = false;
577 for (i = 0; i < this.Tables.Count;i++)
578 this.Tables[i].RejectChanges ();
580 this.EnforceConstraints = oldEnforceConstraints;
583 public virtual void Reset ()
585 IEnumerator constraintEnumerator;
587 // first we remove all ForeignKeyConstraints (if we will not do that
588 // we will get an exception when clearing the tables).
589 for (int i = 0; i < Tables.Count; i++) {
590 ConstraintCollection cc = Tables[i].Constraints;
591 for (int j = 0; j < cc.Count; j++) {
592 if (cc[j] is ForeignKeyConstraint)
602 public void WriteXml (Stream stream)
604 XmlTextWriter writer = new XmlTextWriter (stream, null);
605 writer.Formatting = Formatting.Indented;
610 /// Writes the current data for the DataSet to the specified file.
612 /// <param name="filename">Fully qualified filename to write to</param>
613 public void WriteXml (string fileName)
615 XmlTextWriter writer = new XmlTextWriter (fileName, null);
616 writer.Formatting = Formatting.Indented;
617 writer.WriteStartDocument (true);
622 writer.WriteEndDocument ();
627 public void WriteXml (TextWriter writer)
629 XmlTextWriter xwriter = new XmlTextWriter (writer);
630 xwriter.Formatting = Formatting.Indented;
634 public void WriteXml (XmlWriter writer)
636 WriteXml (writer, XmlWriteMode.IgnoreSchema);
639 public void WriteXml (string filename, XmlWriteMode mode)
641 XmlTextWriter writer = new XmlTextWriter (filename, null);
642 writer.Formatting = Formatting.Indented;
643 writer.WriteStartDocument (true);
646 WriteXml (writer, mode);
649 writer.WriteEndDocument ();
654 public void WriteXml (Stream stream, XmlWriteMode mode)
656 XmlTextWriter writer = new XmlTextWriter (stream, null);
657 writer.Formatting = Formatting.Indented;
658 WriteXml (writer, mode);
661 public void WriteXml (TextWriter writer, XmlWriteMode mode)
663 XmlTextWriter xwriter = new XmlTextWriter (writer);
664 xwriter.Formatting = Formatting.Indented;
665 WriteXml (xwriter, mode);
668 public void WriteXml (XmlWriter writer, XmlWriteMode mode)
670 if (mode == XmlWriteMode.DiffGram) {
672 WriteDiffGramElement(writer);
674 // FIXME: It should not write when there is no content to be written.
675 WriteStartElement (writer, mode, Namespace, Prefix, XmlConvert.EncodeName (DataSetName));
677 if (mode == XmlWriteMode.WriteSchema) {
678 DoWriteXmlSchema (writer);
681 WriteTables (writer, mode, Tables, DataRowVersion.Default);
682 if (mode == XmlWriteMode.DiffGram) {
683 writer.WriteEndElement (); //DataSet name
684 if (HasChanges(DataRowState.Modified | DataRowState.Deleted)) {
686 DataSet beforeDS = GetChanges (DataRowState.Modified | DataRowState.Deleted);
687 WriteStartElement (writer, XmlWriteMode.DiffGram, XmlConstants.DiffgrNamespace, XmlConstants.DiffgrPrefix, "before");
688 WriteTables (writer, mode, beforeDS.Tables, DataRowVersion.Original);
689 writer.WriteEndElement ();
692 writer.WriteEndElement (); // DataSet name or diffgr:diffgram
695 public void WriteXmlSchema (Stream stream)
697 XmlTextWriter writer = new XmlTextWriter (stream, null );
698 writer.Formatting = Formatting.Indented;
699 WriteXmlSchema (writer);
702 public void WriteXmlSchema (string fileName)
704 XmlTextWriter writer = new XmlTextWriter (fileName, null);
706 writer.Formatting = Formatting.Indented;
707 writer.WriteStartDocument (true);
708 WriteXmlSchema (writer);
710 writer.WriteEndDocument ();
715 public void WriteXmlSchema (TextWriter writer)
717 XmlTextWriter xwriter = new XmlTextWriter (writer);
719 xwriter.Formatting = Formatting.Indented;
720 // xwriter.WriteStartDocument ();
721 WriteXmlSchema (xwriter);
723 // xwriter.WriteEndDocument ();
728 public void WriteXmlSchema (XmlWriter writer)
730 //Create a skeleton doc and then write the schema
731 //proper which is common to the WriteXml method in schema mode
732 DoWriteXmlSchema (writer);
735 public void ReadXmlSchema (Stream stream)
737 XmlReader reader = new XmlTextReader (stream, null);
738 ReadXmlSchema (reader);
741 public void ReadXmlSchema (string str)
743 XmlReader reader = new XmlTextReader (str);
745 ReadXmlSchema (reader);
752 public void ReadXmlSchema (TextReader treader)
754 XmlReader reader = new XmlTextReader (treader);
755 ReadXmlSchema (reader);
758 public void ReadXmlSchema (XmlReader reader)
761 new XmlSchemaDataImporter (this, reader);
763 XmlSchemaMapper SchemaMapper = new XmlSchemaMapper (this);
764 SchemaMapper.Read (reader);
768 public XmlReadMode ReadXml (Stream stream)
770 return ReadXml (new XmlTextReader (stream));
773 public XmlReadMode ReadXml (string str)
775 XmlTextReader reader = new XmlTextReader (str);
777 return ReadXml (reader);
784 public XmlReadMode ReadXml (TextReader reader)
786 return ReadXml (new XmlTextReader (reader));
789 public XmlReadMode ReadXml (XmlReader r)
791 return ReadXml (r, XmlReadMode.Auto);
794 public XmlReadMode ReadXml (Stream stream, XmlReadMode mode)
796 return ReadXml (new XmlTextReader (stream), mode);
799 public XmlReadMode ReadXml (string str, XmlReadMode mode)
801 XmlTextReader reader = new XmlTextReader (str);
803 return ReadXml (reader, mode);
810 public XmlReadMode ReadXml (TextReader reader, XmlReadMode mode)
812 return ReadXml (new XmlTextReader (reader), mode);
816 public XmlReadMode ReadXml (XmlReader reader, XmlReadMode mode)
818 switch (reader.ReadState) {
819 case ReadState.EndOfFile:
820 case ReadState.Error:
821 case ReadState.Closed:
824 // Skip XML declaration and prolog
825 reader.MoveToContent();
829 XmlReadMode Result = mode;
831 // If diffgram, then read the first element as diffgram
832 if (reader.LocalName == "diffgram" && reader.NamespaceURI == XmlConstants.DiffgrNamespace) {
834 case XmlReadMode.Auto:
835 case XmlReadMode.DiffGram:
836 XmlDiffLoader DiffLoader = new XmlDiffLoader (this);
837 DiffLoader.Load (reader);
838 // (and leave rest of the reader as is)
839 return XmlReadMode.DiffGram;
840 case XmlReadMode.Fragment:
842 // (and continue to read)
846 // (and leave rest of the reader as is)
850 // If schema, then read the first element as schema
851 if (reader.LocalName == "schema" && reader.NamespaceURI == XmlSchema.Namespace) {
853 case XmlReadMode.IgnoreSchema:
854 case XmlReadMode.InferSchema:
856 // (and break up read)
858 case XmlReadMode.Fragment:
859 ReadXmlSchema (reader);
860 // (and continue to read)
862 case XmlReadMode.Auto:
863 if (Tables.Count == 0) {
864 ReadXmlSchema (reader);
865 return XmlReadMode.ReadSchema;
867 // otherwise just ignore and return IgnoreSchema
869 return XmlReadMode.IgnoreSchema;
872 ReadXmlSchema (reader);
873 // (and leave rest of the reader as is)
874 return mode; // When DiffGram, return DiffGram
877 // Otherwise, read as dataset... but only when required.
879 case XmlReadMode.Auto:
880 case XmlReadMode.InferSchema:
881 case XmlReadMode.Fragment:
887 XmlDataLoader Loader = new XmlDataLoader (this);
888 return Loader.LoadData (reader, mode);
890 #endregion // Public Methods
892 #region Public Events
894 [DataCategory ("Action")]
895 [DataSysDescription ("Occurs when it is not possible to merge schemas for two tables with the same name.")]
896 public event MergeFailedEventHandler MergeFailed;
898 #endregion // Public Events
906 #endregion Destructors
908 #region IListSource methods
909 IList IListSource.GetList ()
911 return DefaultViewManager;
914 bool IListSource.ContainsListCollection {
919 #endregion IListSource methods
921 #region ISupportInitialize methods
922 public void BeginInit ()
926 public void EndInit ()
931 #region ISerializable
932 void ISerializable.GetObjectData (SerializationInfo si, StreamingContext sc)
934 throw new NotImplementedException ();
938 #region Protected Methods
939 protected void GetSerializationData (SerializationInfo info, StreamingContext context)
941 string s = info.GetValue ("XmlDiffGram", typeof (String)) as String;
942 if (s != null) ReadXmlSerializable (new XmlTextReader (new StringReader (s)));
946 protected virtual System.Xml.Schema.XmlSchema GetSchemaSerializable ()
951 protected virtual void ReadXmlSerializable (XmlReader reader)
953 ReadXml (reader, XmlReadMode.DiffGram); // FIXME
956 void IXmlSerializable.ReadXml (XmlReader reader)
959 ReadXmlSerializable(reader);
961 // the XmlSerializationReader does this lines!!!
962 //reader.MoveToContent ();
963 //reader.ReadEndElement (); // </DataSet>
966 void IXmlSerializable.WriteXml (XmlWriter writer)
968 DoWriteXmlSchema (writer);
969 WriteXml (writer, XmlWriteMode.DiffGram);
972 protected virtual bool ShouldSerializeRelations ()
977 protected virtual bool ShouldSerializeTables ()
983 protected internal virtual void OnPropertyChanging (PropertyChangedEventArgs pcevent)
988 protected virtual void OnRemoveRelation (DataRelation relation)
993 protected virtual void OnRemoveTable (DataTable table)
997 protected internal virtual void OnMergeFailed (MergeFailedEventArgs e)
999 if (MergeFailed != null)
1000 MergeFailed (this, e);
1004 protected internal void RaisePropertyChanging (string name)
1009 #region Private Xml Serialisation
1011 private string WriteObjectXml (object o)
1013 switch (Type.GetTypeCode (o.GetType ())) {
1014 case TypeCode.Boolean:
1015 return XmlConvert.ToString ((Boolean) o);
1017 return XmlConvert.ToString ((Byte) o);
1019 return XmlConvert.ToString ((Char) o);
1020 case TypeCode.DateTime:
1021 return XmlConvert.ToString ((DateTime) o);
1022 case TypeCode.Decimal:
1023 return XmlConvert.ToString ((Decimal) o);
1024 case TypeCode.Double:
1025 return XmlConvert.ToString ((Double) o);
1026 case TypeCode.Int16:
1027 return XmlConvert.ToString ((Int16) o);
1028 case TypeCode.Int32:
1029 return XmlConvert.ToString ((Int32) o);
1030 case TypeCode.Int64:
1031 return XmlConvert.ToString ((Int64) o);
1032 case TypeCode.SByte:
1033 return XmlConvert.ToString ((SByte) o);
1034 case TypeCode.Single:
1035 return XmlConvert.ToString ((Single) o);
1036 case TypeCode.UInt16:
1037 return XmlConvert.ToString ((UInt16) o);
1038 case TypeCode.UInt32:
1039 return XmlConvert.ToString ((UInt32) o);
1040 case TypeCode.UInt64:
1041 return XmlConvert.ToString ((UInt64) o);
1043 if (o is TimeSpan) return XmlConvert.ToString ((TimeSpan) o);
1044 if (o is Guid) return XmlConvert.ToString ((Guid) o);
1045 if (o is byte[]) return Convert.ToBase64String ((byte[])o);
1046 return o.ToString ();
1049 private void WriteTables (XmlWriter writer, XmlWriteMode mode, DataTableCollection tableCollection, DataRowVersion version)
1051 //Write out each table in order, providing it is not
1052 //part of another table structure via a nested parent relationship
1053 foreach (DataTable table in tableCollection) {
1054 bool isTopLevel = true;
1055 foreach (DataRelation rel in table.ParentRelations) {
1063 WriteTable ( writer, table, mode, version);
1068 private void WriteTable (XmlWriter writer, DataTable table, XmlWriteMode mode, DataRowVersion version)
1070 DataRow[] rows = new DataRow [table.Rows.Count];
1071 table.Rows.CopyTo (rows, 0);
1072 WriteTable (writer, rows, mode, version);
1075 private void WriteTable (XmlWriter writer, DataRow[] rows, XmlWriteMode mode, DataRowVersion version)
1077 //The columns can be attributes, hidden, elements, or simple content
1078 //There can be 0-1 simple content cols or 0-* elements
1079 System.Collections.ArrayList atts;
1080 System.Collections.ArrayList elements;
1081 DataColumn simple = null;
1083 if (rows.Length == 0) return;
1084 DataTable table = rows[0].Table;
1085 SplitColumns (table, out atts, out elements, out simple);
1086 //sort out the namespacing
1087 string nspc = table.Namespace.Length > 0 ? table.Namespace : Namespace;
1089 foreach (DataRow row in rows) {
1090 if (!row.HasVersion(version) ||
1091 (mode == XmlWriteMode.DiffGram && row.RowState == DataRowState.Unchanged
1092 && version == DataRowVersion.Original))
1095 // First check are all the rows null. If they are we just write empty element
1096 bool AllNulls = true;
1097 foreach (DataColumn dc in table.Columns) {
1099 if (row [dc.ColumnName, version] != DBNull.Value) {
1105 // If all of the columns were null, we have to write empty element
1107 writer.WriteElementString (table.TableName, "");
1111 WriteTableElement (writer, mode, table, row, version);
1113 foreach (DataColumn col in atts) {
1114 WriteColumnAsAttribute (writer, mode, col, row, version);
1117 if (simple != null) {
1118 writer.WriteString (WriteObjectXml (row[simple, version]));
1121 foreach (DataColumn col in elements) {
1122 WriteColumnAsElement (writer, mode, col, row, version);
1126 foreach (DataRelation relation in table.ChildRelations) {
1127 if (relation.Nested) {
1128 WriteTable (writer, row.GetChildRows (relation), mode, version);
1132 writer.WriteEndElement ();
1137 private void WriteColumnAsElement (XmlWriter writer, XmlWriteMode mode, DataColumn col, DataRow row, DataRowVersion version)
1139 string colnspc = null;
1140 object rowObject = row [col, version];
1142 if (rowObject == null || rowObject == DBNull.Value)
1145 if (col.Namespace != String.Empty)
1146 colnspc = col.Namespace;
1148 //TODO check if I can get away with write element string
1149 WriteStartElement (writer, mode, colnspc, col.Prefix, col.ColumnName);
1150 writer.WriteString (WriteObjectXml (rowObject));
1151 writer.WriteEndElement ();
1154 private void WriteColumnAsAttribute (XmlWriter writer, XmlWriteMode mode, DataColumn col, DataRow row, DataRowVersion version)
1156 WriteAttributeString (writer, mode, col.Namespace, col.Prefix, col.ColumnName, row[col, version].ToString ());
1159 private void WriteTableElement (XmlWriter writer, XmlWriteMode mode, DataTable table, DataRow row, DataRowVersion version)
1161 //sort out the namespacing
1162 string nspc = table.Namespace.Length > 0 ? table.Namespace : Namespace;
1164 WriteStartElement (writer, mode, nspc, table.Prefix, table.TableName);
1166 if (mode == XmlWriteMode.DiffGram) {
1167 WriteAttributeString (writer, mode, XmlConstants.DiffgrNamespace, XmlConstants.DiffgrPrefix, "id", table.TableName + (row.XmlRowID + 1));
1168 WriteAttributeString (writer, mode, XmlConstants.MsdataNamespace, XmlConstants.MsdataPrefix, "rowOrder", row.XmlRowID.ToString());
1169 string modeName = null;
1170 if (row.RowState == DataRowState.Modified)
1171 modeName = "modified";
1172 else if (row.RowState == DataRowState.Added)
1173 modeName = "inserted";
1175 if (version != DataRowVersion.Original && modeName != null)
1176 WriteAttributeString (writer, mode, XmlConstants.DiffgrNamespace, XmlConstants.DiffgrPrefix, "hasChanges", modeName);
1180 private void WriteStartElement (XmlWriter writer, XmlWriteMode mode, string nspc, string prefix, string name)
1182 writer.WriteStartElement (prefix, name, nspc);
1185 private void WriteAttributeString (XmlWriter writer, XmlWriteMode mode, string nspc, string prefix, string name, string stringValue)
1188 case XmlWriteMode.WriteSchema:
1189 writer.WriteAttributeString (prefix, name, nspc);
1191 case XmlWriteMode.DiffGram:
1192 writer.WriteAttributeString (prefix, name, nspc,stringValue);
1195 writer.WriteAttributeString (name, stringValue);
1200 internal void WriteIndividualTableContent (XmlWriter writer, DataTable table, XmlWriteMode mode)
1202 ((XmlTextWriter)writer).Formatting = Formatting.Indented;
1204 if (mode == XmlWriteMode.DiffGram) {
1205 SetTableRowsID (table);
1206 WriteDiffGramElement (writer);
1209 WriteStartElement (writer, mode, Namespace, Prefix, XmlConvert.EncodeName (DataSetName));
1211 WriteTable (writer, table, mode, DataRowVersion.Default);
1213 if (mode == XmlWriteMode.DiffGram) {
1214 writer.WriteEndElement (); //DataSet name
1215 if (HasChanges (DataRowState.Modified | DataRowState.Deleted)) {
1217 DataSet beforeDS = GetChanges (DataRowState.Modified | DataRowState.Deleted);
1218 WriteStartElement (writer, XmlWriteMode.DiffGram, XmlConstants.DiffgrNamespace, XmlConstants.DiffgrPrefix, "before");
1219 WriteTable (writer, beforeDS.Tables [table.TableName], mode, DataRowVersion.Original);
1220 writer.WriteEndElement ();
1223 writer.WriteEndElement (); // DataSet name or diffgr:diffgram
1226 XmlSchema IXmlSerializable.GetSchema ()
1228 return BuildSchema ();
1231 XmlSchema BuildSchema ()
1233 return BuildSchema (Tables, Relations);
1236 internal XmlSchema BuildSchema (DataTableCollection tables, DataRelationCollection relations)
1238 string constraintPrefix = "";
1239 XmlSchema schema = new XmlSchema ();
1241 schema.Namespaces.Add("xs", XmlSchema.Namespace);
1242 schema.Namespaces.Add(XmlConstants.MsdataPrefix, XmlConstants.MsdataNamespace);
1244 if (Namespace != "") {
1245 schema.AttributeFormDefault = XmlSchemaForm.Qualified;
1246 schema.ElementFormDefault = XmlSchemaForm.Qualified;
1247 schema.TargetNamespace = Namespace;
1248 schema.Namespaces.Add(XmlConstants.TnsPrefix, Namespace);
1249 constraintPrefix = XmlConstants.TnsPrefix + ":";
1252 // Namespaces defined in tables and columns
1253 // FIXME: What if the same prefix is mapped? Create another
1254 // prefix and apply them. (But how?)
1255 foreach (DataTable dt in tables) {
1256 if (dt.Namespace != String.Empty)
1257 schema.Namespaces.Add (dt.Prefix, dt.Namespace);
1258 foreach (DataColumn col in dt.Columns)
1259 if (col.Namespace != String.Empty)
1260 schema.Namespaces.Add (col.Prefix, col.Namespace);
1263 // set the schema id
1264 schema.Id = DataSetName;
1265 XmlDocument doc = new XmlDocument ();
1266 XmlAttribute xmlnsAttr = doc.CreateAttribute("xmlns");
1267 xmlnsAttr.Value = Namespace;
1269 schema.UnhandledAttributes = new XmlAttribute[] {xmlnsAttr};
1271 XmlSchemaElement elem = new XmlSchemaElement ();
1272 elem.Name = XmlConvert.EncodeName (DataSetName);
1274 XmlAttribute[] atts = new XmlAttribute [2];
1275 atts[0] = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.IsDataSet, XmlConstants.MsdataNamespace);
1276 atts[0].Value = "true";
1278 atts[1] = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.Locale, XmlConstants.MsdataNamespace);
1279 atts[1].Value = locale.Name;
1281 elem.UnhandledAttributes = atts;
1283 schema.Items.Add (elem);
1285 XmlSchemaComplexType complex = new XmlSchemaComplexType ();
1286 elem.SchemaType = complex;
1288 XmlSchemaChoice choice = new XmlSchemaChoice ();
1289 complex.Particle = choice;
1290 choice.MaxOccursString = XmlConstants.Unbounded;
1292 //Write out schema for each table in order
1293 foreach (DataTable table in tables) {
1294 bool isTopLevel = true;
1295 foreach (DataRelation rel in table.ParentRelations) {
1303 choice.Items.Add (GetTableSchema (doc, table));
1307 AddConstraintsToSchema (elem, constraintPrefix, tables, relations);
1311 // Add all constraints in all tables to the schema.
1312 private void AddConstraintsToSchema (XmlSchemaElement elem, string constraintPrefix, DataTableCollection tables, DataRelationCollection relations)
1314 // first add all unique constraints.
1315 Hashtable uniqueNames = AddUniqueConstraints (elem, constraintPrefix, tables);
1316 // Add all foriegn key constraints.
1317 AddForeignKeys (uniqueNames, elem, constraintPrefix, relations);
1320 // Add unique constaraints to the schema.
1321 // return hashtable with the names of all XmlSchemaUnique elements we created.
1322 private Hashtable AddUniqueConstraints (XmlSchemaElement elem, string constraintPrefix, DataTableCollection tables)
1324 XmlDocument doc = new XmlDocument();
1325 Hashtable uniqueNames = new Hashtable();
1326 foreach (DataTable table in tables) {
1328 foreach (Constraint constaint in table.Constraints) {
1330 if (constaint is UniqueConstraint) {
1331 ArrayList attrs = new ArrayList ();
1332 XmlAttribute attrib;
1333 UniqueConstraint uqConst = (UniqueConstraint)constaint;
1334 XmlSchemaUnique uniq = new XmlSchemaUnique ();
1336 // if column of the constraint is hidden do not write the constraint.
1337 bool isHidden = false;
1338 foreach (DataColumn column in uqConst.Columns) {
1339 if (column.ColumnMapping == MappingType.Hidden) {
1348 // if constaraint name do not exist in the hashtable we can use it.
1349 if (!uniqueNames.ContainsKey (uqConst.ConstraintName)) {
1350 uniq.Name = uqConst.ConstraintName;
1352 // generate new constraint name for the XmlSchemaUnique element.
1354 uniq.Name = uqConst.Table.TableName + "_" + uqConst.ConstraintName;
1355 attrib = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.ConstraintName, XmlConstants.MsdataNamespace);
1356 attrib.Value = uqConst.ConstraintName;
1359 if (uqConst.IsPrimaryKey) {
1360 attrib = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.PrimaryKey, XmlConstants.MsdataNamespace);
1361 attrib.Value = "true";
1365 uniq.UnhandledAttributes = (XmlAttribute[])attrs.ToArray (typeof (XmlAttribute));
1367 uniq.Selector = new XmlSchemaXPath();
1368 uniq.Selector.XPath = ".//"+constraintPrefix + uqConst.Table.TableName;
1369 XmlSchemaXPath field;
1370 foreach (DataColumn column in uqConst.Columns) {
1371 field = new XmlSchemaXPath();
1372 field.XPath = constraintPrefix+column.ColumnName;
1373 uniq.Fields.Add(field);
1376 elem.Constraints.Add (uniq);
1377 uniqueNames.Add (uniq.Name, null);
1384 // Add the foriegn keys to the schema.
1385 private void AddForeignKeys (Hashtable uniqueNames, XmlSchemaElement elem, string constraintPrefix, DataRelationCollection relations)
1387 if (relations == null) return;
1389 XmlDocument doc = new XmlDocument();
1390 foreach (DataRelation rel in relations) {
1392 if (rel.ParentKeyConstraint == null || rel.ChildKeyConstraint == null)
1395 ArrayList attrs = new ArrayList ();
1396 XmlAttribute attrib;
1397 XmlSchemaKeyref keyRef = new XmlSchemaKeyref();
1398 keyRef.Name = rel.RelationName;
1399 ForeignKeyConstraint fkConst = rel.ChildKeyConstraint;
1400 UniqueConstraint uqConst = rel.ParentKeyConstraint;
1402 string concatName = rel.ParentTable.TableName + "_" + uqConst.ConstraintName;
1403 // first try to find the concatenated name. If we didn't find it - use constraint name.
1404 if (uniqueNames.ContainsKey (concatName)) {
1405 keyRef.Refer = new XmlQualifiedName(concatName);
1408 keyRef.Refer = new XmlQualifiedName(uqConst.ConstraintName);
1412 attrib = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.IsNested, XmlConstants.MsdataNamespace);
1413 attrib.Value = "true";
1417 keyRef.Selector = new XmlSchemaXPath();
1418 keyRef.Selector.XPath = ".//" + constraintPrefix + rel.ChildTable.TableName;
1419 XmlSchemaXPath field;
1420 foreach (DataColumn column in rel.ChildColumns) {
1421 field = new XmlSchemaXPath();
1422 field.XPath = constraintPrefix+column.ColumnName;
1423 keyRef.Fields.Add(field);
1425 keyRef.UnhandledAttributes = (XmlAttribute[])attrs.ToArray (typeof (XmlAttribute));
1426 elem.Constraints.Add (keyRef);
1430 private XmlSchemaElement GetTableSchema (XmlDocument doc, DataTable table)
1436 SplitColumns (table, out atts, out elements, out simple);
1438 XmlSchemaElement elem = new XmlSchemaElement ();
1439 elem.Name = table.TableName;
1441 XmlSchemaComplexType complex = new XmlSchemaComplexType ();
1442 elem.SchemaType = complex;
1444 //TODO - what about the simple content?
1445 if (simple != null) {
1446 // add simpleContent
1447 XmlSchemaSimpleContent simpleContent = new XmlSchemaSimpleContent();
1448 complex.ContentModel = simpleContent;
1450 // add column name attribute
1451 XmlAttribute[] xlmAttrs = new XmlAttribute [2];
1452 xlmAttrs[0] = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.ColumnName, XmlConstants.MsdataNamespace);
1453 xlmAttrs[0].Value = simple.ColumnName;
1455 // add ordinal attribute
1456 xlmAttrs[1] = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.Ordinal, XmlConstants.MsdataNamespace);
1457 xlmAttrs[1].Value = simple.Ordinal.ToString();
1458 simpleContent.UnhandledAttributes = xlmAttrs;
1462 XmlSchemaSimpleContentExtension extension = new XmlSchemaSimpleContentExtension();
1463 simpleContent.Content = extension;
1464 extension.BaseTypeName = MapType (simple.DataType);
1468 //A sequence of element types or a simple content node
1470 XmlSchemaSequence seq = new XmlSchemaSequence ();
1471 complex.Particle = seq;
1473 foreach (DataColumn col in elements) {
1475 // Add element for the column.
1476 XmlSchemaElement colElem = new XmlSchemaElement ();
1477 ArrayList xattrs = new ArrayList();
1479 colElem.Name = col.ColumnName;
1481 if (col.ColumnName != col.Caption && col.Caption != String.Empty) {
1482 xattr = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.Caption, XmlConstants.MsdataNamespace);
1483 xattr.Value = col.Caption;
1487 if (col.AutoIncrement == true) {
1488 xattr = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.AutoIncrement, XmlConstants.MsdataNamespace);
1489 xattr.Value = "true";
1493 if (col.AutoIncrementSeed != 0) {
1494 xattr = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.AutoIncrementSeed, XmlConstants.MsdataNamespace);
1495 xattr.Value = col.AutoIncrementSeed.ToString();
1499 if (col.DefaultValue.ToString () != String.Empty)
1500 colElem.DefaultValue = col.DefaultValue.ToString ();
1502 if (col.MaxLength < 0)
1503 colElem.SchemaTypeName = MapType (col.DataType);
1505 if (colElem.SchemaTypeName == XmlConstants.QnString && col.DataType != typeof (string)
1506 && col.DataType != typeof (char)) {
1507 xattr = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.DataType, XmlConstants.MsdataNamespace);
1508 xattr.Value = col.DataType.ToString();
1512 if (col.AllowDBNull) {
1513 colElem.MinOccurs = 0;
1516 //writer.WriteAttributeString (XmlConstants.MsdataPrefix,
1517 // XmlConstants.Ordinal,
1518 // XmlConstants.MsdataNamespace,
1519 // col.Ordinal.ToString ());
1521 // Write SimpleType if column have MaxLength
1522 if (col.MaxLength > -1) {
1523 colElem.SchemaType = GetTableSimpleType (doc, col);
1526 colElem.UnhandledAttributes = (XmlAttribute[])xattrs.ToArray(typeof (XmlAttribute));
1527 seq.Items.Add (colElem);
1530 foreach (DataRelation rel in table.ChildRelations) {
1532 seq.Items.Add(GetTableSchema (doc, rel.ChildTable));
1537 //Then a list of attributes
1538 foreach (DataColumn col in atts) {
1539 //<xs:attribute name=col.ColumnName form="unqualified" type=MappedType/>
1540 XmlSchemaAttribute att = new XmlSchemaAttribute ();
1541 att.Name = col.ColumnName;
1542 att.Form = XmlSchemaForm.Unqualified;
1543 att.SchemaTypeName = MapType (col.DataType);
1544 complex.Attributes.Add (att);
1550 private XmlSchemaSimpleType GetTableSimpleType (XmlDocument doc, DataColumn col)
1553 XmlSchemaSimpleType simple = new XmlSchemaSimpleType ();
1556 XmlSchemaSimpleTypeRestriction restriction = new XmlSchemaSimpleTypeRestriction ();
1557 restriction.BaseTypeName = MapType (col.DataType);
1560 XmlSchemaMaxLengthFacet max = new XmlSchemaMaxLengthFacet ();
1561 max.Value = XmlConvert.ToString (col.MaxLength);
1562 restriction.Facets.Add (max);
1564 simple.Content = restriction;
1568 private void DoWriteXmlSchema (XmlWriter writer)
1570 BuildSchema ().Write (writer);
1574 /// Helper function to split columns into attributes elements and simple
1577 private void SplitColumns (DataTable table,
1579 out ArrayList elements,
1580 out DataColumn simple)
1582 //The columns can be attributes, hidden, elements, or simple content
1583 //There can be 0-1 simple content cols or 0-* elements
1584 atts = new System.Collections.ArrayList ();
1585 elements = new System.Collections.ArrayList ();
1588 //Sort out the columns
1589 foreach (DataColumn col in table.Columns) {
1590 switch (col.ColumnMapping) {
1591 case MappingType.Attribute:
1594 case MappingType.Element:
1597 case MappingType.SimpleContent:
1598 if (simple != null) {
1599 throw new System.InvalidOperationException ("There may only be one simple content element");
1604 //ignore Hidden elements
1610 private void WriteDiffGramElement(XmlWriter writer)
1612 WriteStartElement (writer, XmlWriteMode.DiffGram, XmlConstants.DiffgrNamespace, XmlConstants.DiffgrPrefix, "diffgram");
1613 WriteAttributeString(writer, XmlWriteMode.DiffGram, null, "xmlns", XmlConstants.MsdataPrefix, XmlConstants.MsdataNamespace);
1616 private void SetRowsID()
1618 foreach (DataTable Table in Tables)
1619 SetTableRowsID (Table);
1622 private void SetTableRowsID (DataTable Table)
1625 foreach (DataRow Row in Table.Rows) {
1626 Row.XmlRowID = dataRowID;
1632 private XmlQualifiedName MapType (Type type)
1634 switch (Type.GetTypeCode (type)) {
1635 case TypeCode.String: return XmlConstants.QnString;
1636 case TypeCode.Int16: return XmlConstants.QnShort;
1637 case TypeCode.Int32: return XmlConstants.QnInt;
1638 case TypeCode.Int64: return XmlConstants.QnLong;
1639 case TypeCode.Boolean: return XmlConstants.QnBoolean;
1640 case TypeCode.Byte: return XmlConstants.QnUnsignedByte;
1641 //case TypeCode.Char: return XmlConstants.QnChar;
1642 case TypeCode.DateTime: return XmlConstants.QnDateTime;
1643 case TypeCode.Decimal: return XmlConstants.QnDecimal;
1644 case TypeCode.Double: return XmlConstants.QnDouble;
1645 case TypeCode.SByte: return XmlConstants.QnSbyte;
1646 case TypeCode.Single: return XmlConstants.QnFloat;
1647 case TypeCode.UInt16: return XmlConstants.QnUsignedShort;
1648 case TypeCode.UInt32: return XmlConstants.QnUnsignedInt;
1649 case TypeCode.UInt64: return XmlConstants.QnUnsignedLong;
1652 if (typeof (TimeSpan) == type)
1653 return XmlConstants.QnDuration;
1654 else if (typeof (System.Uri) == type)
1655 return XmlConstants.QnUri;
1656 else if (typeof (byte[]) == type)
1657 return XmlConstants.QnBase64Binary;
1658 else if (typeof (XmlQualifiedName) == type)
1659 return XmlConstants.QnXmlQualifiedName;
1661 return XmlConstants.QnString;
1664 #endregion //Private Xml Serialisation