1 //------------------------------------------------------------------------------
2 // <copyright file="DataTableCollection.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="true" primary="false">Microsoft</owner>
7 // <owner current="false" primary="false">Microsoft</owner>
8 //------------------------------------------------------------------------------
10 namespace System.Data {
12 using System.Diagnostics;
13 using System.Collections;
14 using System.ComponentModel;
15 using System.Globalization;
19 /// Represents the collection of tables for the <see cref='System.Data.DataSet'/>.
23 DefaultEvent("CollectionChanged"),
24 Editor("Microsoft.VSDesigner.Data.Design.TablesCollectionEditor, " + AssemblyRef.MicrosoftVSDesigner, "System.Drawing.Design.UITypeEditor, " + AssemblyRef.SystemDrawing),
27 public sealed class DataTableCollection : InternalDataCollectionBase {
29 private readonly DataSet dataSet = null;
30 // private DataTable[] tables = new DataTable[2];
31 // private int tableCount = 0;
32 private readonly ArrayList _list = new ArrayList();
33 private int defaultNameIndex = 1;
34 private DataTable[] delayedAddRangeTables = null;
36 private CollectionChangeEventHandler onCollectionChangedDelegate = null;
37 private CollectionChangeEventHandler onCollectionChangingDelegate = null;
39 private static int _objectTypeCount; // Bid counter
40 private readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
43 /// DataTableCollection constructor. Used only by DataSet.
45 internal DataTableCollection(DataSet dataSet) {
46 Bid.Trace("<ds.DataTableCollection.DataTableCollection|INFO> %d#, dataSet=%d\n", ObjectID, (dataSet != null) ? dataSet.ObjectID : 0);
47 this.dataSet = dataSet;
53 /// in the collection as an object.
56 protected override ArrayList List {
62 internal int ObjectID {
69 /// <para>Gets the table specified by its index.</para>
71 public DataTable this[int index] {
73 try { // Perf: use the readonly _list field directly and let ArrayList check the range
74 return(DataTable) _list[index];
76 catch(ArgumentOutOfRangeException) {
77 throw ExceptionBuilder.TableOutOfRange(index);
83 /// <para>Gets the table in the collection with the given name (not case-sensitive).</para>
85 public DataTable this[string name] {
87 int index = InternalIndexOf(name);
89 throw ExceptionBuilder.CaseInsensitiveNameConflict(name);
92 throw ExceptionBuilder.NamespaceNameConflict(name);
94 return (index < 0) ? null : (DataTable)_list[index];
98 public DataTable this[string name, string tableNamespace] {
100 if (tableNamespace == null)
101 throw ExceptionBuilder.ArgumentNull("tableNamespace");
102 int index = InternalIndexOf(name, tableNamespace);
104 throw ExceptionBuilder.CaseInsensitiveNameConflict(name);
106 return (index < 0) ? null : (DataTable)_list[index];
110 // Case-sensitive search in Schema, data and diffgram loading
111 internal DataTable GetTable(string name, string ns)
113 for (int i = 0; i < _list.Count; i++) {
114 DataTable table = (DataTable) _list[i];
115 if (table.TableName == name && table.Namespace == ns)
121 // Case-sensitive smart search: it will look for a table using the ns only if required to
122 // resolve a conflict
123 internal DataTable GetTableSmart(string name, string ns){
125 DataTable fTable = null;
126 for (int i = 0; i < _list.Count; i++) {
127 DataTable table = (DataTable) _list[i];
128 if (table.TableName == name) {
129 if (table.Namespace == ns)
135 // if we get here we didn't match the namespace
136 // so return the table only if fCount==1 (it's the only one)
137 return (fCount == 1) ? fTable : null;
142 /// the specified table to the collection.
145 public void Add(DataTable table) {
147 Bid.ScopeEnter(out hscp, "<ds.DataTableCollection.Add|API> %d#, table=%d\n", ObjectID, (table!= null) ? table.ObjectID : 0);
149 OnCollectionChanging(new CollectionChangeEventArgs(CollectionChangeAction.Add, table));
153 if (table.SetLocaleValue(dataSet.Locale, false, false) ||
154 table.SetCaseSensitiveValue(dataSet.CaseSensitive, false, false)) {
155 table.ResetIndexes();
157 OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Add, table));
160 Bid.ScopeLeave(ref hscp);
165 /// <para>[To be supplied.]</para>
167 public void AddRange(DataTable[] tables) {
169 Bid.ScopeEnter(out hscp, "<ds.DataTableCollection.AddRange|API> %d#\n", ObjectID);
171 if (dataSet.fInitInProgress) {
172 delayedAddRangeTables = tables;
176 if (tables != null) {
177 foreach(DataTable table in tables) {
185 Bid.ScopeLeave(ref hscp);
191 /// Creates a table with the given name and adds it to the
195 public DataTable Add(string name) {
196 DataTable table = new DataTable(name);
197 // fxcop: new DataTable should inherit the CaseSensitive, Locale, Namespace from DataSet
202 public DataTable Add(string name, string tableNamespace) {
203 DataTable table = new DataTable(name, tableNamespace);
204 // fxcop: new DataTable should inherit the CaseSensitive, Locale from DataSet
210 /// Creates a new table with a default name and adds it to
214 public DataTable Add() {
215 DataTable table = new DataTable();
216 // fxcop: new DataTable should inherit the CaseSensitive, Locale, Namespace from DataSet
223 /// Occurs when the collection is changed.
226 [ResDescriptionAttribute(Res.collectionChangedEventDescr)]
227 public event CollectionChangeEventHandler CollectionChanged {
229 Bid.Trace("<ds.DataTableCollection.add_CollectionChanged|API> %d#\n", ObjectID);
230 onCollectionChangedDelegate += value;
233 Bid.Trace("<ds.DataTableCollection.remove_CollectionChanged|API> %d#\n", ObjectID);
234 onCollectionChangedDelegate -= value;
239 /// <para>[To be supplied.]</para>
241 public event CollectionChangeEventHandler CollectionChanging {
243 Bid.Trace("<ds.DataTableCollection.add_CollectionChanging|API> %d#\n", ObjectID);
244 onCollectionChangingDelegate += value;
247 Bid.Trace("<ds.DataTableCollection.remove_CollectionChanging|API> %d#\n", ObjectID);
248 onCollectionChangingDelegate -= value;
253 /// Adds the table to the tables array.
255 private void ArrayAdd(DataTable table) {
260 /// Creates a new default name.
262 internal string AssignName() {
263 string newName = null;
265 while(this.Contains( newName = MakeName(defaultNameIndex)))
271 /// Does verification on the table and it's name, and points the table at the dataSet that owns this collection.
272 /// An ArgumentNullException is thrown if this table is null. An ArgumentException is thrown if this table
273 /// already belongs to this collection, belongs to another collection.
274 /// A DuplicateNameException is thrown if this collection already has a table with the same
275 /// name (case insensitive).
277 private void BaseAdd(DataTable table) {
279 throw ExceptionBuilder.ArgumentNull("table");
280 if (table.DataSet == dataSet)
281 throw ExceptionBuilder.TableAlreadyInTheDataSet();
282 if (table.DataSet != null)
283 throw ExceptionBuilder.TableAlreadyInOtherDataSet();
285 if (table.TableName.Length == 0)
286 table.TableName = AssignName();
288 if (NamesEqual(table.TableName, dataSet.DataSetName, false, dataSet.Locale) != 0 && !table.fNestedInDataset)
289 throw ExceptionBuilder.DatasetConflictingName(dataSet.DataSetName);
290 RegisterName(table.TableName, table.Namespace);
293 table.SetDataSet(dataSet);
295 //must run thru the document incorporating the addition of this data table
296 //must make sure there is no other schema component which have the same
297 // identity as this table (for example, there must not be a table with the
298 // same identity as a column in this schema.
302 /// BaseGroupSwitch will intelligently remove and add tables from the collection.
304 private void BaseGroupSwitch(DataTable[] oldArray, int oldLength, DataTable[] newArray, int newLength) {
305 // We're doing a smart diff of oldArray and newArray to find out what
306 // should be removed. We'll pass through oldArray and see if it exists
307 // in newArray, and if not, do remove work. newBase is an opt. in case
308 // the arrays have similar prefixes.
310 for (int oldCur = 0; oldCur < oldLength; oldCur++) {
312 for (int newCur = newBase; newCur < newLength; newCur++) {
313 if (oldArray[oldCur] == newArray[newCur]) {
314 if (newBase == newCur) {
322 // This means it's in oldArray and not newArray. Remove it.
323 if (oldArray[oldCur].DataSet == dataSet) {
324 BaseRemove(oldArray[oldCur]);
329 // Now, let's pass through news and those that don't belong, add them.
330 for (int newCur = 0; newCur < newLength; newCur++) {
331 if (newArray[newCur].DataSet != dataSet) {
332 BaseAdd(newArray[newCur]);
333 _list.Add(newArray[newCur]);
339 /// Does verification on the table and it's name, and clears the table's dataSet pointer.
340 /// An ArgumentNullException is thrown if this table is null. An ArgumentException is thrown
341 /// if this table doesn't belong to this collection or if this table is part of a relationship.
343 private void BaseRemove(DataTable table) {
344 if (CanRemove(table, true)) {
345 UnregisterName(table.TableName);
346 table.SetDataSet(null);
350 dataSet.OnRemovedTable(table);
355 /// Verifies if a given table can be removed from the collection.
358 public bool CanRemove(DataTable table) {
359 return CanRemove(table, false);
362 internal bool CanRemove(DataTable table, bool fThrowException) {
364 Bid.ScopeEnter(out hscp, "<ds.DataTableCollection.CanRemove|INFO> %d#, table=%d, fThrowException=%d{bool}\n", ObjectID, (table != null)? table.ObjectID : 0 , fThrowException);
367 if (!fThrowException)
370 throw ExceptionBuilder.ArgumentNull("table");
372 if (table.DataSet != dataSet) {
373 if (!fThrowException)
376 throw ExceptionBuilder.TableNotInTheDataSet(table.TableName);
379 // allow subclasses to throw.
380 dataSet.OnRemoveTable(table);
382 if (table.ChildRelations.Count != 0 || table.ParentRelations.Count != 0) {
383 if (!fThrowException)
386 throw ExceptionBuilder.TableInRelation();
389 for (ParentForeignKeyConstraintEnumerator constraints = new ParentForeignKeyConstraintEnumerator(dataSet, table); constraints.GetNext();) {
390 ForeignKeyConstraint constraint = constraints.GetForeignKeyConstraint();
391 if (constraint.Table == table && constraint.RelatedTable == table) // we can go with (constraint.Table == constraint.RelatedTable)
393 if (!fThrowException)
396 throw ExceptionBuilder.TableInConstraint(table, constraint);
399 for (ChildForeignKeyConstraintEnumerator constraints = new ChildForeignKeyConstraintEnumerator(dataSet, table); constraints.GetNext();) {
400 ForeignKeyConstraint constraint = constraints.GetForeignKeyConstraint();
401 if (constraint.Table == table && constraint.RelatedTable == table) // bug 97670
404 if (!fThrowException)
407 throw ExceptionBuilder.TableInConstraint(table, constraint);
413 Bid.ScopeLeave(ref hscp);
419 /// Clears the collection of any tables.
422 public void Clear() {
424 Bid.ScopeEnter(out hscp, "<ds.DataTableCollection.Clear|API> %d#\n", ObjectID);
426 int oldLength = _list.Count;
427 DataTable[] tables = new DataTable[_list.Count];
428 _list.CopyTo(tables, 0);
430 OnCollectionChanging(RefreshEventArgs);
432 if (dataSet.fInitInProgress && delayedAddRangeTables != null) {
433 delayedAddRangeTables = null;
436 BaseGroupSwitch(tables, oldLength, null, 0);
439 OnCollectionChanged(RefreshEventArgs);
442 Bid.ScopeLeave(ref hscp);
448 /// Checks if a table, specified by name, exists in the collection.
451 public bool Contains(string name) {
452 return (InternalIndexOf(name) >= 0);
455 public bool Contains(string name, string tableNamespace) {
457 throw ExceptionBuilder.ArgumentNull("name");
459 if (tableNamespace == null)
460 throw ExceptionBuilder.ArgumentNull("tableNamespace");
462 return (InternalIndexOf(name, tableNamespace) >= 0);
465 internal bool Contains(string name, string tableNamespace, bool checkProperty, bool caseSensitive) {
467 return (InternalIndexOf(name) >= 0);
469 // Case-Sensitive compare
470 int count = _list.Count;
471 for (int i = 0; i < count; i++) {
472 DataTable table = (DataTable) _list[i];
473 // this may be needed to check wether the cascading is creating some conflicts
474 string ns = checkProperty ? table.Namespace : table.tableNamespace ;
475 if (NamesEqual(table.TableName, name, true, dataSet.Locale) == 1 && (ns == tableNamespace))
481 internal bool Contains(string name, bool caseSensitive) {
483 return (InternalIndexOf(name) >= 0);
485 // Case-Sensitive compare
486 int count = _list.Count;
487 for (int i = 0; i < count; i++) {
488 DataTable table = (DataTable) _list[i];
489 if (NamesEqual(table.TableName, name, true, dataSet.Locale) == 1 )
495 public void CopyTo(DataTable[] array, int index) {
497 throw ExceptionBuilder.ArgumentNull("array");
499 throw ExceptionBuilder.ArgumentOutOfRange("index");
500 if (array.Length - index < _list.Count)
501 throw ExceptionBuilder.InvalidOffsetLength();
502 for(int i = 0; i < _list.Count; ++i) {
503 array[index + i] = (DataTable)_list[i];
509 /// Returns the index of a specified <see cref='System.Data.DataTable'/>.
512 public int IndexOf(DataTable table) {
513 int tableCount = _list.Count;
514 for (int i = 0; i < tableCount; ++i) {
515 if (table == (DataTable) _list[i]) {
524 /// Returns the index of the
525 /// table with the given name (case insensitive), or -1 if the table
526 /// doesn't exist in the collection.
529 public int IndexOf(string tableName) {
530 int index = InternalIndexOf(tableName);
531 return (index < 0) ? -1 : index;
534 public int IndexOf(string tableName, string tableNamespace) {
535 return IndexOf( tableName, tableNamespace, true);
538 internal int IndexOf(string tableName, string tableNamespace, bool chekforNull) { // this should be public! why it is missing?
540 if (tableName == null)
541 throw ExceptionBuilder.ArgumentNull("tableName");
542 if (tableNamespace == null)
543 throw ExceptionBuilder.ArgumentNull("tableNamespace");
545 int index = InternalIndexOf(tableName, tableNamespace);
546 return (index < 0) ? -1 : index;
549 internal void ReplaceFromInference(System.Collections.Generic.List<DataTable> tableList) {
550 Debug.Assert(_list.Count == tableList.Count, "Both lists should have equal numbers of tables");
552 _list.AddRange(tableList);
556 // >= 0: find the match
558 // -2: At least two matches with different cases
559 // -3: At least two matches with different namespaces
560 internal int InternalIndexOf(string tableName) {
562 if ((null != tableName) && (0 < tableName.Length)) {
563 int count = _list.Count;
565 for (int i = 0; i < count; i++) {
566 DataTable table = (DataTable) _list[i];
567 result = NamesEqual(table.TableName, tableName, false, dataSet.Locale);
569 // ok, we have found a table with the same name.
570 // let's see if there are any others with the same name
571 // if any let's return (-3) otherwise...
572 for (int j=i+1;j<count;j++) {
573 DataTable dupTable = (DataTable) _list[j];
574 if (NamesEqual(dupTable.TableName, tableName, false, dataSet.Locale) == 1)
577 //... let's just return i
582 cachedI = (cachedI == -1) ? i : -2;
589 // >= 0: find the match
591 // -2: At least two matches with different cases
592 internal int InternalIndexOf(string tableName, string tableNamespace) {
594 if ((null != tableName) && (0 < tableName.Length)) {
595 int count = _list.Count;
597 for (int i = 0; i < count; i++) {
598 DataTable table = (DataTable) _list[i];
599 result = NamesEqual(table.TableName, tableName, false, dataSet.Locale);
600 if ((result == 1) && (table.Namespace == tableNamespace))
603 if ((result == -1) && (table.Namespace == tableNamespace))
604 cachedI = (cachedI == -1) ? i : -2;
611 internal void FinishInitCollection() {
612 if (delayedAddRangeTables != null) {
613 foreach(DataTable table in delayedAddRangeTables) {
618 delayedAddRangeTables = null;
623 /// Makes a default name with the given index. e.g. Table1, Table2, ... Tablei
625 private string MakeName(int index) {
629 return "Table" + index.ToString(System.Globalization.CultureInfo.InvariantCulture);
634 /// Raises the <see cref='System.Data.DataTableCollection.OnCollectionChanged'/> event.
637 private void OnCollectionChanged(CollectionChangeEventArgs ccevent) {
638 if (onCollectionChangedDelegate != null) {
639 Bid.Trace("<ds.DataTableCollection.OnCollectionChanged|INFO> %d#\n", ObjectID);
640 onCollectionChangedDelegate(this, ccevent);
645 /// <para>[To be supplied.]</para>
647 private void OnCollectionChanging(CollectionChangeEventArgs ccevent) {
648 if (onCollectionChangingDelegate != null) {
649 Bid.Trace("<ds.DataTableCollection.OnCollectionChanging|INFO> %d#\n", ObjectID);
650 onCollectionChangingDelegate(this, ccevent);
655 /// Registers this name as being used in the collection. Will throw an ArgumentException
656 /// if the name is already being used. Called by Add, All property, and Table.TableName property.
657 /// if the name is equivalent to the next default name to hand out, we increment our defaultNameIndex.
659 internal void RegisterName(string name, string tbNamespace) {
660 Bid.Trace("<ds.DataTableCollection.RegisterName|INFO> %d#, name='%ls', tbNamespace='%ls'\n", ObjectID, name, tbNamespace);
661 Debug.Assert (name != null);
663 CultureInfo locale = dataSet.Locale;
664 int tableCount = _list.Count;
665 for (int i = 0; i < tableCount; i++) {
666 DataTable table = (DataTable) _list[i];
667 if (NamesEqual(name, table.TableName, true, locale) != 0 && (tbNamespace == table.Namespace)) {
668 throw ExceptionBuilder.DuplicateTableName(((DataTable) _list[i]).TableName);
671 if (NamesEqual(name, MakeName(defaultNameIndex), true, locale) != 0) {
678 /// Removes the specified table from the collection.
681 public void Remove(DataTable table) {
683 Bid.ScopeEnter(out hscp, "<ds.DataTableCollection.Remove|API> %d#, table=%d\n", ObjectID, (table != null) ? table.ObjectID : 0);
685 OnCollectionChanging(new CollectionChangeEventArgs(CollectionChangeAction.Remove, table));
687 OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Remove, table));
690 Bid.ScopeLeave(ref hscp);
697 /// table at the given index from the collection
700 public void RemoveAt(int index) {
702 Bid.ScopeEnter(out hscp, "<ds.DataTableCollection.RemoveAt|API> %d#, index=%d\n", ObjectID, index);
704 DataTable dt = this[index];
706 throw ExceptionBuilder.TableOutOfRange(index);
710 Bid.ScopeLeave(ref hscp);
716 /// Removes the table with a specified name from the
720 public void Remove(string name) {
722 Bid.ScopeEnter(out hscp, "<ds.DataTableCollection.Remove|API> %d#, name='%ls'\n", ObjectID, name);
724 DataTable dt = this[name];
726 throw ExceptionBuilder.TableNotInTheDataSet(name);
730 Bid.ScopeLeave(ref hscp);
734 public void Remove(string name, string tableNamespace) {
736 throw ExceptionBuilder.ArgumentNull("name");
737 if (tableNamespace == null)
738 throw ExceptionBuilder.ArgumentNull("tableNamespace");
739 DataTable dt = this[name, tableNamespace];
741 throw ExceptionBuilder.TableNotInTheDataSet(name);
747 /// Unregisters this name as no longer being used in the collection. Called by Remove, All property, and
748 /// Table.TableName property. If the name is equivalent to the last proposed default name, we walk backwards
749 /// to find the next proper default name to use.
751 internal void UnregisterName(string name) {
752 Bid.Trace("<ds.DataTableCollection.UnregisterName|INFO> %d#, name='%ls'\n", ObjectID, name);
753 if (NamesEqual(name, MakeName(defaultNameIndex - 1), true, dataSet.Locale) != 0) {
756 } while (defaultNameIndex > 1 &&
757 !Contains(MakeName(defaultNameIndex - 1)));