X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mcs%2Fclass%2FSystem.Data%2FSystem.Data%2FDataSet.cs;h=49ae0f024f96056d5ef7b4fdcc4429ff015bba5e;hb=cbe75fb35eabdffbfdca49f08f87d7d6b3110f21;hp=fe655805dcf840b18d4e15a8ff243679ae26ecf4;hpb=4a45db861575fd48e6087c7874421aef017e700d;p=mono.git diff --git a/mcs/class/System.Data/System.Data/DataSet.cs b/mcs/class/System.Data/System.Data/DataSet.cs index fe655805dcf..49ae0f024f9 100644 --- a/mcs/class/System.Data/System.Data/DataSet.cs +++ b/mcs/class/System.Data/System.Data/DataSet.cs @@ -5,151 +5,301 @@ // Christopher Podurgiel // Daniel Morgan // Rodrigo Moya +// Stuart Caborn +// Tim Coleman (tim@timcoleman.com) +// Ville Palo +// Atsushi Enomoto // // (C) Ximian, Inc. 2002 +// Copyright (C) Tim Coleman, 2002, 2003 +// + +// +// Copyright (C) 2004 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections; using System.ComponentModel; using System.Globalization; +using System.Threading; using System.IO; using System.Runtime.Serialization; using System.Xml; +using System.Xml.Schema; +using System.Xml.Serialization; +using System.Data.Common; -namespace System.Data -{ - /// - /// an in-memory cache of data - /// - [Serializable] - public class DataSet : MarshalByValueComponent, IListSource, - ISupportInitialize, ISerializable { +namespace System.Data { + + [ToolboxItem (false)] + [DefaultProperty ("DataSetName")] + [DesignerAttribute ("Microsoft.VSDesigner.Data.VS.DataSetDesigner, "+ Consts.AssemblyMicrosoft_VSDesigner, "System.ComponentModel.Design.IDesigner")] + [Serializable] + public class DataSet : MarshalByValueComponent, IListSource, + ISupportInitialize, ISerializable, IXmlSerializable + { private string dataSetName; + private string _namespace = ""; + private string prefix; private bool caseSensitive; - private bool enforceConstraints; + private bool enforceConstraints = true; private DataTableCollection tableCollection; + private DataRelationCollection relationCollection; + private PropertyCollection properties; + private DataViewManager defaultView; + private CultureInfo locale = System.Threading.Thread.CurrentThread.CurrentCulture; + internal XmlDataDocument _xmlDataDocument = null; #region Constructors - [MonoTODO] - public DataSet() { - tableCollection = new DataTableCollection (this); + public DataSet () : this ("NewDataSet") + { } - - [MonoTODO] - public DataSet(string name) : this () { + + public DataSet (string name) + { dataSetName = name; + tableCollection = new DataTableCollection (this); + relationCollection = new DataRelationCollection.DataSetRelationCollection (this); + properties = new PropertyCollection (); + this.prefix = String.Empty; + + this.Locale = CultureInfo.CurrentCulture; } - [MonoTODO] - protected DataSet(SerializationInfo info, StreamingContext context) : this () { - throw new NotImplementedException (); + protected DataSet (SerializationInfo info, StreamingContext context) : this () + { + GetSerializationData (info, context); } #endregion // Constructors #region Public Properties + [DataCategory ("Data")] + [DataSysDescription ("Indicates whether comparing strings within the DataSet is case sensitive.")] + [DefaultValue (false)] public bool CaseSensitive { get { return caseSensitive; } set { - caseSensitive = value; + caseSensitive = value; + if (!caseSensitive) { + foreach (DataTable table in Tables) { + foreach (Constraint c in table.Constraints) + c.AssertConstraint (); + } + } } } + [DataCategory ("Data")] + [DataSysDescription ("The name of this DataSet.")] + [DefaultValue ("")] public string DataSetName { - get { - return dataSetName; - } - - set { - dataSetName = value; - } + get { return dataSetName; } + set { dataSetName = value; } } + [DataSysDescription ("Indicates a custom \"view\" of the data contained by the DataSet. This view allows filtering, searching, and navigating through the custom data view.")] + [Browsable (false)] public DataViewManager DefaultViewManager { - [MonoTODO] get { - throw new NotImplementedException (); + if (defaultView == null) + defaultView = new DataViewManager (this); + return defaultView; } - - [MonoTODO] - set { - throw new NotImplementedException (); - } } + [DataSysDescription ("Indicates whether constraint rules are to be followed.")] + [DefaultValue (true)] public bool EnforceConstraints { - get { - return enforceConstraints; - } - - set { - enforceConstraints = value; + get { return enforceConstraints; } + set { + if (value != enforceConstraints) { + enforceConstraints = value; + if (value) { + foreach (DataTable table in Tables) { + // first assert all unique constraints + foreach (UniqueConstraint uc in table.Constraints.UniqueConstraints) + uc.AssertConstraint (); + // then assert all foreign keys + foreach (ForeignKeyConstraint fk in table.Constraints.ForeignKeyConstraints) + fk.AssertConstraint (); + } + } + } } } + [Browsable (false)] + [DataCategory ("Data")] + [DataSysDescription ("The collection that holds custom user information.")] public PropertyCollection ExtendedProperties { - [MonoTODO] - get { - throw new NotImplementedException (); - } + get { return properties; } } + [Browsable (false)] + [DataSysDescription ("Indicates that the DataSet has errors.")] public bool HasErrors { [MonoTODO] get { - throw new NotImplementedException (); + for (int i = 0; i < Tables.Count; i++) { + if (Tables[i].HasErrors) + return true; + } + return false; } } + [DataCategory ("Data")] + [DataSysDescription ("Indicates a locale under which to compare strings within the DataSet.")] public CultureInfo Locale { - [MonoTODO] - get { - throw new NotImplementedException (); + get { + return locale; } - - [MonoTODO] set { - throw new NotImplementedException (); + if (locale == null || !locale.Equals (value)) { + // TODO: check if the new locale is valid + // TODO: update locale of all tables + locale = value; + } } } - public string Namespace { - [MonoTODO] - get { - throw new NotImplementedException (); - } + public void Merge (DataRow[] rows) + { + Merge (rows, false, MissingSchemaAction.Add); + } + + public void Merge (DataSet dataSet) + { + Merge (dataSet, false, MissingSchemaAction.Add); + } + + public void Merge (DataTable table) + { + Merge (table, false, MissingSchemaAction.Add); + } + + public void Merge (DataSet dataSet, bool preserveChanges) + { + Merge (dataSet, preserveChanges, MissingSchemaAction.Add); + } + + [MonoTODO] + public void Merge (DataRow[] rows, bool preserveChanges, MissingSchemaAction missingSchemaAction) + { + if (rows == null) + throw new ArgumentNullException ("rows"); + if (!IsLegalSchemaAction (missingSchemaAction)) + throw new ArgumentOutOfRangeException ("missingSchemaAction"); - [MonoTODO] + MergeManager.Merge (this, rows, preserveChanges, missingSchemaAction); + } + + [MonoTODO] + public void Merge (DataSet dataSet, bool preserveChanges, MissingSchemaAction missingSchemaAction) + { + if (dataSet == null) + throw new ArgumentNullException ("dataSet"); + if (!IsLegalSchemaAction (missingSchemaAction)) + throw new ArgumentOutOfRangeException ("missingSchemaAction"); + + MergeManager.Merge (this, dataSet, preserveChanges, missingSchemaAction); + } + + [MonoTODO] + public void Merge (DataTable table, bool preserveChanges, MissingSchemaAction missingSchemaAction) + { + if (table == null) + throw new ArgumentNullException ("table"); + if (!IsLegalSchemaAction (missingSchemaAction)) + throw new ArgumentOutOfRangeException ("missingSchemaAction"); + + MergeManager.Merge (this, table, preserveChanges, missingSchemaAction); + } + + private static bool IsLegalSchemaAction (MissingSchemaAction missingSchemaAction) + { + if (missingSchemaAction == MissingSchemaAction.Add || missingSchemaAction == MissingSchemaAction.AddWithKey + || missingSchemaAction == MissingSchemaAction.Error || missingSchemaAction == MissingSchemaAction.Ignore) + return true; + return false; + } + + [DataCategory ("Data")] + [DataSysDescription ("Indicates the XML uri namespace for the root element pointed at by this DataSet.")] + [DefaultValue ("")] + public string Namespace { + get { return _namespace; } set { - throw new NotImplementedException (); + //TODO - trigger an event if this happens? + if (value == null) + value = String.Empty; + if (value != this._namespace) + RaisePropertyChanging ("Namespace"); + _namespace = value; } } + [DataCategory ("Data")] + [DataSysDescription ("Indicates the prefix of the namespace used for this DataSet.")] + [DefaultValue ("")] public string Prefix { - [MonoTODO] - get { - throw new NotImplementedException (); - } - - [MonoTODO] + get { return prefix; } set { - throw new NotImplementedException (); + if (value == null) + value = String.Empty; + // Prefix cannot contain any special characters other than '_' and ':' + for (int i = 0; i < value.Length; i++) { + if (!(Char.IsLetterOrDigit (value [i])) && (value [i] != '_') && (value [i] != ':')) + throw new DataException ("Prefix '" + value + "' is not valid, because it contains special characters."); + } + + + if (value == null) + value = string.Empty; + + if (value != this.prefix) + RaisePropertyChanging ("Prefix"); + prefix = value; } } + [DataCategory ("Data")] + [DataSysDescription ("The collection that holds the relations for this DatSet.")] + [DesignerSerializationVisibility (DesignerSerializationVisibility.Content)] public DataRelationCollection Relations { - [MonoTODO] - get{ - throw new NotImplementedException (); + get { + return relationCollection; } } + [Browsable (false)] + [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] public override ISite Site { [MonoTODO] get { @@ -162,172 +312,1558 @@ namespace System.Data } } + [DataCategory ("Data")] + [DataSysDescription ("The collection that holds the tables for this DataSet.")] + [DesignerSerializationVisibility (DesignerSerializationVisibility.Content)] public DataTableCollection Tables { - get { - return tableCollection; - } + get { return tableCollection; } } #endregion // Public Properties #region Public Methods - public void AcceptChanges() + [MonoTODO] + public void AcceptChanges () { - throw new NotImplementedException (); + foreach (DataTable tempTable in tableCollection) + tempTable.AcceptChanges (); } - public void Clear() + public void Clear () { - throw new NotImplementedException (); + if (_xmlDataDocument != null) + throw new NotSupportedException ("Clear function on dataset and datatable is not supported when XmlDataDocument is bound to the DataSet."); + for (int t = 0; t < tableCollection.Count; t++) { + tableCollection[t].Clear (); + } } - public virtual DataSet Clone() + public virtual DataSet Clone () { - throw new NotImplementedException (); + DataSet Copy = new DataSet (); + CopyProperties (Copy); + + foreach (DataTable Table in Tables) { + Copy.Tables.Add (Table.Clone ()); + } + + //Copy Relationships between tables after existance of tables + //and setting properties correctly + CopyRelations (Copy); + + return Copy; } - public DataSet Copy() + // Copies both the structure and data for this DataSet. + public DataSet Copy () { - throw new NotImplementedException (); + DataSet Copy = new DataSet (); + CopyProperties (Copy); + + // Copy DatSet's tables + foreach (DataTable Table in Tables) + Copy.Tables.Add (Table.Copy ()); + + //Copy Relationships between tables after existance of tables + //and setting properties correctly + CopyRelations (Copy); + + return Copy; } - public DataSet GetChanges() + private void CopyProperties (DataSet Copy) { - throw new NotImplementedException (); + Copy.CaseSensitive = CaseSensitive; + //Copy.Container = Container + Copy.DataSetName = DataSetName; + //Copy.DefaultViewManager + //Copy.DesignMode + Copy.EnforceConstraints = EnforceConstraints; + if(ExtendedProperties.Count > 0) { + // Cannot copy extended properties directly as the property does not have a set accessor + Array tgtArray = Array.CreateInstance( typeof (object), ExtendedProperties.Count); + ExtendedProperties.Keys.CopyTo (tgtArray, 0); + for (int i=0; i < ExtendedProperties.Count; i++) + Copy.ExtendedProperties.Add (tgtArray.GetValue (i), ExtendedProperties[tgtArray.GetValue (i)]); + } + Copy.Locale = Locale; + Copy.Namespace = Namespace; + Copy.Prefix = Prefix; + //Copy.Site = Site; // FIXME : Not sure of this. + + } + + + private void CopyRelations (DataSet Copy) + { + + //Creation of the relation contains some of the properties, and the constructor + //demands these values. instead changing the DataRelation constructor and behaviour the + //parameters are pre-configured and sent to the most general constructor + + foreach (DataRelation MyRelation in this.Relations) { + string pTable = MyRelation.ParentTable.TableName; + string cTable = MyRelation.ChildTable.TableName; + DataColumn[] P_DC = new DataColumn[MyRelation.ParentColumns.Length]; + DataColumn[] C_DC = new DataColumn[MyRelation.ChildColumns.Length]; + int i = 0; + + foreach (DataColumn DC in MyRelation.ParentColumns) { + P_DC[i]=Copy.Tables[pTable].Columns[DC.ColumnName]; + i++; + } + + i = 0; + + foreach (DataColumn DC in MyRelation.ChildColumns) { + C_DC[i]=Copy.Tables[cTable].Columns[DC.ColumnName]; + i++; + } + + DataRelation cRel = new DataRelation (MyRelation.RelationName, P_DC, C_DC); + //cRel.ChildColumns = MyRelation.ChildColumns; + //cRel.ChildTable = MyRelation.ChildTable; + //cRel.ExtendedProperties = cRel.ExtendedProperties; + //cRel.Nested = MyRelation.Nested; + //cRel.ParentColumns = MyRelation.ParentColumns; + //cRel.ParentTable = MyRelation.ParentTable; + + Copy.Relations.Add (cRel); + } } - public DataSet GetChanges(DataRowState rowStates) + + + public DataSet GetChanges () { - throw new NotImplementedException (); + return GetChanges (DataRowState.Added | DataRowState.Deleted | DataRowState.Modified); } - public string GetXml() + + public DataSet GetChanges (DataRowState rowStates) { - throw new NotImplementedException (); + if (!HasChanges (rowStates)) + return null; + + DataSet copySet = Clone (); + Hashtable addedRows = new Hashtable (); + + IEnumerator tableEnumerator = Tables.GetEnumerator (); + DataTable origTable; + DataTable copyTable; + while (tableEnumerator.MoveNext ()) { + origTable = (DataTable)tableEnumerator.Current; + copyTable = copySet.Tables[origTable.TableName]; + + // Look for relations that have this table as child + IEnumerator relations = origTable.ParentRelations.GetEnumerator (); + + IEnumerator rowEnumerator = origTable.Rows.GetEnumerator (); + while (rowEnumerator.MoveNext ()) { + DataRow row = (DataRow)rowEnumerator.Current; + + if (row.IsRowChanged (rowStates)) + AddChangedRow (addedRows, copySet, copyTable, relations, row); + } + } + return copySet; + } + + void AddChangedRow (Hashtable addedRows, DataSet copySet, DataTable copyTable, IEnumerator relations, DataRow row) + { + if (addedRows.ContainsKey (row)) return; + + relations.Reset (); + while (relations.MoveNext ()) { + DataRow parentRow = row.GetParentRow ((DataRelation) relations.Current); + if (parentRow == null || addedRows.ContainsKey (parentRow)) continue; + DataTable parentCopyTable = copySet.Tables [parentRow.Table.TableName]; + AddChangedRow (addedRows, copySet, parentCopyTable, parentRow.Table.ParentRelations.GetEnumerator (), parentRow); + } + + DataRow newRow = copyTable.NewRow (); + copyTable.Rows.Add (newRow); + row.CopyValuesToRow (newRow); + newRow.XmlRowID = row.XmlRowID; + addedRows.Add (row,row); } - public string GetXmlSchema() +#if NET_2_0 + [MonoTODO] + public DataTableReader GetDataReader (DataTable[] dataTables) { throw new NotImplementedException (); } - public virtual void RejectChanges() + [MonoTODO] + public DataTableReader GetDataReader () { throw new NotImplementedException (); } +#endif + + public string GetXml () + { + StringWriter Writer = new StringWriter (); + WriteXml (Writer, XmlWriteMode.IgnoreSchema); + return Writer.ToString (); + } - public virtual void Reset() + public string GetXmlSchema () { - throw new NotImplementedException (); + StringWriter Writer = new StringWriter (); + WriteXmlSchema (Writer); + return Writer.ToString (); } - public void WriteXml(Stream stream) + [MonoTODO] + public bool HasChanges () { - throw new NotImplementedException (); + return HasChanges (DataRowState.Added | DataRowState.Deleted | DataRowState.Modified); } - public void WriteXml(string fileName) + [MonoTODO] + public bool HasChanges (DataRowState rowState) { - throw new NotImplementedException (); + if (((int)rowState & 0xffffffe0) != 0) + throw new ArgumentOutOfRangeException ("rowState"); + + DataTableCollection tableCollection = Tables; + DataTable table; + DataRowCollection rowCollection; + DataRow row; + + for (int i = 0; i < tableCollection.Count; i++) { + table = tableCollection[i]; + rowCollection = table.Rows; + for (int j = 0; j < rowCollection.Count; j++) { + row = rowCollection[j]; + if ((row.RowState & rowState) != 0) + return true; + } + } + + return false; } - public void WriteXml(TextWriter writer) + public void InferXmlSchema (XmlReader reader, string[] nsArray) { - throw new NotImplementedException (); + if (reader == null) + return; + XmlDocument doc = new XmlDocument (); + doc.Load (reader); + InferXmlSchema (doc, nsArray); } - public void WriteXml(XmlWriter writer) + private void InferXmlSchema (XmlDocument doc, string [] nsArray) { - throw new NotImplementedException (); + XmlDataInferenceLoader.Infer (this, doc, XmlReadMode.InferSchema, nsArray); } - public void WriteXml(Stream stream, XmlWriteMode mode) + public void InferXmlSchema (Stream stream, string[] nsArray) { - throw new NotImplementedException (); + InferXmlSchema (new XmlTextReader (stream), nsArray); } - public void WriteXml(string fileName, XmlWriteMode mode) + public void InferXmlSchema (TextReader reader, string[] nsArray) { - throw new NotImplementedException (); + InferXmlSchema (new XmlTextReader (reader), nsArray); } - public void WriteXml(TextWriter writer, XmlWriteMode mode) + public void InferXmlSchema (string fileName, string[] nsArray) { - throw new NotImplementedException (); + XmlTextReader reader = new XmlTextReader (fileName); + try { + InferXmlSchema (reader, nsArray); + } finally { + reader.Close (); + } } - public void WriteXml(XmlWriter writer, XmlWriteMode mode) +#if NET_2_0 + [MonoTODO] + public void Load (IDataReader reader, LoadOption loadOption, DataTable[] tables) { throw new NotImplementedException (); } - public void WriteXmlSchema(Stream stream) + [MonoTODO] + public void Load (IDataReader reader, LoadOption loadOption, string[] tables) { throw new NotImplementedException (); } +#endif - public void WriteXmlSchema(string fileName) - { - throw new NotImplementedException (); + public virtual void RejectChanges () + { + int i; + bool oldEnforceConstraints = this.EnforceConstraints; + this.EnforceConstraints = false; + + for (i = 0; i < this.Tables.Count;i++) + this.Tables[i].RejectChanges (); + + this.EnforceConstraints = oldEnforceConstraints; } - public void WriteXmlSchema(TextWriter writer) + public virtual void Reset () { + IEnumerator constraintEnumerator; + + // first we remove all ForeignKeyConstraints (if we will not do that + // we will get an exception when clearing the tables). + for (int i = 0; i < Tables.Count; i++) { + ConstraintCollection cc = Tables[i].Constraints; + for (int j = 0; j < cc.Count; j++) { + if (cc[j] is ForeignKeyConstraint) + cc.Remove (cc[j]); + } + } + + Clear (); + Relations.Clear (); + Tables.Clear (); } - public void WriteXmlSchema(XmlWriter writer) + public void WriteXml (Stream stream) { - throw new NotImplementedException (); + XmlTextWriter writer = new XmlTextWriter (stream, null); + writer.Formatting = Formatting.Indented; + WriteXml (writer); } - #endregion // Public Methods + /// + /// Writes the current data for the DataSet to the specified file. + /// + /// Fully qualified filename to write to + public void WriteXml (string fileName) + { + XmlTextWriter writer = new XmlTextWriter (fileName, null); + writer.Formatting = Formatting.Indented; + writer.WriteStartDocument (true); + try { + WriteXml (writer); + } + finally { + writer.WriteEndDocument (); + writer.Close (); + } + } - #region Public Events + public void WriteXml (TextWriter writer) + { + XmlTextWriter xwriter = new XmlTextWriter (writer); + xwriter.Formatting = Formatting.Indented; + WriteXml (xwriter); + } - public event MergeFailedEventHandler MergeFailed; + public void WriteXml (XmlWriter writer) + { + WriteXml (writer, XmlWriteMode.IgnoreSchema); + } - #endregion // Public Events + public void WriteXml (string filename, XmlWriteMode mode) + { + XmlTextWriter writer = new XmlTextWriter (filename, null); + writer.Formatting = Formatting.Indented; + writer.WriteStartDocument (true); + + try { + WriteXml (writer, mode); + } + finally { + writer.WriteEndDocument (); + writer.Close (); + } + } + + public void WriteXml (Stream stream, XmlWriteMode mode) + { + XmlTextWriter writer = new XmlTextWriter (stream, null); + writer.Formatting = Formatting.Indented; + WriteXml (writer, mode); + } + + public void WriteXml (TextWriter writer, XmlWriteMode mode) + { + XmlTextWriter xwriter = new XmlTextWriter (writer); + xwriter.Formatting = Formatting.Indented; + WriteXml (xwriter, mode); + } + + public void WriteXml (XmlWriter writer, XmlWriteMode mode) + { + if (mode == XmlWriteMode.DiffGram) { + SetRowsID(); + WriteDiffGramElement(writer); + } + + // It should not write when there is no content to be written + bool shouldOutputContent = (mode != XmlWriteMode.DiffGram); + for (int n = 0; n < tableCollection.Count && !shouldOutputContent; n++) + shouldOutputContent = tableCollection [n].Rows.Count > 0; + + if (shouldOutputContent) { + WriteStartElement (writer, mode, Namespace, Prefix, XmlConvert.EncodeName (DataSetName)); + + if (mode == XmlWriteMode.WriteSchema) + DoWriteXmlSchema (writer); + + WriteTables (writer, mode, Tables, DataRowVersion.Default); + writer.WriteEndElement (); + } + + if (mode == XmlWriteMode.DiffGram) { + if (HasChanges(DataRowState.Modified | DataRowState.Deleted)) { + + DataSet beforeDS = GetChanges (DataRowState.Modified | DataRowState.Deleted); + WriteStartElement (writer, XmlWriteMode.DiffGram, XmlConstants.DiffgrNamespace, XmlConstants.DiffgrPrefix, "before"); + WriteTables (writer, mode, beforeDS.Tables, DataRowVersion.Original); + writer.WriteEndElement (); + } + } + + if (mode == XmlWriteMode.DiffGram) + writer.WriteEndElement (); // diffgr:diffgram + + writer.Flush (); + } + + public void WriteXmlSchema (Stream stream) + { + XmlTextWriter writer = new XmlTextWriter (stream, null ); + writer.Formatting = Formatting.Indented; + WriteXmlSchema (writer); + } + + public void WriteXmlSchema (string fileName) + { + XmlTextWriter writer = new XmlTextWriter (fileName, null); + try { + writer.Formatting = Formatting.Indented; + writer.WriteStartDocument (true); + WriteXmlSchema (writer); + } finally { + writer.WriteEndDocument (); + writer.Close (); + } + } + + public void WriteXmlSchema (TextWriter writer) + { + XmlTextWriter xwriter = new XmlTextWriter (writer); + try { + xwriter.Formatting = Formatting.Indented; +// xwriter.WriteStartDocument (); + WriteXmlSchema (xwriter); + } finally { +// xwriter.WriteEndDocument (); + xwriter.Close (); + } + } + + public void WriteXmlSchema (XmlWriter writer) + { + //Create a skeleton doc and then write the schema + //proper which is common to the WriteXml method in schema mode + DoWriteXmlSchema (writer); + } + + public void ReadXmlSchema (Stream stream) + { + XmlReader reader = new XmlTextReader (stream, null); + ReadXmlSchema (reader); + } + + public void ReadXmlSchema (string str) + { + XmlReader reader = new XmlTextReader (str); + try { + ReadXmlSchema (reader); + } + finally { + reader.Close (); + } + } + + public void ReadXmlSchema (TextReader treader) + { + XmlReader reader = new XmlTextReader (treader); + ReadXmlSchema (reader); + } + + public void ReadXmlSchema (XmlReader reader) + { +#if true + new XmlSchemaDataImporter (this, reader).Process (); +#else + XmlSchemaMapper SchemaMapper = new XmlSchemaMapper (this); + SchemaMapper.Read (reader); +#endif + } + + public XmlReadMode ReadXml (Stream stream) + { + return ReadXml (new XmlTextReader (stream)); + } + + public XmlReadMode ReadXml (string str) + { + XmlTextReader reader = new XmlTextReader (str); + try { + return ReadXml (reader); + } + finally { + reader.Close (); + } + } + + public XmlReadMode ReadXml (TextReader reader) + { + return ReadXml (new XmlTextReader (reader)); + } - #region Destructors + public XmlReadMode ReadXml (XmlReader r) + { + return ReadXml (r, XmlReadMode.Auto); + } + + public XmlReadMode ReadXml (Stream stream, XmlReadMode mode) + { + return ReadXml (new XmlTextReader (stream), mode); + } + + public XmlReadMode ReadXml (string str, XmlReadMode mode) + { + XmlTextReader reader = new XmlTextReader (str); + try { + return ReadXml (reader, mode); + } + finally { + reader.Close (); + } + } - ~DataSet() + public XmlReadMode ReadXml (TextReader reader, XmlReadMode mode) { + return ReadXml (new XmlTextReader (reader), mode); } - #endregion Destructors + public XmlReadMode ReadXml (XmlReader reader, XmlReadMode mode) + { + switch (reader.ReadState) { + case ReadState.EndOfFile: + case ReadState.Error: + case ReadState.Closed: + return mode; + } + // Skip XML declaration and prolog + reader.MoveToContent(); + if (reader.EOF) + return mode; + + XmlReadMode Result = mode; + + // If diffgram, then read the first element as diffgram + if (reader.LocalName == "diffgram" && reader.NamespaceURI == XmlConstants.DiffgrNamespace) { + switch (mode) { + case XmlReadMode.Auto: + case XmlReadMode.DiffGram: + XmlDiffLoader DiffLoader = new XmlDiffLoader (this); + DiffLoader.Load (reader); + // (and leave rest of the reader as is) + return XmlReadMode.DiffGram; + case XmlReadMode.Fragment: + reader.Skip (); + // (and continue to read) + break; + default: + reader.Skip (); + // (and leave rest of the reader as is) + return mode; + } + } + // If schema, then read the first element as schema + if (reader.LocalName == "schema" && reader.NamespaceURI == XmlSchema.Namespace) { + switch (mode) { + case XmlReadMode.IgnoreSchema: + case XmlReadMode.InferSchema: + reader.Skip (); + // (and break up read) + return mode; + case XmlReadMode.Fragment: + ReadXmlSchema (reader); + // (and continue to read) + break; + case XmlReadMode.Auto: + if (Tables.Count == 0) { + ReadXmlSchema (reader); + return XmlReadMode.ReadSchema; + } else { + // otherwise just ignore and return IgnoreSchema + reader.Skip (); + return XmlReadMode.IgnoreSchema; + } + default: + ReadXmlSchema (reader); + // (and leave rest of the reader as is) + return mode; // When DiffGram, return DiffGram + } + } + // Otherwise, read as dataset... but only when required. + XmlReadMode explicitReturnMode = XmlReadMode.Auto; + XmlDocument doc; + switch (mode) { + case XmlReadMode.Auto: + if (Tables.Count > 0) + goto case XmlReadMode.IgnoreSchema; + else + goto case XmlReadMode.InferSchema; + case XmlReadMode.InferSchema: + doc = new XmlDocument (); + do { + doc.AppendChild (doc.ReadNode (reader)); + reader.MoveToContent (); + if (doc.DocumentElement != null) + break; + } while (!reader.EOF); + InferXmlSchema (doc, null); + reader = new XmlNodeReader (doc); + explicitReturnMode = XmlReadMode.InferSchema; + break; + case XmlReadMode.ReadSchema: + doc = new XmlDocument (); + do { + doc.AppendChild (doc.ReadNode (reader)); + reader.MoveToContent (); + if (doc.DocumentElement != null) + break; + } while (!reader.EOF); + if (doc.DocumentElement != null) { + XmlElement schema = doc.DocumentElement ["schema", XmlSchema.Namespace] as XmlElement; + if (schema != null) { + ReadXmlSchema (new XmlNodeReader (schema)); + explicitReturnMode = XmlReadMode.ReadSchema; + } + } + reader = new XmlNodeReader (doc); + break; + case XmlReadMode.IgnoreSchema: + case XmlReadMode.Fragment: + break; + default: + reader.Skip (); + return mode; + } + + XmlDataReader.ReadXml (this, reader, mode); + if (explicitReturnMode != XmlReadMode.Auto) + return explicitReturnMode; + return mode == XmlReadMode.Auto ? XmlReadMode.IgnoreSchema : mode; + } + #endregion // Public Methods + + #region Public Events + + [DataCategory ("Action")] + [DataSysDescription ("Occurs when it is not possible to merge schemas for two tables with the same name.")] + public event MergeFailedEventHandler MergeFailed; + + #endregion // Public Events #region IListSource methods IList IListSource.GetList () { - throw new NotImplementedException (); + return DefaultViewManager; } - + bool IListSource.ContainsListCollection { get { - throw new NotImplementedException (); + return true; } } #endregion IListSource methods #region ISupportInitialize methods - void ISupportInitialize.BeginInit () + public void BeginInit () { - throw new NotImplementedException (); } - - void ISupportInitialize.EndInit () + + public void EndInit () { - throw new NotImplementedException (); } #endregion #region ISerializable void ISerializable.GetObjectData (SerializationInfo si, StreamingContext sc) { - throw new NotImplementedException (); + StringWriter sw = new StringWriter (); + XmlTextWriter writer = new XmlTextWriter (sw); + DoWriteXmlSchema (writer); + writer.Flush (); + si.AddValue ("XmlSchema", sw.ToString ()); + + sw = new StringWriter (); + writer = new XmlTextWriter (sw); + WriteXml (writer, XmlWriteMode.DiffGram); + writer.Flush (); + si.AddValue ("XmlDiffGram", sw.ToString ()); } #endregion + + #region Protected Methods + protected void GetSerializationData (SerializationInfo info, StreamingContext context) + { + string s = info.GetValue ("XmlSchema", typeof (String)) as String; + XmlTextReader reader = new XmlTextReader (new StringReader (s)); + ReadXmlSchema (reader); + reader.Close (); + + s = info.GetValue ("XmlDiffGram", typeof (String)) as String; + reader = new XmlTextReader (new StringReader (s)); + ReadXml (reader, XmlReadMode.DiffGram); + reader.Close (); + } + + + protected virtual System.Xml.Schema.XmlSchema GetSchemaSerializable () + { + return null; + } + + protected virtual void ReadXmlSerializable (XmlReader reader) + { + reader.MoveToContent (); + reader.ReadStartElement (); + reader.MoveToContent (); + ReadXmlSchema (reader); + reader.MoveToContent (); + ReadXml (reader, XmlReadMode.DiffGram); + reader.MoveToContent (); + reader.ReadEndElement (); + } + + void IXmlSerializable.ReadXml (XmlReader reader) + { + ReadXmlSerializable(reader); + } + + void IXmlSerializable.WriteXml (XmlWriter writer) + { + DoWriteXmlSchema (writer); + WriteXml (writer, XmlWriteMode.DiffGram); + } + + XmlSchema IXmlSerializable.GetSchema () + { + return BuildSchema (); + } + + protected virtual bool ShouldSerializeRelations () + { + return true; + } + + protected virtual bool ShouldSerializeTables () + { + return true; + } + + [MonoTODO] + protected internal virtual void OnPropertyChanging (PropertyChangedEventArgs pcevent) + { + } + + [MonoTODO] + protected virtual void OnRemoveRelation (DataRelation relation) + { + } + + [MonoTODO] + protected virtual void OnRemoveTable (DataTable table) + { + } + + internal virtual void OnMergeFailed (MergeFailedEventArgs e) + { + if (MergeFailed != null) + MergeFailed (this, e); + } + + [MonoTODO] + protected internal void RaisePropertyChanging (string name) + { + } + #endregion + + #region Private Xml Serialisation + + private string WriteObjectXml (object o) + { + switch (Type.GetTypeCode (o.GetType ())) { + case TypeCode.Boolean: + return XmlConvert.ToString ((Boolean) o); + case TypeCode.Byte: + return XmlConvert.ToString ((Byte) o); + case TypeCode.Char: + return XmlConvert.ToString ((Char) o); + case TypeCode.DateTime: + return XmlConvert.ToString ((DateTime) o); + case TypeCode.Decimal: + return XmlConvert.ToString ((Decimal) o); + case TypeCode.Double: + return XmlConvert.ToString ((Double) o); + case TypeCode.Int16: + return XmlConvert.ToString ((Int16) o); + case TypeCode.Int32: + return XmlConvert.ToString ((Int32) o); + case TypeCode.Int64: + return XmlConvert.ToString ((Int64) o); + case TypeCode.SByte: + return XmlConvert.ToString ((SByte) o); + case TypeCode.Single: + return XmlConvert.ToString ((Single) o); + case TypeCode.UInt16: + return XmlConvert.ToString ((UInt16) o); + case TypeCode.UInt32: + return XmlConvert.ToString ((UInt32) o); + case TypeCode.UInt64: + return XmlConvert.ToString ((UInt64) o); + } + if (o is TimeSpan) return XmlConvert.ToString ((TimeSpan) o); + if (o is Guid) return XmlConvert.ToString ((Guid) o); + if (o is byte[]) return Convert.ToBase64String ((byte[])o); + return o.ToString (); + } + + private void WriteTables (XmlWriter writer, XmlWriteMode mode, DataTableCollection tableCollection, DataRowVersion version) + { + //Write out each table in order, providing it is not + //part of another table structure via a nested parent relationship + foreach (DataTable table in tableCollection) { + bool isTopLevel = true; + /* + foreach (DataRelation rel in table.ParentRelations) { + if (rel.Nested) { + isTopLevel = false; + break; + } + } + */ + if (isTopLevel) { + WriteTable ( writer, table, mode, version); + } + } + } + + private void WriteTable (XmlWriter writer, DataTable table, XmlWriteMode mode, DataRowVersion version) + { + DataRow[] rows = new DataRow [table.Rows.Count]; + table.Rows.CopyTo (rows, 0); + WriteTable (writer, rows, mode, version, true); + } + + private void WriteTable (XmlWriter writer, DataRow[] rows, XmlWriteMode mode, DataRowVersion version, bool skipIfNested) + { + //The columns can be attributes, hidden, elements, or simple content + //There can be 0-1 simple content cols or 0-* elements + System.Collections.ArrayList atts; + System.Collections.ArrayList elements; + DataColumn simple = null; + + if (rows.Length == 0) return; + DataTable table = rows[0].Table; + SplitColumns (table, out atts, out elements, out simple); + //sort out the namespacing + string nspc = table.Namespace.Length > 0 ? table.Namespace : Namespace; + int relationCount = table.ParentRelations.Count; + DataRelation oneRel = relationCount == 1 ? table.ParentRelations [0] : null; + + foreach (DataRow row in rows) { + if (skipIfNested) { + // Skip rows that is a child of any tables. + switch (relationCount) { + case 0: + break; + case 1: + if (row.GetParentRow (oneRel) != null) + continue; + break; + case 2: + bool skip = false; + for (int i = 0; i < table.ParentRelations.Count; i++) + if (row.GetParentRow (table.ParentRelations [i]) != null) { + skip = true; + continue; + } + if (skip) + continue; + break; + } + } + + if (!row.HasVersion(version) || + (mode == XmlWriteMode.DiffGram && row.RowState == DataRowState.Unchanged + && version == DataRowVersion.Original)) + continue; + + // First check are all the rows null. If they are we just write empty element + bool AllNulls = true; + foreach (DataColumn dc in table.Columns) { + + if (row [dc.ColumnName, version] != DBNull.Value) { + AllNulls = false; + break; + } + } + + // If all of the columns were null, we have to write empty element + if (AllNulls) { + writer.WriteElementString (XmlConvert.EncodeLocalName (table.TableName), ""); + continue; + } + + WriteTableElement (writer, mode, table, row, version); + + foreach (DataColumn col in atts) { + WriteColumnAsAttribute (writer, mode, col, row, version); + } + + if (simple != null) { + writer.WriteString (WriteObjectXml (row[simple, version])); + } + else { + foreach (DataColumn col in elements) { + WriteColumnAsElement (writer, mode, col, row, version); + } + } + + foreach (DataRelation relation in table.ChildRelations) { + if (relation.Nested) { + WriteTable (writer, row.GetChildRows (relation), mode, version, false); + } + } + + writer.WriteEndElement (); + } + + } + + private void WriteColumnAsElement (XmlWriter writer, XmlWriteMode mode, DataColumn col, DataRow row, DataRowVersion version) + { + string colnspc = null; + object rowObject = row [col, version]; + + if (rowObject == null || rowObject == DBNull.Value) + return; + + if (col.Namespace != String.Empty) + colnspc = col.Namespace; + + //TODO check if I can get away with write element string + WriteStartElement (writer, mode, colnspc, col.Prefix, XmlConvert.EncodeLocalName (col.ColumnName)); + writer.WriteString (WriteObjectXml (rowObject)); + writer.WriteEndElement (); + } + + private void WriteColumnAsAttribute (XmlWriter writer, XmlWriteMode mode, DataColumn col, DataRow row, DataRowVersion version) + { + WriteAttributeString (writer, mode, col.Namespace, col.Prefix, XmlConvert.EncodeLocalName (col.ColumnName), WriteObjectXml (row[col, version])); + } + + private void WriteTableElement (XmlWriter writer, XmlWriteMode mode, DataTable table, DataRow row, DataRowVersion version) + { + //sort out the namespacing + string nspc = table.Namespace.Length > 0 ? table.Namespace : Namespace; + + WriteStartElement (writer, mode, nspc, table.Prefix, XmlConvert.EncodeLocalName (table.TableName)); + + if (mode == XmlWriteMode.DiffGram) { + WriteAttributeString (writer, mode, XmlConstants.DiffgrNamespace, XmlConstants.DiffgrPrefix, "id", table.TableName + (row.XmlRowID + 1)); + WriteAttributeString (writer, mode, XmlConstants.MsdataNamespace, XmlConstants.MsdataPrefix, "rowOrder", XmlConvert.ToString (row.XmlRowID)); + string modeName = null; + if (row.RowState == DataRowState.Modified) + modeName = "modified"; + else if (row.RowState == DataRowState.Added) + modeName = "inserted"; + + if (version != DataRowVersion.Original && modeName != null) + WriteAttributeString (writer, mode, XmlConstants.DiffgrNamespace, XmlConstants.DiffgrPrefix, "hasChanges", modeName); + } + } + + private void WriteStartElement (XmlWriter writer, XmlWriteMode mode, string nspc, string prefix, string name) + { + writer.WriteStartElement (prefix, name, nspc); + } + + private void WriteAttributeString (XmlWriter writer, XmlWriteMode mode, string nspc, string prefix, string name, string stringValue) + { + switch ( mode) { + case XmlWriteMode.WriteSchema: + writer.WriteAttributeString (prefix, name, nspc); + break; + case XmlWriteMode.DiffGram: + writer.WriteAttributeString (prefix, name, nspc,stringValue); + break; + default: + writer.WriteAttributeString (name, stringValue); + break; + }; + } + + internal void WriteIndividualTableContent (XmlWriter writer, DataTable table, XmlWriteMode mode) + { + ((XmlTextWriter)writer).Formatting = Formatting.Indented; + + if (mode == XmlWriteMode.DiffGram) { + SetTableRowsID (table); + WriteDiffGramElement (writer); + } + + WriteStartElement (writer, mode, Namespace, Prefix, XmlConvert.EncodeName (DataSetName)); + + WriteTable (writer, table, mode, DataRowVersion.Default); + + if (mode == XmlWriteMode.DiffGram) { + writer.WriteEndElement (); //DataSet name + if (HasChanges (DataRowState.Modified | DataRowState.Deleted)) { + + DataSet beforeDS = GetChanges (DataRowState.Modified | DataRowState.Deleted); + WriteStartElement (writer, XmlWriteMode.DiffGram, XmlConstants.DiffgrNamespace, XmlConstants.DiffgrPrefix, "before"); + WriteTable (writer, beforeDS.Tables [table.TableName], mode, DataRowVersion.Original); + writer.WriteEndElement (); + } + } + writer.WriteEndElement (); // DataSet name or diffgr:diffgram + } + + + private void CheckNamespace (string prefix, string ns, XmlNamespaceManager nsmgr, XmlSchema schema) + { + if (ns == String.Empty) + return; + if (ns != nsmgr.DefaultNamespace) { + if (nsmgr.LookupNamespace (nsmgr.NameTable.Get (prefix)) != ns) { + for (int i = 1; i < int.MaxValue; i++) { + string p = nsmgr.NameTable.Add ("app" + i); + if (!nsmgr.HasNamespace (p)) { + nsmgr.AddNamespace (p, ns); + HandleExternalNamespace (p, ns, schema); + break; + } + } + } + } + } + + XmlSchema BuildSchema () + { + return BuildSchema (Tables, Relations); + } + + internal XmlSchema BuildSchema (DataTableCollection tables, DataRelationCollection relations) + { + string constraintPrefix = ""; + XmlSchema schema = new XmlSchema (); + XmlNamespaceManager nsmgr = new XmlNamespaceManager (new NameTable ()); + + if (Namespace != "") { + schema.AttributeFormDefault = XmlSchemaForm.Qualified; + schema.ElementFormDefault = XmlSchemaForm.Qualified; + schema.TargetNamespace = Namespace; + constraintPrefix = XmlConstants.TnsPrefix + ":"; + } + + // set the schema id + string xmlNSURI = "http://www.w3.org/2000/xmlns/"; + schema.Id = DataSetName; + XmlDocument doc = new XmlDocument (); + XmlAttribute attr = null; + ArrayList atts = new ArrayList (); + + nsmgr.AddNamespace ("xs", XmlSchema.Namespace); + nsmgr.AddNamespace (XmlConstants.MsdataPrefix, XmlConstants.MsdataNamespace); + if (Namespace != "") { + nsmgr.AddNamespace (XmlConstants.TnsPrefix, Namespace); + nsmgr.AddNamespace (String.Empty, Namespace); + } + + if (atts.Count > 0) + schema.UnhandledAttributes = atts.ToArray (typeof (XmlAttribute)) as XmlAttribute []; + + XmlSchemaElement elem = new XmlSchemaElement (); + elem.Name = XmlConvert.EncodeName (DataSetName); + + // Add namespaces used in DataSet components (tables, columns, ...) + foreach (DataTable dt in Tables) { + foreach (DataColumn col in dt.Columns) + CheckNamespace (col.Prefix, col.Namespace, nsmgr, schema); + CheckNamespace (dt.Prefix, dt.Namespace, nsmgr, schema); + } + + // Attributes for DataSet element + atts.Clear (); + attr = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.IsDataSet, XmlConstants.MsdataNamespace); + attr.Value = "true"; + atts.Add (attr); + + attr = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.Locale, XmlConstants.MsdataNamespace); + attr.Value = locale.Name; + atts.Add (attr); + + elem.UnhandledAttributes = atts.ToArray (typeof (XmlAttribute)) as XmlAttribute []; + + XmlSchemaComplexType complex = new XmlSchemaComplexType (); + elem.SchemaType = complex; + + XmlSchemaChoice choice = new XmlSchemaChoice (); + complex.Particle = choice; + choice.MaxOccursString = XmlConstants.Unbounded; + + //Write out schema for each table in order + foreach (DataTable table in tables) { + bool isTopLevel = true; + foreach (DataRelation rel in table.ParentRelations) { + if (rel.Nested) { + isTopLevel = false; + break; + } + } + + if (isTopLevel) { + if (table.Namespace != SafeNS (schema.TargetNamespace)) { + XmlSchemaElement extElem = new XmlSchemaElement (); + extElem.RefName = new XmlQualifiedName (table.TableName, table.Namespace); + choice.Items.Add (extElem); + } + else + choice.Items.Add (GetTableSchema (doc, table, schema, nsmgr)); + } + } + + schema.Items.Add (elem); + + AddConstraintsToSchema (elem, constraintPrefix, tables, relations); + foreach (string prefix in nsmgr) { + string ns = nsmgr.LookupNamespace (nsmgr.NameTable.Get (prefix)); + if (prefix != "xmlns" && prefix != "xml" && ns != null && ns != String.Empty) + schema.Namespaces.Add (prefix, ns); + } + return schema; + } + + // Add all constraints in all tables to the schema. + private void AddConstraintsToSchema (XmlSchemaElement elem, string constraintPrefix, DataTableCollection tables, DataRelationCollection relations) + { + // first add all unique constraints. + Hashtable uniqueNames = AddUniqueConstraints (elem, constraintPrefix, tables); + // Add all foriegn key constraints. + AddForeignKeys (uniqueNames, elem, constraintPrefix, relations); + } + + // Add unique constaraints to the schema. + // return hashtable with the names of all XmlSchemaUnique elements we created. + private Hashtable AddUniqueConstraints (XmlSchemaElement elem, string constraintPrefix, DataTableCollection tables) + { + XmlDocument doc = new XmlDocument(); + Hashtable uniqueNames = new Hashtable(); + foreach (DataTable table in tables) { + + foreach (Constraint constaint in table.Constraints) { + + if (constaint is UniqueConstraint) { + ArrayList attrs = new ArrayList (); + XmlAttribute attrib; + UniqueConstraint uqConst = (UniqueConstraint)constaint; + XmlSchemaUnique uniq = new XmlSchemaUnique (); + + // if column of the constraint is hidden do not write the constraint. + bool isHidden = false; + foreach (DataColumn column in uqConst.Columns) { + if (column.ColumnMapping == MappingType.Hidden) { + isHidden = true; + break; + } + } + + if (isHidden) + continue; + + // if constaraint name do not exist in the hashtable we can use it. + if (!uniqueNames.ContainsKey (uqConst.ConstraintName)) { + uniq.Name = uqConst.ConstraintName; + } + // generate new constraint name for the XmlSchemaUnique element. + else { + uniq.Name = uqConst.Table.TableName + "_" + uqConst.ConstraintName; + attrib = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.ConstraintName, XmlConstants.MsdataNamespace); + attrib.Value = uqConst.ConstraintName; + attrs.Add (attrib); + } + if (uqConst.IsPrimaryKey) { + attrib = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.PrimaryKey, XmlConstants.MsdataNamespace); + attrib.Value = "true"; + attrs.Add (attrib); + } + + uniq.UnhandledAttributes = (XmlAttribute[])attrs.ToArray (typeof (XmlAttribute)); + + uniq.Selector = new XmlSchemaXPath(); + uniq.Selector.XPath = ".//"+constraintPrefix + uqConst.Table.TableName; + XmlSchemaXPath field; + foreach (DataColumn column in uqConst.Columns) { + field = new XmlSchemaXPath(); + field.XPath = constraintPrefix+column.ColumnName; + uniq.Fields.Add(field); + } + + elem.Constraints.Add (uniq); + uniqueNames.Add (uniq.Name, null); + } + } + } + return uniqueNames; + } + + // Add the foriegn keys to the schema. + private void AddForeignKeys (Hashtable uniqueNames, XmlSchemaElement elem, string constraintPrefix, DataRelationCollection relations) + { + if (relations == null) return; + + XmlDocument doc = new XmlDocument(); + foreach (DataRelation rel in relations) { + + if (rel.ParentKeyConstraint == null || rel.ChildKeyConstraint == null) + continue; + + bool isHidden = false; + foreach (DataColumn col in rel.ParentColumns) { + if (col.ColumnMapping == MappingType.Hidden) { + isHidden = true; + break; + } + } + foreach (DataColumn col in rel.ChildColumns) { + if (col.ColumnMapping == MappingType.Hidden) { + isHidden = true; + break; + } + } + if (isHidden) + continue; + + ArrayList attrs = new ArrayList (); + XmlAttribute attrib; + XmlSchemaKeyref keyRef = new XmlSchemaKeyref(); + keyRef.Name = rel.RelationName; + ForeignKeyConstraint fkConst = rel.ChildKeyConstraint; + UniqueConstraint uqConst = rel.ParentKeyConstraint; + + string concatName = rel.ParentTable.TableName + "_" + uqConst.ConstraintName; + // first try to find the concatenated name. If we didn't find it - use constraint name. + if (uniqueNames.ContainsKey (concatName)) { + keyRef.Refer = new XmlQualifiedName(concatName); + } + else { + keyRef.Refer = new XmlQualifiedName(uqConst.ConstraintName); + } + + if (rel.Nested) { + attrib = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.IsNested, XmlConstants.MsdataNamespace); + attrib.Value = "true"; + attrs.Add (attrib); + } + + keyRef.Selector = new XmlSchemaXPath(); + keyRef.Selector.XPath = ".//" + constraintPrefix + rel.ChildTable.TableName; + XmlSchemaXPath field; + foreach (DataColumn column in rel.ChildColumns) { + field = new XmlSchemaXPath(); + field.XPath = constraintPrefix+column.ColumnName; + keyRef.Fields.Add(field); + } + keyRef.UnhandledAttributes = (XmlAttribute[])attrs.ToArray (typeof (XmlAttribute)); + elem.Constraints.Add (keyRef); + } + } + + private XmlSchemaElement GetTableSchema (XmlDocument doc, DataTable table, XmlSchema schemaToAdd, XmlNamespaceManager nsmgr) + { + ArrayList elements; + ArrayList atts; + DataColumn simple; + + SplitColumns (table, out atts, out elements, out simple); + + XmlSchemaElement elem = new XmlSchemaElement (); + elem.Name = table.TableName; + + XmlSchemaComplexType complex = new XmlSchemaComplexType (); + elem.SchemaType = complex; + + XmlSchemaObjectCollection schemaAttributes = null; + + if (simple != null) { + // add simpleContent + XmlSchemaSimpleContent simpleContent = new XmlSchemaSimpleContent(); + complex.ContentModel = simpleContent; + + // add column name attribute + XmlAttribute[] xlmAttrs = new XmlAttribute [2]; + xlmAttrs[0] = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.ColumnName, XmlConstants.MsdataNamespace); + xlmAttrs[0].Value = simple.ColumnName; + + // add ordinal attribute + xlmAttrs[1] = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.Ordinal, XmlConstants.MsdataNamespace); + xlmAttrs[1].Value = XmlConvert.ToString (simple.Ordinal); + simpleContent.UnhandledAttributes = xlmAttrs; + + + // add extension + XmlSchemaSimpleContentExtension extension = new XmlSchemaSimpleContentExtension(); + simpleContent.Content = extension; + extension.BaseTypeName = MapType (simple.DataType); + schemaAttributes = extension.Attributes; + } else { + schemaAttributes = complex.Attributes; + //A sequence of element types or a simple content node + // + XmlSchemaSequence seq = new XmlSchemaSequence (); + + foreach (DataColumn col in elements) { + + // Add element for the column. + XmlSchemaElement colElem = new XmlSchemaElement (); + ArrayList xattrs = new ArrayList(); + XmlAttribute xattr; + colElem.Name = col.ColumnName; + + if (col.ColumnName != col.Caption && col.Caption != String.Empty) { + xattr = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.Caption, XmlConstants.MsdataNamespace); + xattr.Value = col.Caption; + xattrs.Add (xattr); + } + + if (col.AutoIncrement == true) { + xattr = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.AutoIncrement, XmlConstants.MsdataNamespace); + xattr.Value = "true"; + xattrs.Add (xattr); + } + + if (col.AutoIncrementSeed != 0) { + xattr = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.AutoIncrementSeed, XmlConstants.MsdataNamespace); + xattr.Value = XmlConvert.ToString (col.AutoIncrementSeed); + xattrs.Add (xattr); + } + + if (col.DefaultValue.ToString () != String.Empty) + colElem.DefaultValue = WriteObjectXml (col.DefaultValue); + + if (col.MaxLength < 0) + colElem.SchemaTypeName = MapType (col.DataType); + + if (colElem.SchemaTypeName == XmlConstants.QnString && col.DataType != typeof (string) + && col.DataType != typeof (char)) { + xattr = doc.CreateAttribute (XmlConstants.MsdataPrefix, XmlConstants.DataType, XmlConstants.MsdataNamespace); + xattr.Value = col.DataType.AssemblyQualifiedName; + xattrs.Add (xattr); + } + + if (col.AllowDBNull) { + colElem.MinOccurs = 0; + } + + //writer.WriteAttributeString (XmlConstants.MsdataPrefix, + // XmlConstants.Ordinal, + // XmlConstants.MsdataNamespace, + // col.Ordinal.ToString ()); + + // Write SimpleType if column have MaxLength + if (col.MaxLength > -1) { + colElem.SchemaType = GetTableSimpleType (doc, col); + } + + colElem.UnhandledAttributes = (XmlAttribute[])xattrs.ToArray(typeof (XmlAttribute)); + seq.Items.Add (colElem); + } + + foreach (DataRelation rel in table.ChildRelations) { + if (rel.Nested) { + if (rel.ChildTable.Namespace != SafeNS (schemaToAdd.TargetNamespace)) { + XmlSchemaElement el = new XmlSchemaElement (); + el.RefName = new XmlQualifiedName (rel.ChildTable.TableName, rel.ChildTable.Namespace); + } else { + XmlSchemaElement el = GetTableSchema (doc, rel.ChildTable, schemaToAdd, nsmgr); + el.MinOccurs = 0; + el.MaxOccursString = "unbounded"; + XmlSchemaComplexType ct = (XmlSchemaComplexType) el.SchemaType; + ct.Name = el.Name; + el.SchemaType = null; + el.SchemaTypeName = new XmlQualifiedName (ct.Name, schemaToAdd.TargetNamespace); + schemaToAdd.Items.Add (ct); + seq.Items.Add (el); + } + } + } + + if (seq.Items.Count > 0) + complex.Particle = seq; + } + + //Then a list of attributes + foreach (DataColumn col in atts) { + // + XmlSchemaAttribute att = new XmlSchemaAttribute (); + att.Name = col.ColumnName; + if (col.Namespace != String.Empty) { + att.Form = XmlSchemaForm.Qualified; + string prefix = col.Prefix == String.Empty ? "app" + schemaToAdd.Namespaces.Count : col.Prefix; + att.Name = prefix + ":" + col.ColumnName; + // FIXME: Handle prefix mapping correctly. + schemaToAdd.Namespaces.Add (prefix, col.Namespace); + } + att.SchemaTypeName = MapType (col.DataType); + schemaAttributes.Add (att); + } + + return elem; + } + + private string SafeNS (string ns) + { + return ns != null ? ns : String.Empty; + } + + private void HandleExternalNamespace (string prefix, string ns, XmlSchema schema) + { + foreach (XmlSchemaExternal ext in schema.Includes) { + XmlSchemaImport imp = ext as XmlSchemaImport; + if (imp != null && imp.Namespace == ns) + return; // nothing to do + } + XmlSchemaImport i = new XmlSchemaImport (); + i.Namespace = ns; + i.SchemaLocation = "_" + prefix + ".xsd"; + schema.Includes.Add (i); + } + + private XmlSchemaSimpleType GetTableSimpleType (XmlDocument doc, DataColumn col) + { + // SimpleType + XmlSchemaSimpleType simple = new XmlSchemaSimpleType (); + + // Restriction + XmlSchemaSimpleTypeRestriction restriction = new XmlSchemaSimpleTypeRestriction (); + restriction.BaseTypeName = MapType (col.DataType); + + // MaxValue + XmlSchemaMaxLengthFacet max = new XmlSchemaMaxLengthFacet (); + max.Value = XmlConvert.ToString (col.MaxLength); + restriction.Facets.Add (max); + + simple.Content = restriction; + return simple; + } + + private void DoWriteXmlSchema (XmlWriter writer) + { + BuildSchema ().Write (writer); + } + + /// + /// Helper function to split columns into attributes elements and simple + /// content + /// + private void SplitColumns (DataTable table, + out ArrayList atts, + out ArrayList elements, + out DataColumn simple) + { + //The columns can be attributes, hidden, elements, or simple content + //There can be 0-1 simple content cols or 0-* elements + atts = new System.Collections.ArrayList (); + elements = new System.Collections.ArrayList (); + simple = null; + + //Sort out the columns + foreach (DataColumn col in table.Columns) { + switch (col.ColumnMapping) { + case MappingType.Attribute: + atts.Add (col); + break; + case MappingType.Element: + elements.Add (col); + break; + case MappingType.SimpleContent: + if (simple != null) { + throw new System.InvalidOperationException ("There may only be one simple content element"); + } + simple = col; + break; + default: + //ignore Hidden elements + break; + } + } + } + + private void WriteDiffGramElement(XmlWriter writer) + { + WriteStartElement (writer, XmlWriteMode.DiffGram, XmlConstants.DiffgrNamespace, XmlConstants.DiffgrPrefix, "diffgram"); + WriteAttributeString(writer, XmlWriteMode.DiffGram, null, "xmlns", XmlConstants.MsdataPrefix, XmlConstants.MsdataNamespace); + } + + private void SetRowsID() + { + foreach (DataTable Table in Tables) + SetTableRowsID (Table); + } + + private void SetTableRowsID (DataTable Table) + { + int dataRowID = 0; + foreach (DataRow Row in Table.Rows) { + Row.XmlRowID = dataRowID; + dataRowID++; + } + } + + + private XmlQualifiedName MapType (Type type) + { + switch (Type.GetTypeCode (type)) { + case TypeCode.String: return XmlConstants.QnString; + case TypeCode.Int16: return XmlConstants.QnShort; + case TypeCode.Int32: return XmlConstants.QnInt; + case TypeCode.Int64: return XmlConstants.QnLong; + case TypeCode.Boolean: return XmlConstants.QnBoolean; + case TypeCode.Byte: return XmlConstants.QnUnsignedByte; + //case TypeCode.Char: return XmlConstants.QnChar; + case TypeCode.DateTime: return XmlConstants.QnDateTime; + case TypeCode.Decimal: return XmlConstants.QnDecimal; + case TypeCode.Double: return XmlConstants.QnDouble; + case TypeCode.SByte: return XmlConstants.QnSbyte; + case TypeCode.Single: return XmlConstants.QnFloat; + case TypeCode.UInt16: return XmlConstants.QnUsignedShort; + case TypeCode.UInt32: return XmlConstants.QnUnsignedInt; + case TypeCode.UInt64: return XmlConstants.QnUnsignedLong; + } + + if (typeof (TimeSpan) == type) + return XmlConstants.QnDuration; + else if (typeof (System.Uri) == type) + return XmlConstants.QnUri; + else if (typeof (byte[]) == type) + return XmlConstants.QnBase64Binary; + else if (typeof (XmlQualifiedName) == type) + return XmlConstants.QnXmlQualifiedName; + else + return XmlConstants.QnString; + } + + #endregion //Private Xml Serialisation } }