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>
12 // (C) Ximian, Inc. 2002
13 // Copyright (C) Tim Coleman, 2002, 2003
17 using System.Collections;
18 using System.ComponentModel;
19 using System.Globalization;
20 using System.Threading;
22 using System.Runtime.Serialization;
24 using System.Xml.Schema;
25 using System.Xml.Serialization;
26 using System.Data.Common;
28 namespace System.Data {
31 [DefaultProperty ("DataSetName")]
33 public class DataSet : MarshalByValueComponent, IListSource,
34 ISupportInitialize, ISerializable, IXmlSerializable
36 private string dataSetName;
37 private string _namespace = "";
38 private string prefix;
39 private bool caseSensitive;
40 private bool enforceConstraints = true;
41 private DataTableCollection tableCollection;
42 private DataRelationCollection relationCollection;
43 private PropertyCollection properties;
44 private DataViewManager defaultView;
45 private CultureInfo locale = System.Threading.Thread.CurrentThread.CurrentCulture;
49 public DataSet () : this ("NewDataSet")
53 public DataSet (string name)
56 tableCollection = new DataTableCollection (this);
57 relationCollection = new DataRelationCollection.DataSetRelationCollection (this);
58 properties = new PropertyCollection ();
59 this.prefix = String.Empty;
61 this.Locale = CultureInfo.CurrentCulture;
65 protected DataSet (SerializationInfo info, StreamingContext context) : this ()
67 throw new NotImplementedException ();
70 #endregion // Constructors
72 #region Public Properties
74 [DataCategory ("Data")]
75 [DataSysDescription ("Indicates whether comparing strings within the DataSet is case sensitive.")]
76 [DefaultValue (false)]
77 public bool CaseSensitive {
82 foreach (DataTable T in Tables) {
83 if (T.VirginCaseSensitive)
84 T.CaseSensitive = value;
87 caseSensitive = value;
89 foreach (DataTable table in Tables) {
90 foreach (Constraint c in table.Constraints)
91 c.AssertConstraint ();
97 [DataCategory ("Data")]
98 [DataSysDescription ("The name of this DataSet.")]
100 public string DataSetName {
101 get { return dataSetName; }
102 set { dataSetName = value; }
105 [DataSysDescription ("Indicates a custom \"view\" of the data contained by the DataSet. This view allows filtering, searching, and navigating through the custom data view.")]
107 public DataViewManager DefaultViewManager {
109 if (defaultView == null)
110 defaultView = new DataViewManager (this);
115 [DataSysDescription ("Indicates whether constraint rules are to be followed.")]
116 [DefaultValue (true)]
117 public bool EnforceConstraints {
118 get { return enforceConstraints; }
120 if (value != enforceConstraints) {
121 enforceConstraints = value;
123 foreach (DataTable table in Tables) {
124 foreach (Constraint c in table.Constraints)
125 c.AssertConstraint ();
133 [DataCategory ("Data")]
134 [DataSysDescription ("The collection that holds custom user information.")]
135 public PropertyCollection ExtendedProperties {
136 get { return properties; }
140 [DataSysDescription ("Indicates that the DataSet has errors.")]
141 public bool HasErrors {
144 for (int i = 0; i < Tables.Count; i++) {
145 if (Tables[i].HasErrors)
152 [DataCategory ("Data")]
153 [DataSysDescription ("Indicates a locale under which to compare strings within the DataSet.")]
154 public CultureInfo Locale {
159 if (locale == null || !locale.Equals (value)) {
160 // TODO: check if the new locale is valid
161 // TODO: update locale of all tables
167 public void Merge (DataRow[] rows)
169 Merge (rows, false, MissingSchemaAction.Add);
172 public void Merge (DataSet dataSet)
174 Merge (dataSet, false, MissingSchemaAction.Add);
177 public void Merge (DataTable table)
179 Merge (table, false, MissingSchemaAction.Add);
182 public void Merge (DataSet dataSet, bool preserveChanges)
184 Merge (dataSet, preserveChanges, MissingSchemaAction.Add);
188 public void Merge (DataRow[] rows, bool preserveChanges, MissingSchemaAction missingSchemaAction)
191 throw new ArgumentNullException ("rows");
192 if (!IsLegalSchemaAction (missingSchemaAction))
193 throw new ArgumentOutOfRangeException ("missingSchemaAction");
195 MergeManager.Merge (this, rows, preserveChanges, missingSchemaAction);
199 public void Merge (DataSet dataSet, bool preserveChanges, MissingSchemaAction missingSchemaAction)
202 throw new ArgumentNullException ("dataSet");
203 if (!IsLegalSchemaAction (missingSchemaAction))
204 throw new ArgumentOutOfRangeException ("missingSchemaAction");
206 MergeManager.Merge (this, dataSet, preserveChanges, missingSchemaAction);
210 public void Merge (DataTable table, bool preserveChanges, MissingSchemaAction missingSchemaAction)
213 throw new ArgumentNullException ("table");
214 if (!IsLegalSchemaAction (missingSchemaAction))
215 throw new ArgumentOutOfRangeException ("missingSchemaAction");
217 MergeManager.Merge (this, table, preserveChanges, missingSchemaAction);
220 private static bool IsLegalSchemaAction (MissingSchemaAction missingSchemaAction)
222 if (missingSchemaAction == MissingSchemaAction.Add || missingSchemaAction == MissingSchemaAction.AddWithKey
223 || missingSchemaAction == MissingSchemaAction.Error || missingSchemaAction == MissingSchemaAction.Ignore)
228 [DataCategory ("Data")]
229 [DataSysDescription ("Indicates the XML uri namespace for the root element pointed at by this DataSet.")]
231 public string Namespace {
233 get { return _namespace; }
236 //TODO - trigger an event if this happens?
241 [DataCategory ("Data")]
242 [DataSysDescription ("Indicates the prefix of the namespace used for this DataSet.")]
244 public string Prefix {
246 get { return prefix; }
249 //TODO - trigger an event if this happens?
252 value = string.Empty;
254 if (value != this.prefix)
255 RaisePropertyChanging ("Prefix");
260 [DataCategory ("Data")]
261 [DataSysDescription ("The collection that holds the relations for this DatSet.")]
262 [DesignerSerializationVisibility (DesignerSerializationVisibility.Content)]
263 public DataRelationCollection Relations {
265 return relationCollection;
270 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
271 public override ISite Site {
274 throw new NotImplementedException ();
279 throw new NotImplementedException ();
283 [DataCategory ("Data")]
284 [DataSysDescription ("The collection that holds the tables for this DataSet.")]
285 [DesignerSerializationVisibility (DesignerSerializationVisibility.Content)]
286 public DataTableCollection Tables {
287 get { return tableCollection; }
290 #endregion // Public Properties
292 #region Public Methods
295 public void AcceptChanges ()
297 foreach (DataTable tempTable in tableCollection)
298 tempTable.AcceptChanges ();
303 // TODO: if currently bound to a XmlDataDocument
304 // throw a NotSupportedException
305 for (int t = 0; t < tableCollection.Count; t++) {
306 tableCollection[t].Clear ();
310 public virtual DataSet Clone ()
312 DataSet Copy = new DataSet ();
313 CopyProperties (Copy);
315 foreach (DataTable Table in Tables) {
316 Copy.Tables.Add (Table.Clone ());
319 //Copy Relationships between tables after existance of tables
320 //and setting properties correctly
321 CopyRelations (Copy);
326 // Copies both the structure and data for this DataSet.
327 public DataSet Copy ()
329 DataSet Copy = new DataSet ();
330 CopyProperties (Copy);
332 // Copy DatSet's tables
333 foreach (DataTable Table in Tables)
334 Copy.Tables.Add (Table.Copy ());
336 //Copy Relationships between tables after existance of tables
337 //and setting properties correctly
338 CopyRelations (Copy);
344 private void CopyProperties (DataSet Copy)
346 Copy.CaseSensitive = CaseSensitive;
347 //Copy.Container = Container
348 Copy.DataSetName = DataSetName;
349 //Copy.DefaultViewManager
351 Copy.EnforceConstraints = EnforceConstraints;
352 //Copy.ExtendedProperties
354 //Copy.Locale = Locale;
355 Copy.Namespace = Namespace;
356 Copy.Prefix = Prefix;
362 private void CopyRelations (DataSet Copy)
365 //Creation of the relation contains some of the properties, and the constructor
366 //demands these values. instead changing the DataRelation constructor and behaviour the
367 //parameters are pre-configured and sent to the most general constructor
369 foreach (DataRelation MyRelation in this.Relations) {
370 string pTable = MyRelation.ParentTable.TableName;
371 string cTable = MyRelation.ChildTable.TableName;
372 DataColumn[] P_DC = new DataColumn[MyRelation.ParentColumns.Length];
373 DataColumn[] C_DC = new DataColumn[MyRelation.ChildColumns.Length];
376 foreach (DataColumn DC in MyRelation.ParentColumns) {
377 P_DC[i]=Copy.Tables[pTable].Columns[DC.ColumnName];
383 foreach (DataColumn DC in MyRelation.ChildColumns) {
384 C_DC[i]=Copy.Tables[cTable].Columns[DC.ColumnName];
388 DataRelation cRel = new DataRelation (MyRelation.RelationName, P_DC, C_DC);
389 //cRel.ChildColumns = MyRelation.ChildColumns;
390 //cRel.ChildTable = MyRelation.ChildTable;
391 //cRel.ExtendedProperties = cRel.ExtendedProperties;
392 //cRel.Nested = MyRelation.Nested;
393 //cRel.ParentColumns = MyRelation.ParentColumns;
394 //cRel.ParentTable = MyRelation.ParentTable;
396 Copy.Relations.Add (cRel);
403 public DataSet GetChanges ()
405 return GetChanges (DataRowState.Added | DataRowState.Deleted | DataRowState.Modified);
409 public DataSet GetChanges (DataRowState rowStates)
411 if (!HasChanges (rowStates))
414 DataSet copySet = Clone ();
415 Hashtable addedRows = new Hashtable ();
417 IEnumerator tableEnumerator = Tables.GetEnumerator ();
420 while (tableEnumerator.MoveNext ()) {
421 origTable = (DataTable)tableEnumerator.Current;
422 copyTable = copySet.Tables[origTable.TableName];
424 // Look for relations that have this table as child
425 IEnumerator relations = origTable.ParentRelations.GetEnumerator ();
427 IEnumerator rowEnumerator = origTable.Rows.GetEnumerator ();
428 while (rowEnumerator.MoveNext ()) {
429 DataRow row = (DataRow)rowEnumerator.Current;
431 if (row.IsRowChanged (rowStates))
432 AddChangedRow (addedRows, copySet, copyTable, relations, row);
438 void AddChangedRow (Hashtable addedRows, DataSet copySet, DataTable copyTable, IEnumerator relations, DataRow row)
440 if (addedRows.ContainsKey (row)) return;
443 while (relations.MoveNext ()) {
444 DataRow parentRow = row.GetParentRow ((DataRelation) relations.Current);
445 if (parentRow == null || addedRows.ContainsKey (parentRow)) continue;
446 DataTable parentCopyTable = copySet.Tables [parentRow.Table.TableName];
447 AddChangedRow (addedRows, copySet, parentCopyTable, parentRow.Table.ParentRelations.GetEnumerator (), parentRow);
450 DataRow newRow = copyTable.NewRow ();
451 copyTable.Rows.Add (newRow);
452 row.CopyValuesToRow (newRow);
453 newRow.XmlRowID = row.XmlRowID;
454 addedRows.Add (row,row);
459 public DataTableReader GetDataReader (DataTable[] dataTables)
461 throw new NotImplementedException ();
465 public DataTableReader GetDataReader ()
467 throw new NotImplementedException ();
471 public string GetXml ()
473 StringWriter Writer = new StringWriter ();
475 // Sending false for not printing the Processing instruction
476 WriteXml (Writer, XmlWriteMode.IgnoreSchema, false);
477 return Writer.ToString ();
480 public string GetXmlSchema ()
482 StringWriter Writer = new StringWriter ();
483 WriteXmlSchema (Writer);
484 return Writer.ToString ();
488 public bool HasChanges ()
490 return HasChanges (DataRowState.Added | DataRowState.Deleted | DataRowState.Modified);
494 public bool HasChanges (DataRowState rowState)
496 if (((int)rowState & 0xffffffe0) != 0)
497 throw new ArgumentOutOfRangeException ("rowState");
499 DataTableCollection tableCollection = Tables;
501 DataRowCollection rowCollection;
504 for (int i = 0; i < tableCollection.Count; i++) {
505 table = tableCollection[i];
506 rowCollection = table.Rows;
507 for (int j = 0; j < rowCollection.Count; j++) {
508 row = rowCollection[j];
509 if ((row.RowState & rowState) != 0)
518 public void InferXmlSchema (XmlReader reader, string[] nsArray)
522 public void InferXmlSchema (Stream stream, string[] nsArray)
524 InferXmlSchema (new XmlTextReader (stream), nsArray);
527 public void InferXmlSchema (TextReader reader, string[] nsArray)
529 InferXmlSchema (new XmlTextReader (reader), nsArray);
532 public void InferXmlSchema (string fileName, string[] nsArray)
534 XmlTextReader reader = new XmlTextReader (fileName);
536 InferXmlSchema (reader, nsArray);
544 public void Load (IDataReader reader, LoadOption loadOption, DataTable[] tables)
546 throw new NotImplementedException ();
550 public void Load (IDataReader reader, LoadOption loadOption, string[] tables)
552 throw new NotImplementedException ();
556 public virtual void RejectChanges ()
559 bool oldEnforceConstraints = this.EnforceConstraints;
560 this.EnforceConstraints = false;
562 for (i = 0; i < this.Tables.Count;i++)
563 this.Tables[i].RejectChanges ();
565 this.EnforceConstraints = oldEnforceConstraints;
568 public virtual void Reset ()
570 IEnumerator constraintEnumerator;
572 // first we remove all ForeignKeyConstraints (if we will not do that
573 // we will get an exception when clearing the tables).
574 for (int i = 0; i < Tables.Count; i++) {
575 ConstraintCollection cc = Tables[i].Constraints;
576 for (int j = 0; j < cc.Count; j++) {
577 if (cc[j] is ForeignKeyConstraint)
587 public void WriteXml (Stream stream)
589 XmlTextWriter writer = new XmlTextWriter (stream, null);
590 writer.Formatting = Formatting.Indented;
595 /// Writes the current data for the DataSet to the specified file.
597 /// <param name="filename">Fully qualified filename to write to</param>
598 public void WriteXml (string fileName)
600 XmlTextWriter writer = new XmlTextWriter (fileName, null);
601 writer.Formatting = Formatting.Indented;
607 public void WriteXml (TextWriter writer)
609 XmlTextWriter xwriter = new XmlTextWriter (writer);
610 xwriter.Formatting = Formatting.Indented;
614 public void WriteXml (XmlWriter writer)
616 WriteXml (writer, XmlWriteMode.IgnoreSchema, true);
619 public void WriteXml (string filename, XmlWriteMode mode)
621 XmlTextWriter writer = new XmlTextWriter (filename, null);
622 writer.Formatting = Formatting.Indented;
623 WriteXml (writer, mode, true);
626 public void WriteXml (Stream stream, XmlWriteMode mode)
628 XmlTextWriter writer = new XmlTextWriter (stream, null);
629 writer.Formatting = Formatting.Indented;
630 WriteXml (writer, mode, true);
633 public void WriteXml (TextWriter writer, XmlWriteMode mode)
635 XmlTextWriter xwriter = new XmlTextWriter (writer);
636 xwriter.Formatting = Formatting.Indented;
637 WriteXml (xwriter, mode, true);
640 public void WriteXml (XmlWriter writer, XmlWriteMode mode)
642 WriteXml (writer, mode, true);
645 internal void WriteXml (Stream stream, XmlWriteMode mode, bool writePI)
647 XmlTextWriter writer = new XmlTextWriter (stream, null);
648 writer.Formatting = Formatting.Indented;
649 WriteXml (writer, mode, writePI);
652 internal void WriteXml (string fileName, XmlWriteMode mode, bool writePI)
654 XmlTextWriter writer = new XmlTextWriter (fileName, null);
655 writer.Formatting = Formatting.Indented;
656 WriteXml (writer, mode, writePI);
661 internal void WriteXml (TextWriter writer, XmlWriteMode mode, bool writePI)
663 XmlTextWriter xwriter = new XmlTextWriter (writer);
664 xwriter.Formatting = Formatting.Indented;
665 WriteXml (xwriter, mode, writePI);
668 internal void WriteXml (XmlWriter writer, XmlWriteMode mode, bool writePI)
670 if (writePI && (writer.WriteState == WriteState.Start))
671 writer.WriteStartDocument (true);
673 if (mode == XmlWriteMode.DiffGram) {
675 WriteDiffGramElement(writer);
678 WriteStartElement (writer, mode, Namespace, Prefix, XmlConvert.EncodeName (DataSetName));
680 /*********************************************************
681 * This is a patch for interoperability with ms.net. *
682 * Because in web services the .net client expects this *
683 * atrribute even if namespace is an empty string *
684 ********************************************************/
685 if (Namespace == null || Namespace.Length == 0)
686 WriteAttributeString (writer, mode, null, null, "xmlns", Namespace);
689 if (mode == XmlWriteMode.WriteSchema) {
690 DoWriteXmlSchema (writer);
693 WriteTables (writer, mode, Tables, DataRowVersion.Default);
694 if (mode == XmlWriteMode.DiffGram) {
695 writer.WriteEndElement (); //DataSet name
696 if (HasChanges(DataRowState.Modified | DataRowState.Deleted)) {
698 DataSet beforeDS = GetChanges (DataRowState.Modified | DataRowState.Deleted);
699 WriteStartElement (writer, XmlWriteMode.DiffGram, XmlConstants.DiffgrNamespace, XmlConstants.DiffgrPrefix, "before");
700 WriteTables (writer, mode, beforeDS.Tables, DataRowVersion.Original);
701 writer.WriteEndElement ();
704 writer.WriteEndElement (); // DataSet name or diffgr:diffgram
707 public void WriteXmlSchema (Stream stream)
709 XmlTextWriter writer = new XmlTextWriter (stream, null );
710 writer.Formatting = Formatting.Indented;
711 WriteXmlSchema (writer);
714 public void WriteXmlSchema (string fileName)
716 XmlTextWriter writer = new XmlTextWriter (fileName, null);
717 writer.Formatting = Formatting.Indented;
718 WriteXmlSchema (writer);
721 public void WriteXmlSchema (TextWriter writer)
723 XmlTextWriter xwriter = new XmlTextWriter (writer);
724 xwriter.Formatting = Formatting.Indented;
725 WriteXmlSchema (xwriter);
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 writer.WriteStartDocument ();
733 DoWriteXmlSchema (writer);
735 writer.WriteEndDocument ();
738 public void ReadXmlSchema (Stream stream)
740 XmlReader reader = new XmlTextReader (stream, null);
741 ReadXmlSchema (reader);
744 public void ReadXmlSchema (string str)
746 XmlReader reader = new XmlTextReader (str);
747 ReadXmlSchema (reader);
750 public void ReadXmlSchema (TextReader treader)
752 XmlReader reader = new XmlTextReader (treader);
753 ReadXmlSchema (reader);
756 public void ReadXmlSchema (XmlReader reader)
758 XmlSchemaMapper SchemaMapper = new XmlSchemaMapper (this);
759 SchemaMapper.Read (reader);
762 public XmlReadMode ReadXml (Stream stream)
764 return ReadXml (new XmlTextReader (stream));
767 public XmlReadMode ReadXml (string str)
769 return ReadXml (new XmlTextReader (str));
772 public XmlReadMode ReadXml (TextReader reader)
774 return ReadXml (new XmlTextReader (reader));
777 public XmlReadMode ReadXml (XmlReader r)
779 XmlDataLoader Loader = new XmlDataLoader (this);
780 // FIXME: somekinda exception?
782 return XmlReadMode.Auto; // FIXME
784 // Check if the curent element is the process instruction (PI).
785 // if it is move to next element.
786 if (r.LocalName == "xml")
790 * If document is diffgram we will use diffgram
792 if (r.LocalName == "diffgram")
793 return ReadXml (r, XmlReadMode.DiffGram);
795 // Get the DataSet name.
796 string dataSetName = XmlConvert.DecodeName (r.LocalName);
797 DataSetName = dataSetName;
799 r.ReadStartElement ();
802 bool schemaRead = false;
803 // Check if the current element is the schema
804 if (r.LocalName == "schema") {
810 if (r.LocalName == "diffgram") {
811 return ReadXml (r, XmlReadMode.DiffGram);
814 // If the schema has been read we should read the rest of the document
816 ReadXml (r, XmlReadMode.IgnoreSchema, false);
817 return XmlReadMode.ReadSchema;
820 // Read with inferschema.
821 return ReadXml (r, XmlReadMode.InferSchema, false);
825 public XmlReadMode ReadXml (Stream stream, XmlReadMode mode)
827 return ReadXml (new XmlTextReader (stream), mode);
830 public XmlReadMode ReadXml (string str, XmlReadMode mode)
832 return ReadXml (new XmlTextReader (str), mode);
835 public XmlReadMode ReadXml (TextReader reader, XmlReadMode mode)
837 return ReadXml (new XmlTextReader (reader), mode);
841 public XmlReadMode ReadXml (XmlReader reader, XmlReadMode mode)
843 // we have to initiate the reader.
844 if (reader.ReadState == ReadState.Initial)
847 // Check if the curent element is the process instruction (PI).
848 // if it is move to next element.
849 if (reader.LocalName == "xml")
850 reader.MoveToContent();
852 XmlReadMode Result = XmlReadMode.Auto;
854 if (mode == XmlReadMode.DiffGram) {
855 if (reader.LocalName != "diffgram"){
856 reader.MoveToContent ();
857 reader.ReadStartElement (); // <DataSet>
859 reader.MoveToContent ();
860 if (reader.LocalName == "schema")
861 ReadXmlSchema (reader);
863 reader.MoveToContent ();
865 XmlDiffLoader DiffLoader = new XmlDiffLoader (this);
866 DiffLoader.Load (reader);
867 Result = XmlReadMode.DiffGram;
870 Result = ReadXml(reader, mode, true);
875 private XmlReadMode ReadXml (XmlReader r, XmlReadMode mode, bool readDataSet) {
878 string dataSetName = XmlConvert.DecodeName (r.LocalName);
879 DataSetName = dataSetName;
880 // get the Namespace of the DataSet.
881 string tmp = r.GetAttribute("xmlns");
885 r.ReadStartElement ();
889 XmlDataLoader Loader = new XmlDataLoader (this);
890 return Loader.LoadData (r, mode);
893 #endregion // Public Methods
895 #region Public Events
897 [DataCategory ("Action")]
898 [DataSysDescription ("Occurs when it is not possible to merge schemas for two tables with the same name.")]
899 public event MergeFailedEventHandler MergeFailed;
901 #endregion // Public Events
909 #endregion Destructors
911 #region IListSource methods
912 IList IListSource.GetList ()
914 return DefaultViewManager;
917 bool IListSource.ContainsListCollection {
922 #endregion IListSource methods
924 #region ISupportInitialize methods
925 public void BeginInit ()
929 public void EndInit ()
934 #region ISerializable
935 void ISerializable.GetObjectData (SerializationInfo si, StreamingContext sc)
937 throw new NotImplementedException ();
941 #region Protected Methods
942 protected void GetSerializationData (SerializationInfo info, StreamingContext context)
944 string s = info.GetValue ("XmlDiffGram", typeof (String)) as String;
945 if (s != null) ReadXmlSerializable (new XmlTextReader (new StringReader (s)));
949 protected virtual System.Xml.Schema.XmlSchema GetSchemaSerializable ()
954 protected virtual void ReadXmlSerializable (XmlReader reader)
956 ReadXml (reader, XmlReadMode.DiffGram); // FIXME
959 void IXmlSerializable.ReadXml (XmlReader reader)
962 ReadXmlSerializable(reader);
964 // the XmlSerializationReader does this lines!!!
965 //reader.MoveToContent ();
966 //reader.ReadEndElement (); // </DataSet>
969 void IXmlSerializable.WriteXml (XmlWriter writer)
971 DoWriteXmlSchema (writer);
972 WriteXml (writer, XmlWriteMode.DiffGram, true);
975 protected virtual bool ShouldSerializeRelations ()
980 protected virtual bool ShouldSerializeTables ()
986 protected internal virtual void OnPropertyChanging (PropertyChangedEventArgs pcevent)
991 protected virtual void OnRemoveRelation (DataRelation relation)
996 protected virtual void OnRemoveTable (DataTable table)
1000 protected internal virtual void OnMergeFailed (MergeFailedEventArgs e)
1002 if (MergeFailed != null)
1003 MergeFailed (this, e);
1007 protected internal void RaisePropertyChanging (string name)
1012 #region Private Xml Serialisation
1014 private string WriteObjectXml (object o)
1016 switch (Type.GetTypeCode (o.GetType ())) {
1017 case TypeCode.Boolean:
1018 return XmlConvert.ToString ((Boolean) o);
1020 return XmlConvert.ToString ((Byte) o);
1022 return XmlConvert.ToString ((Char) o);
1023 case TypeCode.DateTime:
1024 return XmlConvert.ToString ((DateTime) o);
1025 case TypeCode.Decimal:
1026 return XmlConvert.ToString ((Decimal) o);
1027 case TypeCode.Double:
1028 return XmlConvert.ToString ((Double) o);
1029 case TypeCode.Int16:
1030 return XmlConvert.ToString ((Int16) o);
1031 case TypeCode.Int32:
1032 return XmlConvert.ToString ((Int32) o);
1033 case TypeCode.Int64:
1034 return XmlConvert.ToString ((Int64) o);
1035 case TypeCode.SByte:
1036 return XmlConvert.ToString ((SByte) o);
1037 case TypeCode.Single:
1038 return XmlConvert.ToString ((Single) o);
1039 case TypeCode.UInt16:
1040 return XmlConvert.ToString ((UInt16) o);
1041 case TypeCode.UInt32:
1042 return XmlConvert.ToString ((UInt32) o);
1043 case TypeCode.UInt64:
1044 return XmlConvert.ToString ((UInt64) o);
1046 if (o is TimeSpan) return XmlConvert.ToString ((TimeSpan) o);
1047 if (o is Guid) return XmlConvert.ToString ((Guid) o);
1048 return o.ToString ();
1051 private void WriteTables (XmlWriter writer, XmlWriteMode mode, DataTableCollection tableCollection, DataRowVersion version)
1053 //Write out each table in order, providing it is not
1054 //part of another table structure via a nested parent relationship
1055 foreach (DataTable table in tableCollection) {
1056 bool isTopLevel = true;
1057 foreach (DataRelation rel in table.ParentRelations) {
1065 WriteTable ( writer, table, mode, version);
1070 private void WriteTable (XmlWriter writer, DataTable table, XmlWriteMode mode, DataRowVersion version)
1072 DataRow[] rows = new DataRow [table.Rows.Count];
1073 table.Rows.CopyTo (rows, 0);
1074 WriteTable (writer, rows, mode, version);
1077 private void WriteTable (XmlWriter writer, DataRow[] rows, XmlWriteMode mode, DataRowVersion version)
1079 //The columns can be attributes, hidden, elements, or simple content
1080 //There can be 0-1 simple content cols or 0-* elements
1081 System.Collections.ArrayList atts;
1082 System.Collections.ArrayList elements;
1083 DataColumn simple = null;
1085 if (rows.Length == 0) return;
1086 DataTable table = rows[0].Table;
1087 SplitColumns (table, out atts, out elements, out simple);
1088 //sort out the namespacing
1089 string nspc = table.Namespace.Length > 0 ? table.Namespace : Namespace;
1091 foreach (DataRow row in rows) {
1092 if (!row.HasVersion(version) ||
1093 (mode == XmlWriteMode.DiffGram && row.RowState == DataRowState.Unchanged
1094 && version == DataRowVersion.Original))
1097 // First check are all the rows null. If they are we just write empty element
1098 bool AllNulls = true;
1099 foreach (DataColumn dc in table.Columns) {
1101 if (row [dc.ColumnName, version] != DBNull.Value) {
1107 // If all of the columns were null, we have to write empty element
1109 writer.WriteElementString (table.TableName, "");
1113 WriteTableElement (writer, mode, table, row, version);
1115 foreach (DataColumn col in atts) {
1116 WriteColumnAsAttribute (writer, mode, col, row, version);
1119 if (simple != null) {
1120 writer.WriteString (WriteObjectXml (row[simple, version]));
1123 foreach (DataColumn col in elements) {
1124 WriteColumnAsElement (writer, mode, nspc, col, row, version);
1128 foreach (DataRelation relation in table.ChildRelations) {
1129 if (relation.Nested) {
1130 WriteTable (writer, row.GetChildRows (relation), mode, version);
1134 writer.WriteEndElement ();
1139 private void WriteColumnAsElement (XmlWriter writer, XmlWriteMode mode, string nspc, DataColumn col, DataRow row, DataRowVersion version)
1141 string colnspc = nspc;
1142 object rowObject = row [col, version];
1144 if (rowObject == null || rowObject == DBNull.Value)
1147 if (col.Namespace != null) {
1148 colnspc = col.Namespace;
1151 //TODO check if I can get away with write element string
1152 WriteStartElement (writer, mode, colnspc, col.Prefix, col.ColumnName);
1153 writer.WriteString (WriteObjectXml (rowObject));
1154 writer.WriteEndElement ();
1157 private void WriteColumnAsAttribute (XmlWriter writer, XmlWriteMode mode, DataColumn col, DataRow row, DataRowVersion version)
1159 WriteAttributeString (writer, mode, col.Namespace, col.Prefix, col.ColumnName, row[col, version].ToString ());
1162 private void WriteTableElement (XmlWriter writer, XmlWriteMode mode, DataTable table, DataRow row, DataRowVersion version)
1164 //sort out the namespacing
1165 string nspc = table.Namespace.Length > 0 ? table.Namespace : Namespace;
1167 WriteStartElement (writer, mode, nspc, table.Prefix, table.TableName);
1169 if (mode == XmlWriteMode.DiffGram) {
1170 WriteAttributeString (writer, mode, XmlConstants.DiffgrNamespace, XmlConstants.DiffgrPrefix, "id", table.TableName + (row.XmlRowID + 1));
1171 WriteAttributeString (writer, mode, XmlConstants.MsdataNamespace, XmlConstants.MsdataPrefix, "rowOrder", row.XmlRowID.ToString());
1172 string modeName = null;
1173 if (row.RowState == DataRowState.Modified)
1174 modeName = "modified";
1175 else if (row.RowState == DataRowState.Added)
1176 modeName = "inserted";
1178 if (version != DataRowVersion.Original && modeName != null)
1179 WriteAttributeString (writer, mode, XmlConstants.DiffgrNamespace, XmlConstants.DiffgrPrefix, "hasChanges", modeName);
1183 private void WriteStartElement (XmlWriter writer, XmlWriteMode mode, string nspc, string prefix, string name)
1185 if (nspc == null || nspc == "") {
1186 writer.WriteStartElement (name);
1188 else if (prefix != null) {
1189 writer.WriteStartElement (prefix, name, nspc);
1192 writer.WriteStartElement (writer.LookupPrefix (nspc), name, nspc);
1196 private void WriteAttributeString (XmlWriter writer, XmlWriteMode mode, string nspc, string prefix, string name, string stringValue)
1199 case XmlWriteMode.WriteSchema:
1200 writer.WriteAttributeString (prefix, name, nspc);
1202 case XmlWriteMode.DiffGram:
1203 writer.WriteAttributeString (prefix, name, nspc,stringValue);
1206 writer.WriteAttributeString (name, stringValue);
1211 internal void WriteIndividualTableContent (XmlWriter writer, DataTable table, XmlWriteMode mode)
1213 ((XmlTextWriter)writer).Formatting = Formatting.Indented;
1215 if (mode == XmlWriteMode.DiffGram) {
1216 SetTableRowsID (table);
1217 WriteDiffGramElement (writer);
1220 WriteStartElement (writer, mode, Namespace, Prefix, XmlConvert.EncodeName (DataSetName));
1222 WriteTable (writer, table, mode, DataRowVersion.Default);
1224 if (mode == XmlWriteMode.DiffGram) {
1225 writer.WriteEndElement (); //DataSet name
1226 if (HasChanges (DataRowState.Modified | DataRowState.Deleted)) {
1228 DataSet beforeDS = GetChanges (DataRowState.Modified | DataRowState.Deleted);
1229 WriteStartElement (writer, XmlWriteMode.DiffGram, XmlConstants.DiffgrNamespace, XmlConstants.DiffgrPrefix, "before");
1230 WriteTable (writer, beforeDS.Tables [table.TableName], mode, DataRowVersion.Original);
1231 writer.WriteEndElement ();
1234 writer.WriteEndElement (); // DataSet name or diffgr:diffgram
1237 XmlSchema IXmlSerializable.GetSchema ()
1239 return BuildSchema ();
1242 XmlSchema BuildSchema ()
1244 return BuildSchema (Tables, Relations);
1247 internal XmlSchema BuildSchema (DataTableCollection tables, DataRelationCollection relations)
1249 string constraintPrefix = "";
1250 XmlSchema schema = new XmlSchema ();
1252 schema.Namespaces.Add("xs", XmlSchema.Namespace);
1253 schema.Namespaces.Add(XmlConstants.MsdataPrefix, XmlConstants.MsdataNamespace);
1255 if (Namespace != "" && Namespace != null) {
1256 schema.AttributeFormDefault = XmlSchemaForm.Qualified;
1257 schema.ElementFormDefault = XmlSchemaForm.Qualified;
1258 schema.TargetNamespace = Namespace;
1259 schema.Namespaces.Add(XmlConstants.TnsPrefix, Namespace);
1260 constraintPrefix = XmlConstants.TnsPrefix + ":";
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;
1280 elem.UnhandledAttributes = atts;
1282 schema.Items.Add (elem);
1284 XmlSchemaComplexType complex = new XmlSchemaComplexType ();
1285 elem.SchemaType = complex;
1287 XmlSchemaChoice choice = new XmlSchemaChoice ();
1288 complex.Particle = choice;
1289 choice.MaxOccursString = XmlConstants.Unbounded;
1291 //Write out schema for each table in order
1292 foreach (DataTable table in tables) {
1293 bool isTopLevel = true;
1294 foreach (DataRelation rel in table.ParentRelations) {
1302 choice.Items.Add (GetTableSchema (doc, table));
1306 AddConstraintsToSchema (elem, constraintPrefix, tables, relations);
1310 // Add all constraints in all tables to the schema.
1311 private void AddConstraintsToSchema (XmlSchemaElement elem, string constraintPrefix, DataTableCollection tables, DataRelationCollection relations)
1313 // first add all unique constraints.
1314 Hashtable uniqueNames = AddUniqueConstraints (elem, constraintPrefix, tables);
1315 // Add all foriegn key constraints.
1316 AddForeignKeys (uniqueNames, elem, constraintPrefix, relations);
1319 // Add unique constaraints to the schema.
1320 // return hashtable with the names of all XmlSchemaUnique elements we created.
1321 private Hashtable AddUniqueConstraints (XmlSchemaElement elem, string constraintPrefix, DataTableCollection tables)
1323 XmlDocument doc = new XmlDocument();
1324 Hashtable uniqueNames = new Hashtable();
1325 foreach (DataTable table in tables) {
1327 foreach (Constraint constaint in table.Constraints) {
1329 if (constaint is UniqueConstraint) {
1330 ArrayList attrs = new ArrayList ();
1331 XmlAttribute attrib;
1332 UniqueConstraint uqConst = (UniqueConstraint)constaint;
1333 XmlSchemaUnique uniq = new XmlSchemaUnique ();
1335 // if column of the constraint is hidden do not write the constraint.
1336 bool isHidden = false;
1337 foreach (DataColumn column in uqConst.Columns) {
1338 if (column.ColumnMapping == MappingType.Hidden) {
1347 // if constaraint name do not exist in the hashtable we can use it.
1348 if (!uniqueNames.ContainsKey (uqConst.ConstraintName)) {
1349 uniq.Name = uqConst.ConstraintName;
1351 // generate new constraint name for the XmlSchemaUnique element.
1353 uniq.Name = uqConst.Table.TableName + "_" + uqConst.ConstraintName;
1354 attrib = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.ConstraintName, XmlConstants.MsdataNamespace);
1355 attrib.Value = uqConst.ConstraintName;
1358 if (uqConst.IsPrimaryKey) {
1359 attrib = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.PrimaryKey, XmlConstants.MsdataNamespace);
1360 attrib.Value = "true";
1364 uniq.UnhandledAttributes = (XmlAttribute[])attrs.ToArray (typeof (XmlAttribute));
1366 uniq.Selector = new XmlSchemaXPath();
1367 uniq.Selector.XPath = ".//"+constraintPrefix + uqConst.Table.TableName;
1368 XmlSchemaXPath field;
1369 foreach (DataColumn column in uqConst.Columns) {
1370 field = new XmlSchemaXPath();
1371 field.XPath = constraintPrefix+column.ColumnName;
1372 uniq.Fields.Add(field);
1375 elem.Constraints.Add (uniq);
1376 uniqueNames.Add (uniq.Name, null);
1383 // Add the foriegn keys to the schema.
1384 private void AddForeignKeys (Hashtable uniqueNames, XmlSchemaElement elem, string constraintPrefix, DataRelationCollection relations)
1386 if (relations == null) return;
1388 XmlDocument doc = new XmlDocument();
1389 foreach (DataRelation rel in relations) {
1391 if (rel.ParentKeyConstraint == null || rel.ChildKeyConstraint == null)
1394 ArrayList attrs = new ArrayList ();
1395 XmlAttribute attrib;
1396 XmlSchemaKeyref keyRef = new XmlSchemaKeyref();
1397 keyRef.Name = rel.RelationName;
1398 ForeignKeyConstraint fkConst = rel.ChildKeyConstraint;
1399 UniqueConstraint uqConst = rel.ParentKeyConstraint;
1401 string concatName = rel.ParentTable.TableName + "_" + uqConst.ConstraintName;
1402 // first try to find the concatenated name. If we didn't find it - use constraint name.
1403 if (uniqueNames.ContainsKey (concatName)) {
1404 keyRef.Refer = new XmlQualifiedName(concatName);
1407 keyRef.Refer = new XmlQualifiedName(uqConst.ConstraintName);
1411 attrib = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.IsNested, XmlConstants.MsdataNamespace);
1412 attrib.Value = "true";
1416 keyRef.Selector = new XmlSchemaXPath();
1417 keyRef.Selector.XPath = ".//" + constraintPrefix + rel.ChildTable.TableName;
1418 XmlSchemaXPath field;
1419 foreach (DataColumn column in rel.ChildColumns) {
1420 field = new XmlSchemaXPath();
1421 field.XPath = constraintPrefix+column.ColumnName;
1422 keyRef.Fields.Add(field);
1424 keyRef.UnhandledAttributes = (XmlAttribute[])attrs.ToArray (typeof (XmlAttribute));
1425 elem.Constraints.Add (keyRef);
1429 private XmlSchemaElement GetTableSchema (XmlDocument doc, DataTable table)
1435 SplitColumns (table, out atts, out elements, out simple);
1437 XmlSchemaElement elem = new XmlSchemaElement ();
1438 elem.Name = table.TableName;
1440 XmlSchemaComplexType complex = new XmlSchemaComplexType ();
1441 elem.SchemaType = complex;
1443 //TODO - what about the simple content?
1444 if (simple != null) {
1445 // add simpleContent
1446 XmlSchemaSimpleContent simpleContent = new XmlSchemaSimpleContent();
1447 complex.ContentModel = simpleContent;
1449 // add column name attribute
1450 XmlAttribute[] xlmAttrs = new XmlAttribute [2];
1451 xlmAttrs[0] = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.ColumnName, XmlConstants.MsdataNamespace);
1452 xlmAttrs[0].Value = simple.ColumnName;
1454 // add ordinal attribute
1455 xlmAttrs[1] = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.Ordinal, XmlConstants.MsdataNamespace);
1456 xlmAttrs[1].Value = simple.Ordinal.ToString();
1457 simpleContent.UnhandledAttributes = xlmAttrs;
1461 XmlSchemaSimpleContentExtension extension = new XmlSchemaSimpleContentExtension();
1462 simpleContent.Content = extension;
1463 extension.BaseTypeName = MapType (simple.DataType);
1467 //A sequence of element types or a simple content node
1469 XmlSchemaSequence seq = new XmlSchemaSequence ();
1470 complex.Particle = seq;
1472 foreach (DataColumn col in elements) {
1473 //<xs:element name=ColumnName type=MappedType Ordinal=index>
1474 XmlSchemaElement colElem = new XmlSchemaElement ();
1475 ArrayList xattrs = new ArrayList();
1477 colElem.Name = col.ColumnName;
1479 if (col.ColumnName != col.Caption && col.Caption != String.Empty) {
1480 xattr = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.Caption, XmlConstants.MsdataNamespace);
1481 xattr.Value = col.Caption;
1485 if (col.AutoIncrement == true) {
1486 xattr = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.AutoIncrement, XmlConstants.MsdataNamespace);
1487 xattr.Value = "true";
1491 if (col.AutoIncrementSeed != 0) {
1492 xattr = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.AutoIncrementSeed, XmlConstants.MsdataNamespace);
1493 xattr.Value = col.AutoIncrementSeed.ToString();
1497 if (col.DefaultValue.ToString () != String.Empty)
1498 colElem.DefaultValue = col.DefaultValue.ToString ();
1500 if (col.MaxLength < 0)
1501 colElem.SchemaTypeName = MapType (col.DataType);
1503 if (colElem.SchemaTypeName == XmlConstants.QnString && col.DataType != typeof (string)
1504 && col.DataType != typeof (char)) {
1505 xattr = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.DataType, XmlConstants.MsdataNamespace);
1506 xattr.Value = col.DataType.ToString();
1510 if (col.AllowDBNull) {
1511 colElem.MinOccurs = 0;
1514 //writer.WriteAttributeString (XmlConstants.MsdataPrefix,
1515 // XmlConstants.Ordinal,
1516 // XmlConstants.MsdataNamespace,
1517 // col.Ordinal.ToString ());
1519 // Write SimpleType if column have MaxLength
1520 if (col.MaxLength > -1) {
1521 colElem.SchemaType = GetTableSimpleType (doc, col);
1524 colElem.UnhandledAttributes = (XmlAttribute[])xattrs.ToArray(typeof (XmlAttribute));
1525 seq.Items.Add (colElem);
1528 foreach (DataRelation rel in table.ChildRelations) {
1530 seq.Items.Add(GetTableSchema (doc, rel.ChildTable));
1535 //Then a list of attributes
1536 foreach (DataColumn col in atts) {
1537 //<xs:attribute name=col.ColumnName form="unqualified" type=MappedType/>
1538 XmlSchemaAttribute att = new XmlSchemaAttribute ();
1539 att.Name = col.ColumnName;
1540 att.Form = XmlSchemaForm.Unqualified;
1541 att.SchemaTypeName = MapType (col.DataType);
1542 complex.Attributes.Add (att);
1548 private XmlSchemaSimpleType GetTableSimpleType (XmlDocument doc, DataColumn col)
1551 XmlSchemaSimpleType simple = new XmlSchemaSimpleType ();
1554 XmlSchemaSimpleTypeRestriction restriction = new XmlSchemaSimpleTypeRestriction ();
1555 restriction.BaseTypeName = MapType (col.DataType);
1558 XmlSchemaMaxLengthFacet max = new XmlSchemaMaxLengthFacet ();
1559 max.Value = XmlConvert.ToString (col.MaxLength);
1560 restriction.Facets.Add (max);
1562 simple.Content = restriction;
1566 private void DoWriteXmlSchema (XmlWriter writer)
1568 BuildSchema ().Write (writer);
1572 /// Helper function to split columns into attributes elements and simple
1575 private void SplitColumns (DataTable table,
1577 out ArrayList elements,
1578 out DataColumn simple)
1580 //The columns can be attributes, hidden, elements, or simple content
1581 //There can be 0-1 simple content cols or 0-* elements
1582 atts = new System.Collections.ArrayList ();
1583 elements = new System.Collections.ArrayList ();
1586 //Sort out the columns
1587 foreach (DataColumn col in table.Columns) {
1588 switch (col.ColumnMapping) {
1589 case MappingType.Attribute:
1592 case MappingType.Element:
1595 case MappingType.SimpleContent:
1596 if (simple != null) {
1597 throw new System.InvalidOperationException ("There may only be one simple content element");
1602 //ignore Hidden elements
1608 private void WriteDiffGramElement(XmlWriter writer)
1610 WriteStartElement (writer, XmlWriteMode.DiffGram, XmlConstants.DiffgrNamespace, XmlConstants.DiffgrPrefix, "diffgram");
1611 WriteAttributeString(writer, XmlWriteMode.DiffGram, null, "xmlns", XmlConstants.MsdataPrefix, XmlConstants.MsdataNamespace);
1614 private void SetRowsID()
1616 foreach (DataTable Table in Tables) {
1618 foreach (DataRow Row in Table.Rows) {
1619 Row.XmlRowID = dataRowID;
1626 private XmlQualifiedName MapType (Type type)
1628 switch (Type.GetTypeCode (type)) {
1629 case TypeCode.String: return XmlConstants.QnString;
1630 case TypeCode.Int16: return XmlConstants.QnShort;
1631 case TypeCode.Int32: return XmlConstants.QnInt;
1632 case TypeCode.Int64: return XmlConstants.QnLong;
1633 case TypeCode.Boolean: return XmlConstants.QnBoolean;
1634 case TypeCode.Byte: return XmlConstants.QnUnsignedByte;
1635 //case TypeCode.Char: return XmlConstants.QnChar;
1636 case TypeCode.DateTime: return XmlConstants.QnDateTime;
1637 case TypeCode.Decimal: return XmlConstants.QnDecimal;
1638 case TypeCode.Double: return XmlConstants.QnDouble;
1639 case TypeCode.SByte: return XmlConstants.QnSbyte;
1640 case TypeCode.Single: return XmlConstants.QnFloat;
1641 case TypeCode.UInt16: return XmlConstants.QnUsignedShort;
1642 case TypeCode.UInt32: return XmlConstants.QnUnsignedInt;
1643 case TypeCode.UInt64: return XmlConstants.QnUnsignedLong;
1646 if (typeof (TimeSpan) == type)
1647 return XmlConstants.QnDuration;
1648 else if (typeof (System.Uri) == type)
1649 return XmlConstants.QnUri;
1650 else if (typeof (byte[]) == type)
1651 return XmlConstants.QnBase64Binary;
1652 else if (typeof (XmlQualifiedName) == type)
1653 return XmlConstants.QnXmlQualifiedName;
1655 return XmlConstants.QnString;
1658 #endregion //Private Xml Serialisation