2 // System.Data.DataColumnCollection.cs
5 // Christopher Podurgiel (cpodurgiel@msn.com)
6 // Stuart Caborn <stuart.caborn@virgin.net>
7 // Tim Coleman (tim@timcoleman.com)
10 // Copyright (C) Tim Coleman, 2002
11 // Copyright (C) Daniel Morgan, 2003
15 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
17 // Permission is hereby granted, free of charge, to any person obtaining
18 // a copy of this software and associated documentation files (the
19 // "Software"), to deal in the Software without restriction, including
20 // without limitation the rights to use, copy, modify, merge, publish,
21 // distribute, sublicense, and/or sell copies of the Software, and to
22 // permit persons to whom the Software is furnished to do so, subject to
23 // the following conditions:
25 // The above copyright notice and this permission notice shall be
26 // included in all copies or substantial portions of the Software.
28 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
29 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
30 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
31 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
32 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
33 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
34 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
38 using System.Collections;
39 using System.ComponentModel;
41 namespace System.Data {
44 [DefaultEvent ("CollectionChanged")]
45 public class DataColumnCollection : InternalDataCollectionBase
47 //This hashtable maps between column name to DataColumn object.
48 private Hashtable columnFromName = new Hashtable();
49 //This ArrayList contains the auto-increment columns names
50 private ArrayList autoIncrement = new ArrayList();
51 //This holds the next index to use for default column name.
52 private int defaultColumnIndex = 1;
53 //table should be the DataTable this DataColumnCollection belongs to.
54 private DataTable parentTable = null;
56 // Internal Constructor. This Class can only be created from other classes in this assembly.
57 internal DataColumnCollection(DataTable table):base()
63 /// Gets the DataColumn from the collection at the specified index.
65 public virtual DataColumn this[int index]
69 return (DataColumn) base.List[index];
74 /// Gets the DataColumn from the collection with the specified name.
76 public virtual DataColumn this[string name]
80 DataColumn dc = columnFromName[name] as DataColumn;
85 int tmp = IndexOf(name, true);
93 /// Gets a list of the DataColumnCollection items.
95 protected override ArrayList List
103 internal ArrayList AutoIncrmentColumns
107 return autoIncrement;
114 //DefaultValue set and AutoInc set check
115 //?Validate Expression??
116 //Name check and creation
118 //Check Unique if true then add a unique constraint
119 //?Notify Rows of new column ?
124 /// Creates and adds a DataColumn object to the DataColumnCollection.
126 /// <returns></returns>
127 public virtual DataColumn Add()
129 string defaultName = GetNextDefaultColumnName ();
130 DataColumn column = new DataColumn (defaultName);
135 private void RegisterName(string name, DataColumn column)
137 if (columnFromName.Contains(name))
138 throw new DuplicateNameException("A DataColumn named '" + name + "' already belongs to this DataTable.");
140 columnFromName[name] = column;
142 if (name.StartsWith("Column") && name == MakeName(defaultColumnIndex + 1))
146 defaultColumnIndex++;
148 while (Contains(MakeName(defaultColumnIndex + 1)));
152 private void UnregisterName(string name)
154 if (columnFromName.Contains(name))
155 columnFromName.Remove(name);
157 if (name.StartsWith("Column") && name == MakeName(defaultColumnIndex - 1))
161 defaultColumnIndex--;
163 while (!Contains(MakeName(defaultColumnIndex - 1)) && defaultColumnIndex > 1);
167 private string GetNextDefaultColumnName ()
169 string defColumnName = MakeName(defaultColumnIndex);
170 for (int index = defaultColumnIndex + 1; Contains(defColumnName); ++index) {
171 defColumnName = MakeName(index);
172 defaultColumnIndex++;
174 defaultColumnIndex++;
175 return defColumnName;
178 static readonly string[] TenColumns = { "Column0", "Column1", "Column2", "Column3", "Column4", "Column5", "Column6", "Column7", "Column8", "Column9" };
180 private string MakeName(int index)
183 return TenColumns[index];
185 return String.Concat("Column", index.ToString());
189 /// Creates and adds the specified DataColumn object to the DataColumnCollection.
191 /// <param name="column">The DataColumn to add.</param>
192 public void Add(DataColumn column)
196 throw new ArgumentNullException ("column", "'column' argument cannot be null.");
198 if (column.ColumnName.Equals(String.Empty))
200 column.ColumnName = GetNextDefaultColumnName ();
203 // if (Contains(column.ColumnName))
204 // throw new DuplicateNameException("A DataColumn named '" + column.ColumnName + "' already belongs to this DataTable.");
206 if (column.Table != null)
207 throw new ArgumentException ("Column '" + column.ColumnName + "' already belongs to this or another DataTable.");
209 CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Add, this);
211 column.SetTable (parentTable);
212 RegisterName(column.ColumnName, column);
213 int ordinal = base.List.Add(column);
214 column.SetOrdinal (ordinal);
216 // if table already has rows we need to allocate space
217 // in the column data container
218 if ( parentTable.Rows.Count > 0 ) {
219 column.DataContainer.Capacity = parentTable.RecordCache.CurrentCapacity;
222 if (column.AutoIncrement) {
223 DataRowCollection rows = column.Table.Rows;
224 for (int i = 0; i < rows.Count; i++)
225 rows [i] [ordinal] = column.AutoIncrementValue ();
228 if (column.AutoIncrement)
229 autoIncrement.Add(column);
231 OnCollectionChanged (e);
235 /// Creates and adds a DataColumn object with the specified name to the DataColumnCollection.
237 /// <param name="columnName">The name of the column.</param>
238 /// <returns>The newly created DataColumn.</returns>
239 public virtual DataColumn Add(string columnName)
241 if (columnName == null || columnName == String.Empty)
243 columnName = GetNextDefaultColumnName();
246 DataColumn column = new DataColumn(columnName);
252 /// Creates and adds a DataColumn object with the specified name and type to the DataColumnCollection.
254 /// <param name="columnName">The ColumnName to use when cretaing the column.</param>
255 /// <param name="type">The DataType of the new column.</param>
256 /// <returns>The newly created DataColumn.</returns>
257 public virtual DataColumn Add(string columnName, Type type)
259 if (columnName == null || columnName == "")
261 columnName = GetNextDefaultColumnName ();
264 DataColumn column = new DataColumn(columnName, type);
270 /// Creates and adds a DataColumn object with the specified name, type, and expression to the DataColumnCollection.
272 /// <param name="columnName">The name to use when creating the column.</param>
273 /// <param name="type">The DataType of the new column.</param>
274 /// <param name="expression">The expression to assign to the Expression property.</param>
275 /// <returns>The newly created DataColumn.</returns>
276 public virtual DataColumn Add(string columnName, Type type, string expression)
278 if (columnName == null || columnName == "")
280 columnName = GetNextDefaultColumnName ();
283 DataColumn column = new DataColumn(columnName, type, expression);
289 /// Copies the elements of the specified DataColumn array to the end of the collection.
291 /// <param name="columns">The array of DataColumn objects to add to the collection.</param>
292 public void AddRange(DataColumn[] columns)
294 foreach (DataColumn column in columns)
302 /// Checks whether a given column can be removed from the collection.
304 /// <param name="column">A DataColumn in the collection.</param>
305 /// <returns>true if the column can be removed; otherwise, false.</returns>
306 public bool CanRemove(DataColumn column)
309 //Check that the column does not have a null reference.
314 //Check that the column is part of this collection.
315 if (!Contains(column.ColumnName))
322 //Check if this column is part of a relationship. (this could probably be written better)
323 foreach (DataRelation childRelation in parentTable.ChildRelations)
325 foreach (DataColumn childColumn in childRelation.ChildColumns)
327 if (childColumn == column)
333 foreach (DataColumn parentColumn in childRelation.ParentColumns)
335 if (parentColumn == column)
342 //Check if this column is part of a relationship. (this could probably be written better)
343 foreach (DataRelation parentRelation in parentTable.ParentRelations)
345 foreach (DataColumn childColumn in parentRelation.ChildColumns)
347 if (childColumn == column)
353 foreach (DataColumn parentColumn in parentRelation.ParentColumns)
355 if (parentColumn == column)
363 //Check if another column's expression depends on this column.
365 foreach (DataColumn dataColumn in List)
367 if (dataColumn.Expression.ToString().IndexOf(column.ColumnName) > 0)
373 // check for part of pk
374 UniqueConstraint uc = UniqueConstraint.GetPrimaryKeyConstraint (parentTable.Constraints);
375 if (uc != null && uc.Contains (column))
376 throw new ArgumentException (String.Format ("Cannot remove column {0}, because" +
377 " it is part of primarykey",
379 // check for part of fk
380 DataSet ds = parentTable.DataSet;
383 foreach (DataTable t in ds.Tables) {
384 if (t == parentTable)
386 foreach (Constraint c in t.Constraints) {
387 if (! (c is ForeignKeyConstraint))
389 ForeignKeyConstraint fk = (ForeignKeyConstraint) c;
390 if (fk.Contains (column, true) // look in parent
391 || fk.Contains (column, false)) // look in children
392 throw new ArgumentException (String.Format ("Cannot remove column {0}, because" +
393 " it is part of foreign key constraint",
407 /// Clears the collection of any columns.
411 CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Refresh, this);
414 // FIXME: Hmm... This loop could look little nicer :)
415 foreach (DataColumn Col in List) {
417 foreach (DataRelation Rel in Col.Table.ParentRelations) {
419 foreach (DataColumn Col2 in Rel.ParentColumns) {
420 if (Object.ReferenceEquals (Col, Col2))
421 throw new ArgumentException ("Cannot remove this column, because " +
422 "it is part of the parent key for relationship " +
423 Rel.RelationName + ".");
426 foreach (DataColumn Col2 in Rel.ChildColumns) {
427 if (Object.ReferenceEquals (Col, Col2))
428 throw new ArgumentException ("Cannot remove this column, because " +
429 "it is part of the parent key for relationship " +
430 Rel.RelationName + ".");
435 foreach (DataRelation Rel in Col.Table.ChildRelations) {
437 foreach (DataColumn Col2 in Rel.ParentColumns) {
438 if (Object.ReferenceEquals (Col, Col2))
439 throw new ArgumentException ("Cannot remove this column, because " +
440 "it is part of the parent key for relationship " +
441 Rel.RelationName + ".");
444 foreach (DataColumn Col2 in Rel.ChildColumns) {
445 if (Object.ReferenceEquals (Col, Col2))
446 throw new ArgumentException ("Cannot remove this column, because " +
447 "it is part of the parent key for relationship " +
448 Rel.RelationName + ".");
454 // whether all columns can be removed
455 foreach (DataColumn col in this) {
456 if (!CanRemove (col))
457 throw new ArgumentException ("Cannot remove column {0}", col.ColumnName);
461 columnFromName.Clear();
462 autoIncrement.Clear();
464 OnCollectionChanged(e);
465 } catch (Exception ex) {
466 throw new ArgumentException (ex.Message, ex);
471 /// Checks whether the collection contains a column with the specified name.
473 /// <param name="name">The ColumnName of the column to check for.</param>
474 /// <returns>true if a column exists with this name; otherwise, false.</returns>
475 public bool Contains(string name)
477 if (columnFromName.Contains(name))
480 return (IndexOf(name, false) != -1);
484 /// Gets the index of a column specified by name.
486 /// <param name="column">The name of the column to return.</param>
487 /// <returns>The index of the column specified by column if it is found; otherwise, -1.</returns>
488 public virtual int IndexOf(DataColumn column)
490 return base.List.IndexOf(column);
494 /// Gets the index of the column with the given name (the name is not case sensitive).
496 /// <param name="columnName">The name of the column to find.</param>
497 /// <returns>The zero-based index of the column with the specified name, or -1 if the column doesn't exist in the collection.</returns>
498 public int IndexOf(string columnName)
500 DataColumn dc = columnFromName[columnName] as DataColumn;
505 return IndexOf(columnName, false);
509 /// Raises the OnCollectionChanged event.
511 /// <param name="ccevent">A CollectionChangeEventArgs that contains the event data.</param>
512 protected virtual void OnCollectionChanged(CollectionChangeEventArgs ccevent)
514 if (CollectionChanged != null)
516 CollectionChanged(this, ccevent);
521 /// Raises the OnCollectionChanging event.
523 /// <param name="ccevent">A CollectionChangeEventArgs that contains the event data.</param>
524 protected internal virtual void OnCollectionChanging(CollectionChangeEventArgs ccevent)
526 if (CollectionChanged != null)
528 //FIXME: this is not right
529 //CollectionChanged(this, ccevent);
530 throw new NotImplementedException();
535 /// Removes the specified DataColumn object from the collection.
537 /// <param name="column">The DataColumn to remove.</param>
538 public void Remove(DataColumn column)
541 throw new ArgumentNullException ("column", "'column' argument cannot be null.");
543 if (!Contains(column.ColumnName))
544 throw new ArgumentException ("Cannot remove a column that doesn't belong to this table.");
545 //TODO: can remove first with exceptions
546 //and OnChanging Event
547 CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Remove, this);
549 int ordinal = column.Ordinal;
550 UnregisterName(column.ColumnName);
551 base.List.Remove(column);
553 //Update the ordinals
554 for( int i = ordinal ; i < this.Count ; i ++ )
556 this[i].SetOrdinal( i );
559 if (parentTable != null)
560 parentTable.OnRemoveColumn(column);
562 if (column.AutoIncrement)
563 autoIncrement.Remove(column);
565 OnCollectionChanged(e);
570 /// Removes the DataColumn object with the specified name from the collection.
572 /// <param name="name">The name of the column to remove.</param>
573 public void Remove(string name)
575 DataColumn column = this[name];
578 throw new ArgumentException ("Column '" + name + "' does not belong to table " + ( parentTable == null ? "" : parentTable.TableName ) + ".");
583 /// Removes the column at the specified index from the collection.
585 /// <param name="index">The index of the column to remove.</param>
586 public void RemoveAt(int index)
589 throw new IndexOutOfRangeException ("Cannot find column " + index + ".");
591 DataColumn column = this[index];
597 /// Do the same as Constains -method but case sensitive
599 private bool CaseSensitiveContains(string columnName)
601 DataColumn column = this[columnName];
604 return string.Compare(column.ColumnName, columnName, false) == 0;
609 internal void UpdateAutoIncrement(DataColumn col,bool isAutoIncrement)
613 if (!autoIncrement.Contains(col))
614 autoIncrement.Add(col);
618 if (autoIncrement.Contains(col))
619 autoIncrement.Remove(col);
623 private int IndexOf (string name, bool error)
625 int count = 0, match = -1;
626 for (int i = 0; i < List.Count; i++)
628 String name2 = ((DataColumn) List[i]).ColumnName;
629 if (String.Compare (name, name2, true) == 0)
631 if (String.Compare (name, name2, false) == 0)
639 if (count > 1 && error)
640 throw new ArgumentException ("There is no match for the name in the same case and there are multiple matches in different case.");
647 /// Occurs when the columns collection changes, either by adding or removing a column.
649 [ResDescriptionAttribute ("Occurs whenever this collection's membership changes.")]
650 public event CollectionChangeEventHandler CollectionChanged;