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.
316 //Check that the column is part of this collection.
317 if (!Contains(column.ColumnName))
324 //Check if this column is part of a relationship. (this could probably be written better)
325 foreach (DataRelation childRelation in parentTable.ChildRelations)
327 foreach (DataColumn childColumn in childRelation.ChildColumns)
329 if (childColumn == column)
335 foreach (DataColumn parentColumn in childRelation.ParentColumns)
337 if (parentColumn == column)
344 //Check if this column is part of a relationship. (this could probably be written better)
345 foreach (DataRelation parentRelation in parentTable.ParentRelations)
347 foreach (DataColumn childColumn in parentRelation.ChildColumns)
349 if (childColumn == column)
355 foreach (DataColumn parentColumn in parentRelation.ParentColumns)
357 if (parentColumn == column)
365 //Check if another column's expression depends on this column.
367 foreach (DataColumn dataColumn in List)
369 if (dataColumn.Expression.ToString().IndexOf(column.ColumnName) > 0)
375 //TODO: check constraints
381 /// Clears the collection of any columns.
385 CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Refresh, this);
388 // FIXME: Hmm... This loop could look little nicer :)
389 foreach (DataColumn Col in List) {
391 foreach (DataRelation Rel in Col.Table.ParentRelations) {
393 foreach (DataColumn Col2 in Rel.ParentColumns) {
394 if (Object.ReferenceEquals (Col, Col2))
395 throw new ArgumentException ("Cannot remove this column, because " +
396 "it is part of the parent key for relationship " +
397 Rel.RelationName + ".");
400 foreach (DataColumn Col2 in Rel.ChildColumns) {
401 if (Object.ReferenceEquals (Col, Col2))
402 throw new ArgumentException ("Cannot remove this column, because " +
403 "it is part of the parent key for relationship " +
404 Rel.RelationName + ".");
409 foreach (DataRelation Rel in Col.Table.ChildRelations) {
411 foreach (DataColumn Col2 in Rel.ParentColumns) {
412 if (Object.ReferenceEquals (Col, Col2))
413 throw new ArgumentException ("Cannot remove this column, because " +
414 "it is part of the parent key for relationship " +
415 Rel.RelationName + ".");
418 foreach (DataColumn Col2 in Rel.ChildColumns) {
419 if (Object.ReferenceEquals (Col, Col2))
420 throw new ArgumentException ("Cannot remove this column, because " +
421 "it is part of the parent key for relationship " +
422 Rel.RelationName + ".");
428 columnFromName.Clear();
429 autoIncrement.Clear();
431 OnCollectionChanged(e);
436 /// Checks whether the collection contains a column with the specified name.
438 /// <param name="name">The ColumnName of the column to check for.</param>
439 /// <returns>true if a column exists with this name; otherwise, false.</returns>
440 public bool Contains(string name)
442 if (columnFromName.Contains(name))
445 return (IndexOf(name, false) != -1);
449 /// Gets the index of a column specified by name.
451 /// <param name="column">The name of the column to return.</param>
452 /// <returns>The index of the column specified by column if it is found; otherwise, -1.</returns>
453 public virtual int IndexOf(DataColumn column)
455 return base.List.IndexOf(column);
459 /// Gets the index of the column with the given name (the name is not case sensitive).
461 /// <param name="columnName">The name of the column to find.</param>
462 /// <returns>The zero-based index of the column with the specified name, or -1 if the column doesn't exist in the collection.</returns>
463 public int IndexOf(string columnName)
465 DataColumn dc = columnFromName[columnName] as DataColumn;
470 return IndexOf(columnName, false);
474 /// Raises the OnCollectionChanged event.
476 /// <param name="ccevent">A CollectionChangeEventArgs that contains the event data.</param>
477 protected virtual void OnCollectionChanged(CollectionChangeEventArgs ccevent)
479 if (CollectionChanged != null)
481 CollectionChanged(this, ccevent);
486 /// Raises the OnCollectionChanging event.
488 /// <param name="ccevent">A CollectionChangeEventArgs that contains the event data.</param>
489 protected internal virtual void OnCollectionChanging(CollectionChangeEventArgs ccevent)
491 if (CollectionChanged != null)
493 //FIXME: this is not right
494 //CollectionChanged(this, ccevent);
495 throw new NotImplementedException();
500 /// Removes the specified DataColumn object from the collection.
502 /// <param name="column">The DataColumn to remove.</param>
503 public void Remove(DataColumn column)
506 throw new ArgumentNullException ("column", "'column' argument cannot be null.");
508 if (!Contains(column.ColumnName))
509 throw new ArgumentException ("Cannot remove a column that doesn't belong to this table.");
510 //TODO: can remove first with exceptions
511 //and OnChanging Event
512 CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Remove, this);
514 int ordinal = column.Ordinal;
515 UnregisterName(column.ColumnName);
516 base.List.Remove(column);
518 //Update the ordinals
519 for( int i = ordinal ; i < this.Count ; i ++ )
521 this[i].SetOrdinal( i );
524 if (parentTable != null)
525 parentTable.OnRemoveColumn(column);
527 if (column.AutoIncrement)
528 autoIncrement.Remove(column);
530 OnCollectionChanged(e);
535 /// Removes the DataColumn object with the specified name from the collection.
537 /// <param name="name">The name of the column to remove.</param>
538 public void Remove(string name)
540 DataColumn column = this[name];
543 throw new ArgumentException ("Column '" + name + "' does not belong to table " + ( parentTable == null ? "" : parentTable.TableName ) + ".");
548 /// Removes the column at the specified index from the collection.
550 /// <param name="index">The index of the column to remove.</param>
551 public void RemoveAt(int index)
554 throw new IndexOutOfRangeException ("Cannot find column " + index + ".");
556 DataColumn column = this[index];
562 /// Do the same as Constains -method but case sensitive
564 private bool CaseSensitiveContains(string columnName)
566 DataColumn column = this[columnName];
569 return string.Compare(column.ColumnName, columnName, false) == 0;
574 internal void UpdateAutoIncrement(DataColumn col,bool isAutoIncrement)
578 if (!autoIncrement.Contains(col))
579 autoIncrement.Add(col);
583 if (autoIncrement.Contains(col))
584 autoIncrement.Remove(col);
588 private int IndexOf (string name, bool error)
590 int count = 0, match = -1;
591 for (int i = 0; i < List.Count; i++)
593 String name2 = ((DataColumn) List[i]).ColumnName;
594 if (String.Compare (name, name2, true) == 0)
596 if (String.Compare (name, name2, false) == 0)
604 if (count > 1 && error)
605 throw new ArgumentException ("There is no match for the name in the same case and there are multiple matches in different case.");
612 /// Occurs when the columns collection changes, either by adding or removing a column.
614 [ResDescriptionAttribute ("Occurs whenever this collection's membership changes.")]
615 public event CollectionChangeEventHandler CollectionChanged;