|
//------------------------------------------------------------------------------
// <copyright file="DataTableCollection.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="true" primary="false">Microsoft</owner>
// <owner current="false" primary="false">Microsoft</owner>
//------------------------------------------------------------------------------
namespace System.Data {
using System;
using System.Diagnostics;
using System.Collections;
using System.ComponentModel;
using System.Globalization;
/// <devdoc>
/// <para>
/// Represents the collection of tables for the <see cref='System.Data.DataSet'/>.
/// </para>
/// </devdoc>
[
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);
/// <devdoc>
/// DataTableCollection constructor. Used only by DataSet.
/// </devdoc>
internal DataTableCollection(DataSet dataSet) {
Bid.Trace("<ds.DataTableCollection.DataTableCollection|INFO> %d#, dataSet=%d\n", ObjectID, (dataSet != null) ? dataSet.ObjectID : 0);
this.dataSet = dataSet;
}
/// <devdoc>
/// <para>
/// Gets the tables
/// in the collection as an object.
/// </para>
/// </devdoc>
protected override ArrayList List {
get {
return _list;
}
}
internal int ObjectID {
get {
return _objectID;
}
}
/// <devdoc>
/// <para>Gets the table specified by its index.</para>
/// </devdoc>
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);
}
}
}
/// <devdoc>
/// <para>Gets the table in the collection with the given name (not case-sensitive).</para>
/// </devdoc>
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;
}
/// <devdoc>
/// <para>
/// Adds
/// the specified table to the collection.
/// </para>
/// </devdoc>
public void Add(DataTable table) {
IntPtr hscp;
Bid.ScopeEnter(out hscp, "<ds.DataTableCollection.Add|API> %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);
}
}
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public void AddRange(DataTable[] tables) {
IntPtr hscp;
Bid.ScopeEnter(out hscp, "<ds.DataTableCollection.AddRange|API> %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);
}
}
/// <devdoc>
/// <para>
/// Creates a table with the given name and adds it to the
/// collection.
/// </para>
/// </devdoc>
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;
}
/// <devdoc>
/// <para>
/// Creates a new table with a default name and adds it to
/// the collection.
/// </para>
/// </devdoc>
public DataTable Add() {
DataTable table = new DataTable();
// fxcop: new DataTable should inherit the CaseSensitive, Locale, Namespace from DataSet
Add(table);
return table;
}
/// <devdoc>
/// <para>
/// Occurs when the collection is changed.
/// </para>
/// </devdoc>
[ResDescriptionAttribute(Res.collectionChangedEventDescr)]
public event CollectionChangeEventHandler CollectionChanged {
add {
Bid.Trace("<ds.DataTableCollection.add_CollectionChanged|API> %d#\n", ObjectID);
onCollectionChangedDelegate += value;
}
remove {
Bid.Trace("<ds.DataTableCollection.remove_CollectionChanged|API> %d#\n", ObjectID);
onCollectionChangedDelegate -= value;
}
}
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public event CollectionChangeEventHandler CollectionChanging {
add {
Bid.Trace("<ds.DataTableCollection.add_CollectionChanging|API> %d#\n", ObjectID);
onCollectionChangingDelegate += value;
}
remove {
Bid.Trace("<ds.DataTableCollection.remove_CollectionChanging|API> %d#\n", ObjectID);
onCollectionChangingDelegate -= value;
}
}
/// <devdoc>
/// Adds the table to the tables array.
/// </devdoc>
private void ArrayAdd(DataTable table) {
_list.Add(table);
}
/// <devdoc>
/// Creates a new default name.
/// </devdoc>
internal string AssignName() {
string newName = null;
// RAIDBUG: 91671
while(this.Contains( newName = MakeName(defaultNameIndex)))
defaultNameIndex++;
return newName;
}
/// <devdoc>
/// 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).
/// </devdoc>
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);
foreach (Constraint constr in table.Constraints) {
constr.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.
}
/// <devdoc>
/// BaseGroupSwitch will intelligently remove and add tables from the collection.
/// </devdoc>
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]);
}
}
}
/// <devdoc>
/// 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.
/// </devdoc>
private void BaseRemove(DataTable table) {
if (CanRemove(table, true)) {
UnregisterName(table.TableName);
table.SetDataSet(null);
//Remove dataSet from constriants
foreach (Constraint constr in table.Constraints) {
constr.SetDataSet(null);
}
}
_list.Remove(table);
dataSet.OnRemovedTable(table);
}
/// <devdoc>
/// <para>
/// Verifies if a given table can be removed from the collection.
/// </para>
/// </devdoc>
public bool CanRemove(DataTable table) {
return CanRemove(table, false);
}
internal bool CanRemove(DataTable table, bool fThrowException) {
IntPtr hscp;
Bid.ScopeEnter(out hscp, "<ds.DataTableCollection.CanRemove|INFO> %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);
}
}
/// <devdoc>
/// <para>
/// Clears the collection of any tables.
/// </para>
/// </devdoc>
public void Clear() {
IntPtr hscp;
Bid.ScopeEnter(out hscp, "<ds.DataTableCollection.Clear|API> %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);
}
}
/// <devdoc>
/// <para>
/// Checks if a table, specified by name, exists in the collection.
/// </para>
/// </devdoc>
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];
}
}
/// <devdoc>
/// <para>
/// Returns the index of a specified <see cref='System.Data.DataTable'/>.
/// </para>
/// </devdoc>
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;
}
/// <devdoc>
/// <para>
/// Returns the index of the
/// table with the given name (case insensitive), or -1 if the table
/// doesn't exist in the collection.
/// </para>
/// </devdoc>
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<DataTable> 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<count;j++) {
DataTable dupTable = (DataTable) _list[j];
if (NamesEqual(dupTable.TableName, tableName, false, dataSet.Locale) == 1)
return -3;
}
//... let's just return i
return i;
}
if (result == -1)
cachedI = (cachedI == -1) ? i : -2;
}
}
return cachedI;
}
// Return value:
// >= 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;
}
}
/// <devdoc>
/// Makes a default name with the given index. e.g. Table1, Table2, ... Tablei
/// </devdoc>
private string MakeName(int index) {
if (1 == index) {
return "Table1";
}
return "Table" + index.ToString(System.Globalization.CultureInfo.InvariantCulture);
}
/// <devdoc>
/// <para>
/// Raises the <see cref='System.Data.DataTableCollection.OnCollectionChanged'/> event.
/// </para>
/// </devdoc>
private void OnCollectionChanged(CollectionChangeEventArgs ccevent) {
if (onCollectionChangedDelegate != null) {
Bid.Trace("<ds.DataTableCollection.OnCollectionChanged|INFO> %d#\n", ObjectID);
onCollectionChangedDelegate(this, ccevent);
}
}
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
private void OnCollectionChanging(CollectionChangeEventArgs ccevent) {
if (onCollectionChangingDelegate != null) {
Bid.Trace("<ds.DataTableCollection.OnCollectionChanging|INFO> %d#\n", ObjectID);
onCollectionChangingDelegate(this, ccevent);
}
}
/// <devdoc>
/// 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.
/// </devdoc>
internal void RegisterName(string name, string tbNamespace) {
Bid.Trace("<ds.DataTableCollection.RegisterName|INFO> %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++;
}
}
/// <devdoc>
/// <para>
/// Removes the specified table from the collection.
/// </para>
/// </devdoc>
public void Remove(DataTable table) {
IntPtr hscp;
Bid.ScopeEnter(out hscp, "<ds.DataTableCollection.Remove|API> %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);
}
}
/// <devdoc>
/// <para>
/// Removes the
/// table at the given index from the collection
/// </para>
/// </devdoc>
public void RemoveAt(int index) {
IntPtr hscp;
Bid.ScopeEnter(out hscp, "<ds.DataTableCollection.RemoveAt|API> %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);
}
}
/// <devdoc>
/// <para>
/// Removes the table with a specified name from the
/// collection.
/// </para>
/// </devdoc>
public void Remove(string name) {
IntPtr hscp;
Bid.ScopeEnter(out hscp, "<ds.DataTableCollection.Remove|API> %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);
}
/// <devdoc>
/// 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.
/// </devdoc>
internal void UnregisterName(string name) {
Bid.Trace("<ds.DataTableCollection.UnregisterName|INFO> %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)));
}
}
}
}
|