//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // // Microsoft // Microsoft // Microsoft //------------------------------------------------------------------------------ namespace System.Data { using System; using System.Xml; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Data.Common; using System.Diagnostics; /// /// Represents a collection of /// objects for a . /// [ DefaultEvent("CollectionChanged"), Editor("Microsoft.VSDesigner.Data.Design.ColumnsCollectionEditor, " + AssemblyRef.MicrosoftVSDesigner, "System.Drawing.Design.UITypeEditor, " + AssemblyRef.SystemDrawing), ] public sealed class DataColumnCollection : InternalDataCollectionBase { private readonly DataTable table; private readonly ArrayList _list = new ArrayList(); private int defaultNameIndex = 1; private DataColumn[] delayedAddRangeColumns; private readonly Dictionary columnFromName; // Links names to columns private CollectionChangeEventHandler onCollectionChangedDelegate; private CollectionChangeEventHandler onCollectionChangingDelegate; private CollectionChangeEventHandler onColumnPropertyChangedDelegate; private bool fInClear; private DataColumn[] columnsImplementingIChangeTracking = DataTable.zeroColumns; private int nColumnsImplementingIChangeTracking = 0; private int nColumnsImplementingIRevertibleChangeTracking = 0; /// /// DataColumnCollection constructor. Used only by DataTable. /// internal DataColumnCollection(DataTable table) { this.table = table; columnFromName = new Dictionary(); } /// /// Gets the list of the collection items. /// protected override ArrayList List { get { return _list; } } internal DataColumn[] ColumnsImplementingIChangeTracking { get { return columnsImplementingIChangeTracking; } } internal int ColumnsImplementingIChangeTrackingCount{ get { return nColumnsImplementingIChangeTracking; } } internal int ColumnsImplementingIRevertibleChangeTrackingCount { get { return nColumnsImplementingIRevertibleChangeTracking; } } /// /// /// Gets the /// from the collection at the specified index. /// /// public DataColumn this[int index] { get { try { // Perf: use the readonly _list field directly and let ArrayList check the range return (DataColumn)_list[index]; } catch(ArgumentOutOfRangeException) { throw ExceptionBuilder.ColumnOutOfRange(index); } } } /// /// Gets the from the collection with the specified name. /// public DataColumn this[string name] { get { if (null == name) { throw ExceptionBuilder.ArgumentNull("name"); } DataColumn column; if ((!columnFromName.TryGetValue(name, out column)) || (column == null)) { // Case-Insensitive compares int index = IndexOfCaseInsensitive(name); if (0 <= index) { column = (DataColumn)_list[index]; } else if (-2 == index) { throw ExceptionBuilder.CaseInsensitiveNameConflict(name); } } return column; } } internal DataColumn this[string name, string ns] { get { DataColumn column; if ((columnFromName.TryGetValue(name, out column)) && (column != null) && (column.Namespace == ns)) { return column; } return null; } } internal void EnsureAdditionalCapacity(int capacity) { if (_list.Capacity < capacity + _list.Count) { _list.Capacity = capacity + _list.Count; } } /// /// Adds the specified /// to the columns collection. /// public void Add(DataColumn column) { AddAt(-1, column); } internal void AddAt(int index, DataColumn column) { if (column != null && column.ColumnMapping == MappingType.SimpleContent) { if (table.XmlText != null && table.XmlText != column) throw ExceptionBuilder.CannotAddColumn3(); if (table.ElementColumnCount > 0) throw ExceptionBuilder.CannotAddColumn4(column.ColumnName); OnCollectionChanging(new CollectionChangeEventArgs(CollectionChangeAction.Add, column)); BaseAdd(column); if (index != -1) ArrayAdd(index, column); else ArrayAdd(column); table.XmlText = column; } else { OnCollectionChanging(new CollectionChangeEventArgs(CollectionChangeAction.Add, column)); BaseAdd(column); if (index != -1) ArrayAdd(index, column); else ArrayAdd(column); // if the column is an element increase the internal dataTable counter if (column.ColumnMapping == MappingType.Element) table.ElementColumnCount ++; } if (!table.fInitInProgress && column != null && column.Computed) { column.Expression = column.Expression; } OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Add, column)); } /// /// [To be supplied.] /// public void AddRange(DataColumn[] columns) { if (table.fInitInProgress) { delayedAddRangeColumns = columns; return; } if (columns != null) { foreach(DataColumn column in columns) { if (column != null) { Add(column); } } } } /// /// Creates and adds a /// with /// the specified name, type, and compute expression to the columns collection. /// public DataColumn Add(string columnName, Type type, string expression) { DataColumn column = new DataColumn(columnName, type, expression); Add(column); return column; } /// /// Creates and adds a /// with the /// specified name and type to the columns collection. /// public DataColumn Add(string columnName, Type type) { DataColumn column = new DataColumn(columnName, type); Add(column); return column; } /// /// Creates and adds a /// with the specified name to the columns collection. /// public DataColumn Add(string columnName) { DataColumn column = new DataColumn(columnName); Add(column); return column; } /// /// Creates and adds a to a columns collection. /// public DataColumn Add() { DataColumn column = new DataColumn(); Add(column); return column; } /// /// Occurs when the columns collection changes, either by adding or removing a column. /// [ResDescriptionAttribute(Res.collectionChangedEventDescr)] public event CollectionChangeEventHandler CollectionChanged { add { onCollectionChangedDelegate += value; } remove { onCollectionChangedDelegate -= value; } } internal event CollectionChangeEventHandler CollectionChanging { add { onCollectionChangingDelegate += value; } remove { onCollectionChangingDelegate -= value; } } internal event CollectionChangeEventHandler ColumnPropertyChanged { add { onColumnPropertyChangedDelegate += value; } remove { onColumnPropertyChangedDelegate -= value; } } /// /// Adds the column to the columns array. /// private void ArrayAdd(DataColumn column) { _list.Add(column); column.SetOrdinalInternal(_list.Count - 1); CheckIChangeTracking(column); } private void ArrayAdd(int index, DataColumn column) { _list.Insert(index, column); CheckIChangeTracking(column); } private void ArrayRemove(DataColumn column) { column.SetOrdinalInternal(-1); _list.Remove(column); int count = _list.Count; for (int i =0; i < count; i++) { ((DataColumn) _list[i]).SetOrdinalInternal(i); } if (column.ImplementsIChangeTracking) { RemoveColumnsImplementingIChangeTrackingList(column); } } /// /// Creates a new default name. /// internal string AssignName() { string newName = MakeName(defaultNameIndex++); while (columnFromName.ContainsKey(newName)) { newName = MakeName(defaultNameIndex++); } return newName; } /// /// Does verification on the column and it's name, and points the column at the dataSet that owns this collection. /// An ArgumentNullException is thrown if this column is null. An ArgumentException is thrown if this column /// already belongs to this collection, belongs to another collection. /// A DuplicateNameException is thrown if this collection already has a column with the same /// name (case insensitive). /// private void BaseAdd(DataColumn column) { if (column == null) throw ExceptionBuilder.ArgumentNull("column"); if (column.table == table) throw ExceptionBuilder.CannotAddColumn1(column.ColumnName); if (column.table != null) throw ExceptionBuilder.CannotAddColumn2(column.ColumnName); if (column.ColumnName.Length == 0) { column.ColumnName = AssignName(); } RegisterColumnName(column.ColumnName, column); try { column.SetTable(table); if (!table.fInitInProgress && column.Computed) { if (column.DataExpression.DependsOn(column)) { throw ExceptionBuilder.ExpressionCircular(); } } if (0 < table.RecordCapacity) { // adding a column to table with existing rows column.SetCapacity(table.RecordCapacity); } // fill column with default value. for (int record = 0; record < table.RecordCapacity; record++) { column.InitializeRecord(record); } if (table.DataSet != null) { column.OnSetDataSet(); } } catch (Exception e) { // if (ADP.IsCatchableOrSecurityExceptionType(e)) { UnregisterName(column.ColumnName); } throw; } } /// /// BaseGroupSwitch will intelligently remove and add tables from the collection. /// private void BaseGroupSwitch(DataColumn[] oldArray, int oldLength, DataColumn[] 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].Table == table) { BaseRemove(oldArray[oldCur]); _list.Remove(oldArray[oldCur]); oldArray[oldCur].SetOrdinalInternal(-1); } } } // Now, let's pass through news and those that don't belong, add them. for (int newCur = 0; newCur < newLength; newCur++) { if (newArray[newCur].Table != table) { BaseAdd(newArray[newCur]); _list.Add(newArray[newCur]); } newArray[newCur].SetOrdinalInternal(newCur); } } /// /// Does verification on the column and it's name, and clears the column's dataSet pointer. /// An ArgumentNullException is thrown if this column is null. An ArgumentException is thrown /// if this column doesn't belong to this collection or if this column is part of a relationship. /// An ArgumentException is thrown if another column's compute expression depends on this column. /// private void BaseRemove(DataColumn column) { if (CanRemove(column, true)) { // remove if (column.errors > 0) { for (int i = 0; i < table.Rows.Count; i++) { table.Rows[i].ClearError(column); } } UnregisterName(column.ColumnName); column.SetTable(null); } } /// /// Checks /// if /// a given column can be removed from the collection. /// public bool CanRemove(DataColumn column) { return CanRemove(column, false); } internal bool CanRemove(DataColumn column, bool fThrowException) { if (column == null) { if (!fThrowException) return false; else throw ExceptionBuilder.ArgumentNull("column"); } if (column.table != table) { if (!fThrowException) return false; else throw ExceptionBuilder.CannotRemoveColumn(); } // allow subclasses to complain first. table.OnRemoveColumnInternal(column); // We need to make sure the column is not involved in any Relations or Constriants if (table.primaryKey != null && table.primaryKey.Key.ContainsColumn(column)) { if (!fThrowException) return false; else throw ExceptionBuilder.CannotRemovePrimaryKey(); } for (int i = 0; i < table.ParentRelations.Count; i++) { if (table.ParentRelations[i].ChildKey.ContainsColumn(column)) { if (!fThrowException) return false; else throw ExceptionBuilder.CannotRemoveChildKey(table.ParentRelations[i].RelationName); } } for (int i = 0; i < table.ChildRelations.Count; i++) { if (table.ChildRelations[i].ParentKey.ContainsColumn(column)) { if (!fThrowException) return false; else throw ExceptionBuilder.CannotRemoveChildKey(table.ChildRelations[i].RelationName); } } for (int i = 0; i < table.Constraints.Count; i++) { if (table.Constraints[i].ContainsColumn(column)) if (!fThrowException) return false; else throw ExceptionBuilder.CannotRemoveConstraint(table.Constraints[i].ConstraintName, table.Constraints[i].Table.TableName); } if (table.DataSet != null) { for (ParentForeignKeyConstraintEnumerator en = new ParentForeignKeyConstraintEnumerator(table.DataSet, table); en.GetNext();) { Constraint constraint = en.GetConstraint(); if (((ForeignKeyConstraint)constraint).ParentKey.ContainsColumn(column)) if (!fThrowException) return false; else throw ExceptionBuilder.CannotRemoveConstraint(constraint.ConstraintName, constraint.Table.TableName); } } if (column.dependentColumns != null) { for (int i = 0; i < column.dependentColumns.Count; i++) { DataColumn col = column.dependentColumns[i]; if (fInClear && (col.Table == table || col.Table == null)) continue; if (col.Table == null) continue; Debug.Assert(col.Computed, "invalid (non an expression) column in the expression dependent columns"); DataExpression expr = col.DataExpression; if ((expr!= null) && (expr.DependsOn(column))) { if (!fThrowException) return false; else throw ExceptionBuilder.CannotRemoveExpression(col.ColumnName, col.Expression); } } } // SQLBU 429176: you can't remove a column participating in an index, // while index events are suspended else the indexes won't be properly maintained. // However, all the above checks should catch those participating columns. // except when a column is in a DataView RowFilter or Sort clause foreach (Index index in table.LiveIndexes) { #if false if (!Object.ReferenceEquals(index, column.sortIndex)) { foreach (IndexField field in index.IndexFields) { if (Object.ReferenceEquals(field.Column, column)) { if (fThrowException) { throw ExceptionBuilder.CannotRemoveExpression("DataView", column.ColumnName); } return false; } } } #endif } return true; } private void CheckIChangeTracking(DataColumn column) { if (column.ImplementsIRevertibleChangeTracking) { nColumnsImplementingIRevertibleChangeTracking++; nColumnsImplementingIChangeTracking++; AddColumnsImplementingIChangeTrackingList(column); } else if (column.ImplementsIChangeTracking) { nColumnsImplementingIChangeTracking++; AddColumnsImplementingIChangeTrackingList(column); } } /// /// /// Clears the collection of any columns. /// /// public void Clear() { int oldLength = _list.Count; DataColumn[] columns = new DataColumn[_list.Count]; _list.CopyTo(columns, 0); OnCollectionChanging(RefreshEventArgs); if (table.fInitInProgress && delayedAddRangeColumns != null) { delayedAddRangeColumns = null; } try { // this will smartly add and remove the appropriate tables. fInClear = true; BaseGroupSwitch(columns, oldLength, null, 0); fInClear = false; } catch (Exception e) { // if (ADP.IsCatchableOrSecurityExceptionType(e)) { // something messed up: restore to old values and throw fInClear = false; BaseGroupSwitch(null, 0, columns, oldLength); _list.Clear(); for (int i = 0; i < oldLength; i++) _list.Add(columns[i]); } throw; } _list.Clear(); table.ElementColumnCount = 0; OnCollectionChanged(RefreshEventArgs); } /// /// Checks whether the collection contains a column with the specified name. /// public bool Contains(string name) { DataColumn column; if ((columnFromName.TryGetValue(name, out column)) && (column != null)) { return true; } return (IndexOfCaseInsensitive(name) >= 0); } internal bool Contains(string name, bool caseSensitive) { DataColumn column; if ((columnFromName.TryGetValue(name, out column)) && (column != null)) { return true; } if (caseSensitive) { // above check did case sensitive check return false; } else { return (IndexOfCaseInsensitive(name) >= 0); } } public void CopyTo(DataColumn[] 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] = (DataColumn)_list[i]; } } /// /// /// Returns the index of a specified . /// /// public int IndexOf(DataColumn column) { int columnCount = _list.Count; for (int i = 0; i < columnCount; ++i) { if (column == (DataColumn) _list[i]) { return i; } } return -1; } /// /// Returns the index of /// a column specified by name. /// public int IndexOf(string columnName) { if ((null != columnName) && (0 < columnName.Length)) { int count = Count; DataColumn column; if ((columnFromName.TryGetValue(columnName, out column)) && (column != null)) { for (int j = 0; j < count; j++) if (column == _list[j]) { return j; } } else { int res = IndexOfCaseInsensitive(columnName); return (res < 0) ? -1 : res; } } return -1; } internal int IndexOfCaseInsensitive (string name) { int hashcode = table.GetSpecialHashCode(name); int cachedI = -1; DataColumn column = null; for (int i = 0; i < Count; i++) { column = (DataColumn) _list[i]; if ( (hashcode == 0 || column._hashCode == 0 || column._hashCode == hashcode) && NamesEqual(column.ColumnName, name, false, table.Locale) != 0 ) { if (cachedI == -1) cachedI = i; else return -2; } } return cachedI; } internal void FinishInitCollection() { if (delayedAddRangeColumns != null) { foreach(DataColumn column in delayedAddRangeColumns) { if (column != null) { Add(column); } } foreach(DataColumn column in delayedAddRangeColumns) { if (column != null) { column.FinishInitInProgress(); } } delayedAddRangeColumns = null; } } /// /// Makes a default name with the given index. e.g. Column1, Column2, ... Columni /// private string MakeName(int index) { if (1 == index) { return "Column1"; } return "Column" + index.ToString(System.Globalization.CultureInfo.InvariantCulture); } internal void MoveTo(DataColumn column, int newPosition) { if (0 > newPosition || newPosition > Count -1) { throw ExceptionBuilder.InvalidOrdinal("ordinal", newPosition); } if (column.ImplementsIChangeTracking) { RemoveColumnsImplementingIChangeTrackingList(column); } _list.Remove(column); _list.Insert(newPosition, column); int count = _list.Count; for (int i =0; i < count; i++) { ((DataColumn) _list[i]).SetOrdinalInternal(i); } CheckIChangeTracking(column); OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Refresh, column)); } /// /// /// Raises the event. /// /// private void OnCollectionChanged(CollectionChangeEventArgs ccevent) { table.UpdatePropertyDescriptorCollectionCache(); if ((null != ccevent) && !table.SchemaLoading && !table.fInitInProgress) { DataColumn column = (DataColumn)ccevent.Element; } if (onCollectionChangedDelegate != null) { onCollectionChangedDelegate(this, ccevent); } } /// /// [To be supplied.] /// private void OnCollectionChanging(CollectionChangeEventArgs ccevent) { if (onCollectionChangingDelegate != null) { onCollectionChangingDelegate(this, ccevent); } } internal void OnColumnPropertyChanged(CollectionChangeEventArgs ccevent) { table.UpdatePropertyDescriptorCollectionCache(); if (onColumnPropertyChangedDelegate != null) { onColumnPropertyChangedDelegate(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 Column.ColumnName property. /// if the name is equivalent to the next default name to hand out, we increment our defaultNameIndex. /// NOTE: To add a child table, pass column as null /// internal void RegisterColumnName(string name, DataColumn column) { Debug.Assert (name != null); try { columnFromName.Add(name, column); if (null != column) { column._hashCode = table.GetSpecialHashCode(name); } } catch (ArgumentException) { // Argument exception means that there is already an existing key if (columnFromName[name] != null) { if (column != null) { throw ExceptionBuilder.CannotAddDuplicate(name); } else { throw ExceptionBuilder.CannotAddDuplicate3(name); } } throw ExceptionBuilder.CannotAddDuplicate2(name); } // If we're adding a child table, then update defaultNameIndex to avoid colisions between the child table and auto-generated column names if ((column == null) && NamesEqual(name, MakeName(defaultNameIndex), true, table.Locale) != 0) { do { defaultNameIndex++; } while (Contains(MakeName(defaultNameIndex))); } } internal bool CanRegisterName(string name) { Debug.Assert (name != null, "Must specify a name"); return (!columnFromName.ContainsKey(name)); } /// /// Removes the specified /// from the collection. /// public void Remove(DataColumn column) { OnCollectionChanging(new CollectionChangeEventArgs(CollectionChangeAction.Remove, column)); BaseRemove(column); ArrayRemove(column); OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Remove, column)); // if the column is an element decrease the internal dataTable counter if (column.ColumnMapping == MappingType.Element) table.ElementColumnCount --; } /// /// Removes the /// column at the specified index from the collection. /// public void RemoveAt(int index) { DataColumn dc = this[index]; if (dc == null) throw ExceptionBuilder.ColumnOutOfRange(index); Remove(dc); } /// /// Removes the /// column with the specified name from the collection. /// public void Remove(string name) { DataColumn dc = this[name]; if (dc == null) throw ExceptionBuilder.ColumnNotInTheTable(name, table.TableName); Remove(dc); } /// /// Unregisters this name as no longer being used in the collection. Called by Remove, All property, and /// Column.ColumnName 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) { columnFromName.Remove(name); if (NamesEqual(name, MakeName(defaultNameIndex - 1), true, table.Locale) != 0) { do { defaultNameIndex--; } while (defaultNameIndex > 1 && !Contains(MakeName(defaultNameIndex - 1))); } } private void AddColumnsImplementingIChangeTrackingList(DataColumn dataColumn) { DataColumn[] columns = columnsImplementingIChangeTracking; DataColumn[] tempColumns = new DataColumn[columns.Length +1]; columns.CopyTo(tempColumns, 0); tempColumns[columns.Length] = dataColumn; columnsImplementingIChangeTracking = tempColumns; } private void RemoveColumnsImplementingIChangeTrackingList(DataColumn dataColumn) { DataColumn[] columns = columnsImplementingIChangeTracking; DataColumn[] tempColumns = new DataColumn[columns.Length - 1]; for(int i = 0, j = 0; i < columns.Length; i++) { if (columns[i] != dataColumn) { tempColumns[j++] = columns[i]; } } columnsImplementingIChangeTracking = tempColumns; } } }