//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // // [....] // [....] // [....] //------------------------------------------------------------------------------ namespace System.Data { using System; using System.Diagnostics; using System.Collections; using System.ComponentModel; using System.Globalization; /// /// /// Represents the collection of tables for the . /// /// [ DefaultEvent("CollectionChanged"), Editor("Microsoft.VSDesigner.Data.Design.TablesCollectionEditor, " + AssemblyRef.MicrosoftVSDesigner, "System.Drawing.Design.UITypeEditor, " + AssemblyRef.SystemDrawing), ListBindable(false), ] public sealed class DataTableCollection : InternalDataCollectionBase { private readonly DataSet dataSet = null; // private DataTable[] tables = new DataTable[2]; // private int tableCount = 0; private readonly ArrayList _list = new ArrayList(); private int defaultNameIndex = 1; private DataTable[] delayedAddRangeTables = null; private CollectionChangeEventHandler onCollectionChangedDelegate = null; private CollectionChangeEventHandler onCollectionChangingDelegate = null; private static int _objectTypeCount; // Bid counter private readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount); /// /// DataTableCollection constructor. Used only by DataSet. /// internal DataTableCollection(DataSet dataSet) { Bid.Trace(" %d#, dataSet=%d\n", ObjectID, (dataSet != null) ? dataSet.ObjectID : 0); this.dataSet = dataSet; } /// /// /// Gets the tables /// in the collection as an object. /// /// protected override ArrayList List { get { return _list; } } internal int ObjectID { get { return _objectID; } } /// /// Gets the table specified by its index. /// public DataTable this[int index] { get { try { // Perf: use the readonly _list field directly and let ArrayList check the range return(DataTable) _list[index]; } catch(ArgumentOutOfRangeException) { throw ExceptionBuilder.TableOutOfRange(index); } } } /// /// Gets the table in the collection with the given name (not case-sensitive). /// public DataTable this[string name] { get { int index = InternalIndexOf(name); if (index == -2) { throw ExceptionBuilder.CaseInsensitiveNameConflict(name); } if (index == -3) { throw ExceptionBuilder.NamespaceNameConflict(name); } return (index < 0) ? null : (DataTable)_list[index]; } } public DataTable this[string name, string tableNamespace] { get { if (tableNamespace == null) throw ExceptionBuilder.ArgumentNull("tableNamespace"); int index = InternalIndexOf(name, tableNamespace); if (index == -2) { throw ExceptionBuilder.CaseInsensitiveNameConflict(name); } return (index < 0) ? null : (DataTable)_list[index]; } } // Case-sensitive search in Schema, data and diffgram loading internal DataTable GetTable(string name, string ns) { for (int i = 0; i < _list.Count; i++) { DataTable table = (DataTable) _list[i]; if (table.TableName == name && table.Namespace == ns) return table; } return null; } // Case-sensitive smart search: it will look for a table using the ns only if required to // resolve a conflict internal DataTable GetTableSmart(string name, string ns){ int fCount = 0; DataTable fTable = null; for (int i = 0; i < _list.Count; i++) { DataTable table = (DataTable) _list[i]; if (table.TableName == name) { if (table.Namespace == ns) return table; fCount++; fTable = table; } } // if we get here we didn't match the namespace // so return the table only if fCount==1 (it's the only one) return (fCount == 1) ? fTable : null; } /// /// /// Adds /// the specified table to the collection. /// /// public void Add(DataTable table) { IntPtr hscp; Bid.ScopeEnter(out hscp, " %d#, table=%d\n", ObjectID, (table!= null) ? table.ObjectID : 0); try { OnCollectionChanging(new CollectionChangeEventArgs(CollectionChangeAction.Add, table)); BaseAdd(table); ArrayAdd(table); if (table.SetLocaleValue(dataSet.Locale, false, false) || table.SetCaseSensitiveValue(dataSet.CaseSensitive, false, false)) { table.ResetIndexes(); } OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Add, table)); } finally { Bid.ScopeLeave(ref hscp); } } /// /// [To be supplied.] /// public void AddRange(DataTable[] tables) { IntPtr hscp; Bid.ScopeEnter(out hscp, " %d#\n", ObjectID); try { if (dataSet.fInitInProgress) { delayedAddRangeTables = tables; return; } if (tables != null) { foreach(DataTable table in tables) { if (table != null) { Add(table); } } } } finally{ Bid.ScopeLeave(ref hscp); } } /// /// /// Creates a table with the given name and adds it to the /// collection. /// /// public DataTable Add(string name) { DataTable table = new DataTable(name); // fxcop: new DataTable should inherit the CaseSensitive, Locale, Namespace from DataSet Add(table); return table; } public DataTable Add(string name, string tableNamespace) { DataTable table = new DataTable(name, tableNamespace); // fxcop: new DataTable should inherit the CaseSensitive, Locale from DataSet Add(table); return table; } /// /// /// Creates a new table with a default name and adds it to /// the collection. /// /// public DataTable Add() { DataTable table = new DataTable(); // fxcop: new DataTable should inherit the CaseSensitive, Locale, Namespace from DataSet Add(table); return table; } /// /// /// Occurs when the collection is changed. /// /// [ResDescriptionAttribute(Res.collectionChangedEventDescr)] public event CollectionChangeEventHandler CollectionChanged { add { Bid.Trace(" %d#\n", ObjectID); onCollectionChangedDelegate += value; } remove { Bid.Trace(" %d#\n", ObjectID); onCollectionChangedDelegate -= value; } } /// /// [To be supplied.] /// public event CollectionChangeEventHandler CollectionChanging { add { Bid.Trace(" %d#\n", ObjectID); onCollectionChangingDelegate += value; } remove { Bid.Trace(" %d#\n", ObjectID); onCollectionChangingDelegate -= value; } } /// /// Adds the table to the tables array. /// private void ArrayAdd(DataTable table) { _list.Add(table); } /// /// Creates a new default name. /// internal string AssignName() { string newName = null; // RAIDBUG: 91671 while(this.Contains( newName = MakeName(defaultNameIndex))) defaultNameIndex++; return newName; } /// /// Does verification on the table and it's name, and points the table at the dataSet that owns this collection. /// An ArgumentNullException is thrown if this table is null. An ArgumentException is thrown if this table /// already belongs to this collection, belongs to another collection. /// A DuplicateNameException is thrown if this collection already has a table with the same /// name (case insensitive). /// private void BaseAdd(DataTable table) { if (table == null) throw ExceptionBuilder.ArgumentNull("table"); if (table.DataSet == dataSet) throw ExceptionBuilder.TableAlreadyInTheDataSet(); if (table.DataSet != null) throw ExceptionBuilder.TableAlreadyInOtherDataSet(); if (table.TableName.Length == 0) table.TableName = AssignName(); else { if (NamesEqual(table.TableName, dataSet.DataSetName, false, dataSet.Locale) != 0 && !table.fNestedInDataset) throw ExceptionBuilder.DatasetConflictingName(dataSet.DataSetName); RegisterName(table.TableName, table.Namespace); } table.SetDataSet(dataSet); //must run thru the document incorporating the addition of this data table //must make sure there is no other schema component which have the same // identity as this table (for example, there must not be a table with the // same identity as a column in this schema. } /// /// BaseGroupSwitch will intelligently remove and add tables from the collection. /// private void BaseGroupSwitch(DataTable[] oldArray, int oldLength, DataTable[] newArray, int newLength) { // We're doing a smart diff of oldArray and newArray to find out what // should be removed. We'll pass through oldArray and see if it exists // in newArray, and if not, do remove work. newBase is an opt. in case // the arrays have similar prefixes. int newBase = 0; for (int oldCur = 0; oldCur < oldLength; oldCur++) { bool found = false; for (int newCur = newBase; newCur < newLength; newCur++) { if (oldArray[oldCur] == newArray[newCur]) { if (newBase == newCur) { newBase++; } found = true; break; } } if (!found) { // This means it's in oldArray and not newArray. Remove it. if (oldArray[oldCur].DataSet == dataSet) { BaseRemove(oldArray[oldCur]); } } } // Now, let's pass through news and those that don't belong, add them. for (int newCur = 0; newCur < newLength; newCur++) { if (newArray[newCur].DataSet != dataSet) { BaseAdd(newArray[newCur]); _list.Add(newArray[newCur]); } } } /// /// Does verification on the table and it's name, and clears the table's dataSet pointer. /// An ArgumentNullException is thrown if this table is null. An ArgumentException is thrown /// if this table doesn't belong to this collection or if this table is part of a relationship. /// private void BaseRemove(DataTable table) { if (CanRemove(table, true)) { UnregisterName(table.TableName); table.SetDataSet(null); } _list.Remove(table); dataSet.OnRemovedTable(table); } /// /// /// Verifies if a given table can be removed from the collection. /// /// public bool CanRemove(DataTable table) { return CanRemove(table, false); } internal bool CanRemove(DataTable table, bool fThrowException) { IntPtr hscp; Bid.ScopeEnter(out hscp, " %d#, table=%d, fThrowException=%d{bool}\n", ObjectID, (table != null)? table.ObjectID : 0 , fThrowException); try { if (table == null) { if (!fThrowException) return false; else throw ExceptionBuilder.ArgumentNull("table"); } if (table.DataSet != dataSet) { if (!fThrowException) return false; else throw ExceptionBuilder.TableNotInTheDataSet(table.TableName); } // allow subclasses to throw. dataSet.OnRemoveTable(table); if (table.ChildRelations.Count != 0 || table.ParentRelations.Count != 0) { if (!fThrowException) return false; else throw ExceptionBuilder.TableInRelation(); } for (ParentForeignKeyConstraintEnumerator constraints = new ParentForeignKeyConstraintEnumerator(dataSet, table); constraints.GetNext();) { ForeignKeyConstraint constraint = constraints.GetForeignKeyConstraint(); if (constraint.Table == table && constraint.RelatedTable == table) // we can go with (constraint.Table == constraint.RelatedTable) continue; if (!fThrowException) return false; else throw ExceptionBuilder.TableInConstraint(table, constraint); } for (ChildForeignKeyConstraintEnumerator constraints = new ChildForeignKeyConstraintEnumerator(dataSet, table); constraints.GetNext();) { ForeignKeyConstraint constraint = constraints.GetForeignKeyConstraint(); if (constraint.Table == table && constraint.RelatedTable == table) // bug 97670 continue; if (!fThrowException) return false; else throw ExceptionBuilder.TableInConstraint(table, constraint); } return true; } finally{ Bid.ScopeLeave(ref hscp); } } /// /// /// Clears the collection of any tables. /// /// public void Clear() { IntPtr hscp; Bid.ScopeEnter(out hscp, " %d#\n", ObjectID); try { int oldLength = _list.Count; DataTable[] tables = new DataTable[_list.Count]; _list.CopyTo(tables, 0); OnCollectionChanging(RefreshEventArgs); if (dataSet.fInitInProgress && delayedAddRangeTables != null) { delayedAddRangeTables = null; } BaseGroupSwitch(tables, oldLength, null, 0); _list.Clear(); OnCollectionChanged(RefreshEventArgs); } finally { Bid.ScopeLeave(ref hscp); } } /// /// /// Checks if a table, specified by name, exists in the collection. /// /// public bool Contains(string name) { return (InternalIndexOf(name) >= 0); } public bool Contains(string name, string tableNamespace) { if (name == null) throw ExceptionBuilder.ArgumentNull("name"); if (tableNamespace == null) throw ExceptionBuilder.ArgumentNull("tableNamespace"); return (InternalIndexOf(name, tableNamespace) >= 0); } internal bool Contains(string name, string tableNamespace, bool checkProperty, bool caseSensitive) { if (!caseSensitive) return (InternalIndexOf(name) >= 0); // Case-Sensitive compare int count = _list.Count; for (int i = 0; i < count; i++) { DataTable table = (DataTable) _list[i]; // this may be needed to check wether the cascading is creating some conflicts string ns = checkProperty ? table.Namespace : table.tableNamespace ; if (NamesEqual(table.TableName, name, true, dataSet.Locale) == 1 && (ns == tableNamespace)) return true; } return false; } internal bool Contains(string name, bool caseSensitive) { if (!caseSensitive) return (InternalIndexOf(name) >= 0); // Case-Sensitive compare int count = _list.Count; for (int i = 0; i < count; i++) { DataTable table = (DataTable) _list[i]; if (NamesEqual(table.TableName, name, true, dataSet.Locale) == 1 ) return true; } return false; } public void CopyTo(DataTable[] array, int index) { if (array==null) throw ExceptionBuilder.ArgumentNull("array"); if (index < 0) throw ExceptionBuilder.ArgumentOutOfRange("index"); if (array.Length - index < _list.Count) throw ExceptionBuilder.InvalidOffsetLength(); for(int i = 0; i < _list.Count; ++i) { array[index + i] = (DataTable)_list[i]; } } /// /// /// Returns the index of a specified . /// /// public int IndexOf(DataTable table) { int tableCount = _list.Count; for (int i = 0; i < tableCount; ++i) { if (table == (DataTable) _list[i]) { return i; } } return -1; } /// /// /// Returns the index of the /// table with the given name (case insensitive), or -1 if the table /// doesn't exist in the collection. /// /// public int IndexOf(string tableName) { int index = InternalIndexOf(tableName); return (index < 0) ? -1 : index; } public int IndexOf(string tableName, string tableNamespace) { return IndexOf( tableName, tableNamespace, true); } internal int IndexOf(string tableName, string tableNamespace, bool chekforNull) { // this should be public! why it is missing? if (chekforNull) { if (tableName == null) throw ExceptionBuilder.ArgumentNull("tableName"); if (tableNamespace == null) throw ExceptionBuilder.ArgumentNull("tableNamespace"); } int index = InternalIndexOf(tableName, tableNamespace); return (index < 0) ? -1 : index; } internal void ReplaceFromInference(System.Collections.Generic.List tableList) { Debug.Assert(_list.Count == tableList.Count, "Both lists should have equal numbers of tables"); _list.Clear(); _list.AddRange(tableList); } // Return value: // >= 0: find the match // -1: No match // -2: At least two matches with different cases // -3: At least two matches with different namespaces internal int InternalIndexOf(string tableName) { int cachedI = -1; if ((null != tableName) && (0 < tableName.Length)) { int count = _list.Count; int result = 0; for (int i = 0; i < count; i++) { DataTable table = (DataTable) _list[i]; result = NamesEqual(table.TableName, tableName, false, dataSet.Locale); if (result == 1) { // ok, we have found a table with the same name. // let's see if there are any others with the same name // if any let's return (-3) otherwise... for (int j=i+1;j= 0: find the match // -1: No match // -2: At least two matches with different cases internal int InternalIndexOf(string tableName, string tableNamespace) { int cachedI = -1; if ((null != tableName) && (0 < tableName.Length)) { int count = _list.Count; int result = 0; for (int i = 0; i < count; i++) { DataTable table = (DataTable) _list[i]; result = NamesEqual(table.TableName, tableName, false, dataSet.Locale); if ((result == 1) && (table.Namespace == tableNamespace)) return i; if ((result == -1) && (table.Namespace == tableNamespace)) cachedI = (cachedI == -1) ? i : -2; } } return cachedI; } internal void FinishInitCollection() { if (delayedAddRangeTables != null) { foreach(DataTable table in delayedAddRangeTables) { if (table != null) { Add(table); } } delayedAddRangeTables = null; } } /// /// Makes a default name with the given index. e.g. Table1, Table2, ... Tablei /// private string MakeName(int index) { if (1 == index) { return "Table1"; } return "Table" + index.ToString(System.Globalization.CultureInfo.InvariantCulture); } /// /// /// Raises the event. /// /// private void OnCollectionChanged(CollectionChangeEventArgs ccevent) { if (onCollectionChangedDelegate != null) { Bid.Trace(" %d#\n", ObjectID); onCollectionChangedDelegate(this, ccevent); } } /// /// [To be supplied.] /// private void OnCollectionChanging(CollectionChangeEventArgs ccevent) { if (onCollectionChangingDelegate != null) { Bid.Trace(" %d#\n", ObjectID); onCollectionChangingDelegate(this, ccevent); } } /// /// Registers this name as being used in the collection. Will throw an ArgumentException /// if the name is already being used. Called by Add, All property, and Table.TableName property. /// if the name is equivalent to the next default name to hand out, we increment our defaultNameIndex. /// internal void RegisterName(string name, string tbNamespace) { Bid.Trace(" %d#, name='%ls', tbNamespace='%ls'\n", ObjectID, name, tbNamespace); Debug.Assert (name != null); CultureInfo locale = dataSet.Locale; int tableCount = _list.Count; for (int i = 0; i < tableCount; i++) { DataTable table = (DataTable) _list[i]; if (NamesEqual(name, table.TableName, true, locale) != 0 && (tbNamespace == table.Namespace)) { throw ExceptionBuilder.DuplicateTableName(((DataTable) _list[i]).TableName); } } if (NamesEqual(name, MakeName(defaultNameIndex), true, locale) != 0) { defaultNameIndex++; } } /// /// /// Removes the specified table from the collection. /// /// public void Remove(DataTable table) { IntPtr hscp; Bid.ScopeEnter(out hscp, " %d#, table=%d\n", ObjectID, (table != null) ? table.ObjectID : 0); try { OnCollectionChanging(new CollectionChangeEventArgs(CollectionChangeAction.Remove, table)); BaseRemove(table); OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Remove, table)); } finally{ Bid.ScopeLeave(ref hscp); } } /// /// /// Removes the /// table at the given index from the collection /// /// public void RemoveAt(int index) { IntPtr hscp; Bid.ScopeEnter(out hscp, " %d#, index=%d\n", ObjectID, index); try { DataTable dt = this[index]; if (dt == null) throw ExceptionBuilder.TableOutOfRange(index); Remove(dt); } finally { Bid.ScopeLeave(ref hscp); } } /// /// /// Removes the table with a specified name from the /// collection. /// /// public void Remove(string name) { IntPtr hscp; Bid.ScopeEnter(out hscp, " %d#, name='%ls'\n", ObjectID, name); try { DataTable dt = this[name]; if (dt == null) throw ExceptionBuilder.TableNotInTheDataSet(name); Remove(dt); } finally{ Bid.ScopeLeave(ref hscp); } } public void Remove(string name, string tableNamespace) { if (name == null) throw ExceptionBuilder.ArgumentNull("name"); if (tableNamespace == null) throw ExceptionBuilder.ArgumentNull("tableNamespace"); DataTable dt = this[name, tableNamespace]; if (dt == null) throw ExceptionBuilder.TableNotInTheDataSet(name); Remove(dt); } /// /// Unregisters this name as no longer being used in the collection. Called by Remove, All property, and /// Table.TableName property. If the name is equivalent to the last proposed default name, we walk backwards /// to find the next proper default name to use. /// internal void UnregisterName(string name) { Bid.Trace(" %d#, name='%ls'\n", ObjectID, name); if (NamesEqual(name, MakeName(defaultNameIndex - 1), true, dataSet.Locale) != 0) { do { defaultNameIndex--; } while (defaultNameIndex > 1 && !Contains(MakeName(defaultNameIndex - 1))); } } } }