// Author:
// Christopher Podurgiel (cpodurgiel@msn.com)
// Stuart Caborn <stuart.caborn@virgin.net>
+// Tim Coleman (tim@timcoleman.com)
//
// (C) Chris Podurgiel
+// Copyright (C) Tim Coleman, 2002
+// Copyright (C) Daniel Morgan, 2003
+//
+
+//
+// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections;
using System.ComponentModel;
-namespace System.Data
-{
- /// <summary>
- /// Represents a collection of DataColumn objects for a DataTable.
- /// </summary>
+namespace System.Data {
+ [Editor]
[Serializable]
+ [DefaultEvent ("CollectionChanged")]
public class DataColumnCollection : InternalDataCollectionBase
{
- // The defaultNameIndex is used to create a default name for a column if one wasn't given.
- private int defaultNameIndex;
-
+ //This hashtable maps between column name to DataColumn object.
+ private Hashtable columnFromName = new Hashtable();
+ //This ArrayList contains the auto-increment columns names
+ private ArrayList autoIncrement = new ArrayList();
+ //This holds the next index to use for default column name.
+ private int defaultColumnIndex = 1;
//table should be the DataTable this DataColumnCollection belongs to.
private DataTable parentTable = null;
+ // Keep reference to most recent columns passed to AddRange()
+ // so that they can be added when EndInit() is called.
+ DataColumn[] _mostRecentColumns = null;
// Internal Constructor. This Class can only be created from other classes in this assembly.
internal DataColumnCollection(DataTable table):base()
{
- defaultNameIndex = 1;
parentTable = table;
}
{
get
{
+ if (index < 0 || index > base.List.Count) {
+ throw new IndexOutOfRangeException("Cannot find column " + index + ".");
+ }
return (DataColumn) base.List[index];
}
}
{
get
{
- foreach (DataColumn column in base.List)
- {
- if (column.ColumnName == name)
- {
- return column;
- }
- }
- return null;
+ DataColumn dc = columnFromName[name] as DataColumn;
+
+ if (dc != null)
+ return dc;
+
+ int tmp = IndexOf(name, true);
+ if (tmp == -1)
+ return null;
+ return this[tmp];
}
}
/// <summary>
/// Gets a list of the DataColumnCollection items.
/// </summary>
- protected internal override ArrayList List
+ protected override ArrayList List
{
get
{
}
}
+ internal ArrayList AutoIncrmentColumns
+ {
+ get
+ {
+ return autoIncrement;
+ }
+ }
+
//Add Logic
//
//Changing Event
/// <returns></returns>
public virtual DataColumn Add()
{
- //FIXME:
- DataColumn column = new DataColumn("Column" + defaultNameIndex.ToString());
- CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Add, this);
-
- column.SetTable(parentTable);
- base.List.Add(column);
- OnCollectionChanged(e);
- defaultNameIndex++;
+ string defaultName = GetNextDefaultColumnName ();
+ DataColumn column = new DataColumn (defaultName);
+ Add (column);
return column;
}
+ internal void RegisterName(string name, DataColumn column)
+ {
+ if (columnFromName.Contains(name))
+ throw new DuplicateNameException("A DataColumn named '" + name + "' already belongs to this DataTable.");
+
+ columnFromName[name] = column;
+
+ if (name.StartsWith("Column") && name == MakeName(defaultColumnIndex + 1))
+ {
+ do
+ {
+ defaultColumnIndex++;
+ }
+ while (Contains(MakeName(defaultColumnIndex + 1)));
+ }
+ }
+
+ internal void UnregisterName(string name)
+ {
+ if (columnFromName.Contains(name))
+ columnFromName.Remove(name);
+
+ if (name.StartsWith("Column") && name == MakeName(defaultColumnIndex - 1))
+ {
+ do
+ {
+ defaultColumnIndex--;
+ }
+ while (!Contains(MakeName(defaultColumnIndex - 1)) && defaultColumnIndex > 1);
+ }
+ }
+
+ private string GetNextDefaultColumnName ()
+ {
+ string defColumnName = MakeName(defaultColumnIndex);
+ for (int index = defaultColumnIndex + 1; Contains(defColumnName); ++index) {
+ defColumnName = MakeName(index);
+ defaultColumnIndex++;
+ }
+ defaultColumnIndex++;
+ return defColumnName;
+ }
+
+ static readonly string[] TenColumns = { "Column0", "Column1", "Column2", "Column3", "Column4", "Column5", "Column6", "Column7", "Column8", "Column9" };
+
+ private string MakeName(int index)
+ {
+ if (index < 10)
+ return TenColumns[index];
+
+ return String.Concat("Column", index.ToString());
+ }
+
/// <summary>
/// Creates and adds the specified DataColumn object to the DataColumnCollection.
/// </summary>
/// <param name="column">The DataColumn to add.</param>
- [MonoTODO]
public void Add(DataColumn column)
- {
- //FIXME:
- if(Contains(column.ColumnName))
+ {
+
+ if (column == null)
+ throw new ArgumentNullException ("column", "'column' argument cannot be null.");
+
+ if (column.ColumnName.Equals(String.Empty))
{
- throw new DuplicateNameException("A column named " + column.ColumnName + " already belongs to this DataTable.");
+ column.ColumnName = GetNextDefaultColumnName ();
}
- else
- {
- CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Add, this);
-
- column.SetTable( parentTable);
- base.List.Add(column);
-
- //add constraints if neccesary
- if(column.Unique)
- {
- UniqueConstraint uc = new UniqueConstraint(column);
- parentTable.Constraints.Add(uc);
- }
-
- //TODO: add missing constraints. i.e. Primary/Foreign keys
+// if (Contains(column.ColumnName))
+// throw new DuplicateNameException("A DataColumn named '" + column.ColumnName + "' already belongs to this DataTable.");
- OnCollectionChanged(e);
- return;
+ if (column.Table != null)
+ throw new ArgumentException ("Column '" + column.ColumnName + "' already belongs to this or another DataTable.");
+
+ CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Add, this);
+
+ column.SetTable (parentTable);
+ RegisterName(column.ColumnName, column);
+ int ordinal = base.List.Add(column);
+ column.SetOrdinal (ordinal);
+
+ // if table already has rows we need to allocate space
+ // in the column data container
+ if ( parentTable.Rows.Count > 0 ) {
+ column.DataContainer.Capacity = parentTable.RecordCache.CurrentCapacity;
+ }
+
+ if (column.AutoIncrement) {
+ DataRowCollection rows = column.Table.Rows;
+ for (int i = 0; i < rows.Count; i++)
+ rows [i] [ordinal] = column.AutoIncrementValue ();
}
+
+ if (column.AutoIncrement)
+ autoIncrement.Add(column);
+
+ OnCollectionChanged (e);
}
/// <summary>
/// <returns>The newly created DataColumn.</returns>
public virtual DataColumn Add(string columnName)
{
-
- //FIXME: this wont work. If the user decides to add a column named
- //"ColumnXX" where XX is a number these two will conflict.
if (columnName == null || columnName == String.Empty)
{
- columnName = "Column" + defaultNameIndex.ToString();
- defaultNameIndex++;
+ columnName = GetNextDefaultColumnName();
}
- if(Contains(columnName))
- {
- throw new DuplicateNameException("A column named " + columnName + " already belongs to this DataTable.");
- }
- else
- {
- DataColumn column = new DataColumn(columnName);
-
- CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Add, this);
- column.SetTable(parentTable);
- int ordinal = base.List.Add(column);
- column.SetOrdinal( ordinal );
- OnCollectionChanged(e);
- return column;
- }
+ DataColumn column = new DataColumn(columnName);
+ Add (column);
+ return column;
}
/// <summary>
{
if (columnName == null || columnName == "")
{
- //FIXME: this wont work. If the user decides to add a column named
- //"ColumnXX" where XX is a number these two will conflict.
- columnName = "Column" + defaultNameIndex.ToString();
- defaultNameIndex++;
- }
-
- if(Contains(columnName))
- {
- throw new DuplicateNameException("A column named " + columnName + " already belongs to this DataTable.");
- }
- else
- {
- DataColumn column = new DataColumn(columnName, type);
- CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Add, this);
- column.SetTable(parentTable);
- int ordinal = base.List.Add(column);
- column.SetOrdinal( ordinal );
- OnCollectionChanged(e);
- return column;
+ columnName = GetNextDefaultColumnName ();
}
+
+ DataColumn column = new DataColumn(columnName, type);
+ Add (column);
+ return column;
}
/// <summary>
/// <param name="type">The DataType of the new column.</param>
/// <param name="expression">The expression to assign to the Expression property.</param>
/// <returns>The newly created DataColumn.</returns>
- public virtual DataColumn Add(string columnName, Type type, string expression)
+ public virtual DataColumn Add(string columnName, Type type, string expression)
{
- //FIXME: See Add Logic
if (columnName == null || columnName == "")
{
- columnName = "Column" + defaultNameIndex.ToString();
- defaultNameIndex++;
+ columnName = GetNextDefaultColumnName ();
}
- if(Contains(columnName))
- {
- throw new DuplicateNameException("A column named " + columnName + " already belongs to this DataTable.");
- }
- else
- {
- DataColumn column = new DataColumn(columnName, type, expression);
- CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Add, this);
- column.SetTable(parentTable);
- int ordinal = base.List.Add(column);
- column.SetOrdinal( ordinal );
- OnCollectionChanged(e);
- return column;
- }
+ DataColumn column = new DataColumn(columnName, type, expression);
+ Add (column);
+ return column;
}
/// <summary>
/// <param name="columns">The array of DataColumn objects to add to the collection.</param>
public void AddRange(DataColumn[] columns)
{
+ if (parentTable.fInitInProgress){
+ _mostRecentColumns = columns;
+ return;
+ }
+
+ if (columns == null)
+ return;
+
foreach (DataColumn column in columns)
{
Add(column);
}
- return;
}
/// <summary>
{
//Check that the column does not have a null reference.
- if (column == null)
+ if (column == null)
{
return false;
}
//Check that the column is part of this collection.
- if (!Contains(column.ColumnName))
+ if (parentTable != column.Table)
{
return false;
}
+ parentTable.OnRemoveColumn(column);
+ UniqueConstraint primaryKey = parentTable.PrimaryKeyConstraint;
+ if (primaryKey != null && primaryKey.IsColumnContained(column))
+ return false;
//Check if this column is part of a relationship. (this could probably be written better)
foreach (DataRelation childRelation in parentTable.ChildRelations)
{
- foreach (DataColumn childColumn in childRelation.ChildColumns)
+ foreach (DataColumn childColumn in childRelation.ChildColumns)
{
- if (childColumn == column)
+ if (childColumn == column)
{
return false;
}
}
- foreach (DataColumn parentColumn in childRelation.ParentColumns)
+ foreach (DataColumn parentColumn in childRelation.ParentColumns)
{
- if (parentColumn == column)
+ if (parentColumn == column)
{
return false;
}
}
//Check if this column is part of a relationship. (this could probably be written better)
- foreach (DataRelation parentRelation in parentTable.ParentRelations)
+ foreach (DataRelation parentRelation in parentTable.ParentRelations)
{
- foreach (DataColumn childColumn in parentRelation.ChildColumns)
+ foreach (DataColumn childColumn in parentRelation.ChildColumns)
{
- if (childColumn == column)
+ if (childColumn == column)
{
return false;
}
}
- foreach (DataColumn parentColumn in parentRelation.ParentColumns)
+ foreach (DataColumn parentColumn in parentRelation.ParentColumns)
{
- if (parentColumn == column)
+ if (parentColumn == column)
{
return false;
}
}
}
+ //TODO: check constraints
+ for (int i = 0; i < parentTable.Constraints.Count; i++) {
+ if (parentTable.Constraints[i].IsColumnContained(column))
+ return false;
+ }
+
+ if (parentTable.DataSet != null) {
+ //FIXME: check whether some parent key in ForeignConstriants contains
+ // the column
+ }
+
//Check if another column's expression depends on this column.
- foreach (DataColumn dataColumn in List)
+ foreach (DataColumn dataColumn in List)
{
- if (dataColumn.Expression.ToString().IndexOf(column.ColumnName) > 0)
+ if (dataColumn.CompiledExpression != null &&
+ dataColumn.CompiledExpression.DependsOn(column))
{
return false;
}
}
-
- //TODO: check constraints
-
+ // check for part of pk
+ UniqueConstraint uc = UniqueConstraint.GetPrimaryKeyConstraint (parentTable.Constraints);
+ if (uc != null && uc.IsColumnContained(column)) {
+ return false;
+ }
+ // check for part of fk
+ DataSet ds = parentTable.DataSet;
+ if (ds != null) {
+ foreach (DataTable t in ds.Tables) {
+ if (t == parentTable)
+ continue;
+ foreach (Constraint c in t.Constraints) {
+ if (! (c is ForeignKeyConstraint))
+ continue;
+ ForeignKeyConstraint fk = (ForeignKeyConstraint) c;
+ if (fk.IsColumnContained(column))
+ return false;
+ }
+ }
+ }
return true;
}
public void Clear()
{
CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Refresh, this);
+
+ // FIXME: Hmm... This loop could look little nicer :)
+ foreach (DataColumn Col in List) {
+ foreach (DataRelation Rel in Col.Table.ParentRelations) {
+ foreach (DataColumn Col2 in Rel.ParentColumns) {
+ if (Object.ReferenceEquals (Col, Col2))
+ throw new ArgumentException ("Cannot remove this column, because " +
+ "it is part of the parent key for relationship " +
+ Rel.RelationName + ".");
+ }
+ foreach (DataColumn Col2 in Rel.ChildColumns) {
+ if (Object.ReferenceEquals (Col, Col2))
+ throw new ArgumentException ("Cannot remove this column, because " +
+ "it is part of the parent key for relationship " +
+ Rel.RelationName + ".");
+
+ }
+ }
+
+ foreach (DataRelation Rel in Col.Table.ChildRelations) {
+ foreach (DataColumn Col2 in Rel.ParentColumns) {
+ if (Object.ReferenceEquals (Col, Col2))
+ throw new ArgumentException ("Cannot remove this column, because " +
+ "it is part of the parent key for relationship " +
+ Rel.RelationName + ".");
+ }
+ foreach (DataColumn Col2 in Rel.ChildColumns) {
+ if (Object.ReferenceEquals (Col, Col2))
+ throw new ArgumentException ("Cannot remove this column, because " +
+ "it is part of the parent key for relationship " +
+ Rel.RelationName + ".");
+ }
+ }
+ }
+
+ // whether all columns can be removed
+ foreach (DataColumn col in this) {
+ if (!CanRemove (col))
+ throw new ArgumentException ("Cannot remove column {0}", col.ColumnName);
+ }
+
+ columnFromName.Clear();
+ autoIncrement.Clear();
base.List.Clear();
OnCollectionChanged(e);
- return;
+
}
/// <summary>
/// <returns>true if a column exists with this name; otherwise, false.</returns>
public bool Contains(string name)
{
- return (IndexOf(name) != -1);
+ if (columnFromName.Contains(name))
+ return true;
+
+ return (IndexOf(name, false) != -1);
}
/// <summary>
/// <returns>The zero-based index of the column with the specified name, or -1 if the column doesn't exist in the collection.</returns>
public int IndexOf(string columnName)
{
-
- DataColumn column = this[columnName];
-
- if (column != null)
- {
- return IndexOf(column);
- }
- else
- {
- return -1;
- }
+ DataColumn dc = columnFromName[columnName] as DataColumn;
+
+ if (dc != null)
+ return IndexOf(dc);
+
+ return IndexOf(columnName, false);
}
/// <summary>
/// <param name="ccevent">A CollectionChangeEventArgs that contains the event data.</param>
protected virtual void OnCollectionChanged(CollectionChangeEventArgs ccevent)
{
+ parentTable.ResetPropertyDescriptorsCache();
if (CollectionChanged != null)
{
CollectionChanged(this, ccevent);
/// <param name="column">The DataColumn to remove.</param>
public void Remove(DataColumn column)
{
+ if (column == null)
+ throw new ArgumentNullException ("column", "'column' argument cannot be null.");
+
+ if (!Contains(column.ColumnName))
+ throw new ArgumentException ("Cannot remove a column that doesn't belong to this table.");
//TODO: can remove first with exceptions
//and OnChanging Event
CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Remove, this);
int ordinal = column.Ordinal;
+ UnregisterName(column.ColumnName);
base.List.Remove(column);
//Update the ordinals
{
this[i].SetOrdinal( i );
}
-
+
+ if (parentTable != null)
+ parentTable.OnRemoveColumn(column);
+
+ if (column.AutoIncrement)
+ autoIncrement.Remove(column);
+
OnCollectionChanged(e);
return;
}
/// <param name="name">The name of the column to remove.</param>
public void Remove(string name)
{
- DataColumn column = this[name];
- Remove( column );
+ DataColumn column = this[name];
+
+ if (column == null)
+ throw new ArgumentException ("Column '" + name + "' does not belong to table " + ( parentTable == null ? "" : parentTable.TableName ) + ".");
+ Remove(column);
}
/// <summary>
/// <param name="index">The index of the column to remove.</param>
public void RemoveAt(int index)
{
+ if (Count <= index)
+ throw new IndexOutOfRangeException ("Cannot find column " + index + ".");
+
DataColumn column = this[index];
- Remove( column );
+ Remove(column);
+ }
+
+ // Helper AddRange() - Call this function when EndInit is called
+ internal void PostEndInit() {
+ DataColumn[] cols = _mostRecentColumns;
+ _mostRecentColumns = null;
+ AddRange (cols);
}
+
+ /// <summary>
+ /// Do the same as Constains -method but case sensitive
+ /// </summary>
+ private bool CaseSensitiveContains(string columnName)
+ {
+ DataColumn column = this[columnName];
+
+ if (column != null)
+ return string.Compare(column.ColumnName, columnName, false) == 0;
+
+ return false;
+ }
+
+ internal void UpdateAutoIncrement(DataColumn col,bool isAutoIncrement)
+ {
+ if (isAutoIncrement)
+ {
+ if (!autoIncrement.Contains(col))
+ autoIncrement.Add(col);
+ }
+ else
+ {
+ if (autoIncrement.Contains(col))
+ autoIncrement.Remove(col);
+ }
+ }
+
+ private int IndexOf (string name, bool error)
+ {
+ int count = 0, match = -1;
+ for (int i = 0; i < List.Count; i++)
+ {
+ String name2 = ((DataColumn) List[i]).ColumnName;
+ if (String.Compare (name, name2, true) == 0)
+ {
+ if (String.Compare (name, name2, false) == 0)
+ return i;
+ match = i;
+ count++;
+ }
+ }
+ if (count == 1)
+ return match;
+ if (count > 1 && error)
+ throw new ArgumentException ("There is no match for the name in the same case and there are multiple matches in different case.");
+ return -1;
+ }
+
+ #region Events
+
/// <summary>
/// Occurs when the columns collection changes, either by adding or removing a column.
/// </summary>
+ [ResDescriptionAttribute ("Occurs whenever this collection's membership changes.")]
public event CollectionChangeEventHandler CollectionChanged;
+
+ #endregion
}
}