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.
39 using System.Collections;
40 using System.ComponentModel;
42 namespace System.Data {
44 internal class Doublet
46 public Doublet (int count, string columnname)
49 this.columnNames.Add (columnname);
51 // Number of case insensitive column name
53 // Array of exact column names
54 public ArrayList columnNames = new ArrayList ();
57 [Editor ("Microsoft.VSDesigner.Data.Design.ColumnsCollectionEditor, " + Consts.AssemblyMicrosoft_VSDesigner,
58 "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
62 [DefaultEvent ("CollectionChanged")]
67 class DataColumnCollection : InternalDataCollectionBase {
68 //This hashtable maps between unique case insensetive column name to a doublet containing column ref and column count
70 private Hashtable columnNameCount = new Hashtable (StringComparer.OrdinalIgnoreCase);
72 private Hashtable columnNameCount = new Hashtable (CaseInsensitiveHashCodeProvider.Default, CaseInsensitiveComparer.Default);
74 //This hashtable maps between column name to DataColumn object.
75 private Hashtable columnFromName = new Hashtable ();
76 //This ArrayList contains the auto-increment columns names
77 private ArrayList autoIncrement = new ArrayList ();
78 //This holds the next index to use for default column name.
79 private int defaultColumnIndex = 1;
80 //table should be the DataTable this DataColumnCollection belongs to.
81 private DataTable parentTable = null;
82 // Keep reference to most recent columns passed to AddRange()
83 // so that they can be added when EndInit() is called.
84 DataColumn [] _mostRecentColumns = null;
86 static readonly string ColumnPrefix = "Column";
88 // Internal Constructor. This Class can only be created from other classes in this assembly.
89 internal DataColumnCollection (DataTable table)
95 /// Gets the DataColumn from the collection at the specified index.
101 DataColumn this [int index] {
103 if (index < 0 || index >= base.List.Count)
104 throw new IndexOutOfRangeException ("Cannot find column " + index + ".");
105 return (DataColumn) base.List [index];
110 /// Gets the DataColumn from the collection with the specified name.
116 DataColumn this [string name] {
120 throw new ArgumentNullException ("name");
123 DataColumn dc = columnFromName [name] as DataColumn;
127 int tmp = IndexOf (name, true);
128 return tmp == -1 ? null : (DataColumn) base.List [tmp];
133 /// Gets a list of the DataColumnCollection items.
135 protected override ArrayList List {
136 get { return base.List; }
139 internal ArrayList AutoIncrmentColumns {
140 get { return autoIncrement; }
146 //DefaultValue set and AutoInc set check
147 //?Validate Expression??
148 //Name check and creation
150 //Check Unique if true then add a unique constraint
151 //?Notify Rows of new column ?
156 /// Creates and adds a DataColumn object to the DataColumnCollection.
158 /// <returns></returns>
165 DataColumn column = new DataColumn (null);
171 public void CopyTo (DataColumn [] array, int index)
173 CopyTo ((Array) array, index);
177 internal void RegisterName (string name, DataColumn column)
180 columnFromName.Add (name, column);
181 } catch (ArgumentException) {
182 throw new DuplicateNameException ("A DataColumn named '" + name + "' already belongs to this DataTable.");
185 // Get existing doublet
186 Doublet d = (Doublet) columnNameCount [name];
188 // Add reference count
191 d.columnNames.Add (name);
193 // no existing doublet
195 d = new Doublet (1, name);
196 columnNameCount [name] = d;
200 if (name.Length <= ColumnPrefix.Length || !name.StartsWith (ColumnPrefix, StringComparison.Ordinal))
203 if (name.Length <= ColumnPrefix.Length || !name.StartsWith (ColumnPrefix))
207 if (name == MakeName (defaultColumnIndex + 1)) {
209 defaultColumnIndex++;
210 } while (Contains (MakeName (defaultColumnIndex + 1)));
214 internal void UnregisterName (string name)
216 if (columnFromName.Contains (name))
217 columnFromName.Remove (name);
219 // Get the existing doublet
220 Doublet d = (Doublet) columnNameCount [name];
222 // decrease reference count
224 d.columnNames.Remove (name);
225 // remove doublet if no more references
227 columnNameCount.Remove (name);
230 if (name.StartsWith(ColumnPrefix) && name == MakeName(defaultColumnIndex - 1)) {
232 defaultColumnIndex--;
233 } while (!Contains (MakeName (defaultColumnIndex - 1)) && defaultColumnIndex > 1);
237 private string GetNextDefaultColumnName ()
239 string defColumnName = MakeName (defaultColumnIndex);
240 for (int index = defaultColumnIndex + 1; Contains (defColumnName); ++index) {
241 defColumnName = MakeName (index);
242 defaultColumnIndex++;
244 defaultColumnIndex++;
245 return defColumnName;
248 static readonly string[] TenColumns = { "Column0", "Column1", "Column2", "Column3", "Column4", "Column5", "Column6", "Column7", "Column8", "Column9" };
250 static string MakeName (int index)
253 return TenColumns [index];
254 return String.Concat (ColumnPrefix, index.ToString());
258 /// Creates and adds the specified DataColumn object to the DataColumnCollection.
260 /// <param name="column">The DataColumn to add.</param>
261 public void Add (DataColumn column)
264 throw new ArgumentNullException ("column", "'column' argument cannot be null.");
267 /* in 1.1, they must do this here, as the
268 * setting of ColumnName below causes an event
270 column.PropertyChanged += new PropertyChangedEventHandler (ColumnPropertyChanged);
273 if (column.ColumnName.Length == 0) {
274 column.ColumnName = GetNextDefaultColumnName ();
277 // if (Contains(column.ColumnName))
278 // throw new DuplicateNameException("A DataColumn named '" + column.ColumnName + "' already belongs to this DataTable.");
280 if (column.Table != null)
281 throw new ArgumentException ("Column '" + column.ColumnName + "' already belongs to this or another DataTable.");
283 column.SetTable (parentTable);
284 RegisterName (column.ColumnName, column);
285 int ordinal = base.List.Add (column);
288 column.Ordinal = ordinal;
290 column.SetOrdinal (ordinal);
293 // Check if the Column Expression is ok
294 if (column.CompiledExpression != null)
295 if (parentTable.Rows.Count == 0)
296 column.CompiledExpression.Eval (parentTable.NewRow());
298 column.CompiledExpression.Eval (parentTable.Rows[0]);
300 // if table already has rows we need to allocate space
301 // in the column data container
302 if (parentTable.Rows.Count > 0)
303 column.DataContainer.Capacity = parentTable.RecordCache.CurrentCapacity;
305 if (column.AutoIncrement) {
306 DataRowCollection rows = column.Table.Rows;
307 for (int i = 0; i < rows.Count; i++)
308 rows [i] [ordinal] = column.AutoIncrementValue ();
311 if (column.AutoIncrement)
312 autoIncrement.Add (column);
315 column.PropertyChanged += new PropertyChangedEventHandler (ColumnPropertyChanged);
318 OnCollectionChanged (new CollectionChangeEventArgs(CollectionChangeAction.Add, column));
322 /// Creates and adds a DataColumn object with the specified name to the DataColumnCollection.
324 /// <param name="columnName">The name of the column.</param>
325 /// <returns>The newly created DataColumn.</returns>
330 DataColumn Add (string columnName)
332 DataColumn column = new DataColumn (columnName);
338 /// Creates and adds a DataColumn object with the specified name and type to the DataColumnCollection.
340 /// <param name="columnName">The ColumnName to use when cretaing the column.</param>
341 /// <param name="type">The DataType of the new column.</param>
342 /// <returns>The newly created DataColumn.</returns>
347 DataColumn Add (string columnName, Type type)
349 if (columnName == null || columnName == "")
350 columnName = GetNextDefaultColumnName ();
352 DataColumn column = new DataColumn (columnName, type);
358 /// Creates and adds a DataColumn object with the specified name, type, and expression to the DataColumnCollection.
360 /// <param name="columnName">The name to use when creating the column.</param>
361 /// <param name="type">The DataType of the new column.</param>
362 /// <param name="expression">The expression to assign to the Expression property.</param>
363 /// <returns>The newly created DataColumn.</returns>
368 DataColumn Add (string columnName, Type type, string expression)
370 if (columnName == null || columnName == "")
371 columnName = GetNextDefaultColumnName ();
373 DataColumn column = new DataColumn (columnName, type, expression);
379 /// Copies the elements of the specified DataColumn array to the end of the collection.
381 /// <param name="columns">The array of DataColumn objects to add to the collection.</param>
382 public void AddRange (DataColumn [] columns)
384 if (parentTable.InitInProgress){
385 _mostRecentColumns = columns;
392 foreach (DataColumn column in columns){
399 private string GetColumnDependency (DataColumn column)
402 foreach (DataRelation rel in parentTable.ParentRelations)
403 if (Array.IndexOf (rel.ChildColumns, column) != -1)
404 return String.Format (" child key for relationship {0}.", rel.RelationName);
405 foreach (DataRelation rel in parentTable.ChildRelations)
406 if (Array.IndexOf (rel.ParentColumns, column) != -1)
407 return String.Format (" parent key for relationship {0}.", rel.RelationName);
409 foreach (Constraint c in parentTable.Constraints)
410 if (c.IsColumnContained (column))
411 return String.Format (" constraint {0} on the table {1}.",
412 c.ConstraintName, parentTable);
415 // check if the foreign-key constraint on any table in the dataset refers to this column.
416 // though a forignkeyconstraint automatically creates a uniquecontrainton the parent
417 // table and would fail above, but we still need to check, as it is legal to manually remove
418 // the constraint on the parent table.
419 if (parentTable.DataSet != null)
420 foreach (DataTable table in parentTable.DataSet.Tables)
421 foreach (Constraint c in table.Constraints)
422 if (c is ForeignKeyConstraint && c.IsColumnContained(column))
423 return String.Format (
424 " constraint {0} on the table {1}.", c.ConstraintName, table.TableName);
426 foreach (DataColumn col in this)
427 if (col.CompiledExpression != null && col.CompiledExpression.DependsOn (column))
428 return col.Expression;
433 /// Checks whether a given column can be removed from the collection.
435 /// <param name="column">A DataColumn in the collection.</param>
436 /// <returns>true if the column can be removed; otherwise, false.</returns>
437 public bool CanRemove (DataColumn column)
439 if (column == null || column.Table != parentTable || GetColumnDependency (column) != String.Empty)
445 /// Clears the collection of any columns.
449 CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Refresh, this);
451 // its not necessary to check if each column in the collection can removed.
452 // Can simply check, if there are any constraints/relations related to the table,
453 // in which case, throw an exception.
454 // Also, shudnt check for expression columns since all the columns in the table
455 // are being removed.
456 if (parentTable.Constraints.Count != 0 ||
457 parentTable.ParentRelations.Count != 0 ||
458 parentTable.ChildRelations.Count != 0)
459 foreach (DataColumn col in this) {
460 string s = GetColumnDependency (col);
461 if (s != String.Empty)
462 throw new ArgumentException ("Cannot remove this column, because it is part of the" + s);
465 if (parentTable.DataSet != null)
466 foreach (DataTable table in parentTable.DataSet.Tables)
467 foreach (Constraint c in table.Constraints) {
468 if (!(c is ForeignKeyConstraint) ||
469 ((ForeignKeyConstraint) c).RelatedTable != parentTable)
471 throw new ArgumentException (
473 "Cannot remove this column, because it is part of the constraint {0} on the table {1}",
474 c.ConstraintName, table.TableName));
477 foreach (DataColumn col in this)
478 col.ResetColumnInfo ();
480 columnFromName.Clear ();
481 autoIncrement.Clear ();
482 columnNameCount.Clear ();
484 defaultColumnIndex = 1;
485 OnCollectionChanged (e);
489 /// Checks whether the collection contains a column with the specified name.
491 /// <param name="name">The ColumnName of the column to check for.</param>
492 /// <returns>true if a column exists with this name; otherwise, false.</returns>
493 public bool Contains (string name)
495 if (columnFromName.Contains (name))
498 return (IndexOf (name, false) != -1);
502 /// Gets the index of a column specified by name.
504 /// <param name="column">The name of the column to return.</param>
505 /// <returns>The index of the column specified by column if it is found; otherwise, -1.</returns>
510 int IndexOf (DataColumn column)
514 return base.List.IndexOf (column);
518 /// Gets the index of the column with the given name (the name is not case sensitive).
520 /// <param name="columnName">The name of the column to find.</param>
521 /// <returns>The zero-based index of the column with the specified name, or -1 if the column doesn't exist in the collection.</returns>
522 public int IndexOf (string columnName)
524 if (columnName == null)
526 DataColumn dc = columnFromName [columnName] as DataColumn;
531 return IndexOf (columnName, false);
535 /// Raises the OnCollectionChanged event.
537 /// <param name="ccevent">A CollectionChangeEventArgs that contains the event data.</param>
543 void OnCollectionChanged (CollectionChangeEventArgs ccevent)
545 parentTable.ResetPropertyDescriptorsCache ();
546 if (CollectionChanged != null)
547 CollectionChanged (this, ccevent);
551 /// Raises the OnCollectionChanging event.
553 /// <param name="ccevent">A CollectionChangeEventArgs that contains the event data.</param>
555 protected internal virtual
559 void OnCollectionChanging (CollectionChangeEventArgs ccevent)
561 if (CollectionChanged != null) {
562 //FIXME: this is not right
563 //CollectionChanged(this, ccevent);
564 throw new NotImplementedException();
569 /// Removes the specified DataColumn object from the collection.
571 /// <param name="column">The DataColumn to remove.</param>
572 public void Remove (DataColumn column)
575 throw new ArgumentNullException ("column", "'column' argument cannot be null.");
577 if (!Contains (column.ColumnName))
578 throw new ArgumentException ("Cannot remove a column that doesn't belong to this table.");
580 string dependency = GetColumnDependency (column);
581 if (dependency != String.Empty)
582 throw new ArgumentException ("Cannot remove this column, because it is part of " + dependency);
584 CollectionChangeEventArgs e = new CollectionChangeEventArgs (CollectionChangeAction.Remove, column);
586 int ordinal = column.Ordinal;
587 UnregisterName (column.ColumnName);
588 base.List.Remove (column);
591 column.ResetColumnInfo ();
593 //Update the ordinals
594 for( int i = ordinal ; i < this.Count ; i ++ )
598 this[i].SetOrdinal(i);
601 if (parentTable != null)
602 parentTable.OnRemoveColumn (column);
604 if (column.AutoIncrement)
605 autoIncrement.Remove (column);
607 column.PropertyChanged -= new PropertyChangedEventHandler (ColumnPropertyChanged);
609 OnCollectionChanged (e);
613 /// Removes the DataColumn object with the specified name from the collection.
615 /// <param name="name">The name of the column to remove.</param>
616 public void Remove (string name)
618 DataColumn column = this [name];
621 throw new ArgumentException ("Column '" + name + "' does not belong to table " + ( parentTable == null ? "" : parentTable.TableName ) + ".");
626 /// Removes the column at the specified index from the collection.
628 /// <param name="index">The index of the column to remove.</param>
629 public void RemoveAt (int index)
632 throw new IndexOutOfRangeException ("Cannot find column " + index + ".");
634 DataColumn column = this [index];
638 // Helper AddRange() - Call this function when EndInit is called
639 internal void PostAddRange ()
641 if (_mostRecentColumns == null)
644 foreach (DataColumn column in _mostRecentColumns){
649 _mostRecentColumns = null;
652 internal void UpdateAutoIncrement (DataColumn col,bool isAutoIncrement)
654 if (isAutoIncrement) {
655 if (!autoIncrement.Contains (col))
656 autoIncrement.Add (col);
658 if (autoIncrement.Contains (col))
659 autoIncrement.Remove (col);
663 private int IndexOf (string name, bool error)
665 // exact case matching has already be done by the caller
666 // Get existing doublet
667 Doublet d = (Doublet) columnNameCount [name];
671 // return index of the column from the only column name of the doublet
672 return base.List.IndexOf (columnFromName [d.columnNames [0]]);
673 } else if (d.count > 1 && error) {
674 // there's more than one, exception!
675 throw new ArgumentException ("There is no match for '" + name + "' in the same case and there are multiple matches in different case.");
686 /// Occurs when the columns collection changes, either by adding or removing a column.
688 [ResDescriptionAttribute ("Occurs whenever this collection's membership changes.")]
689 public event CollectionChangeEventHandler CollectionChanged;
691 internal event CollectionChangeEventHandler CollectionMetaDataChanged;
694 private void OnCollectionMetaDataChanged (CollectionChangeEventArgs ccevent)
696 parentTable.ResetPropertyDescriptorsCache ();
697 if (CollectionMetaDataChanged != null)
698 CollectionMetaDataChanged (this, ccevent);
701 private void ColumnPropertyChanged (object sender, PropertyChangedEventArgs args)
703 OnCollectionMetaDataChanged (new CollectionChangeEventArgs(CollectionChangeAction.Refresh, sender));
707 internal void MoveColumn (int oldOrdinal, int newOrdinal)
709 if (newOrdinal == -1 || newOrdinal > this.Count)
710 throw new ArgumentOutOfRangeException ("ordinal", "Ordinal '" + newOrdinal + "' exceeds the maximum number.");
711 if (oldOrdinal == newOrdinal)
714 int start = newOrdinal > oldOrdinal ? oldOrdinal : newOrdinal;
715 int end = newOrdinal > oldOrdinal ? newOrdinal : oldOrdinal;
716 int direction = newOrdinal > oldOrdinal ? 1 : (-1);
718 DataColumn currColumn = this [start];
719 for (int i = start; i < end; i += direction) {
720 List [i] = List [i+direction];
721 ((DataColumn) List [i]).Ordinal = i;
723 List [end] = currColumn;
724 currColumn.Ordinal = end;