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)));