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 {
43 [Editor ("Microsoft.VSDesigner.Data.Design.ColumnsCollectionEditor, " + Consts.AssemblyMicrosoft_VSDesigner,
44 "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
48 [DefaultEvent ("CollectionChanged")]
53 class DataColumnCollection : InternalDataCollectionBase
55 //This hashtable maps between column name to DataColumn object.
56 private Hashtable columnFromName = new Hashtable();
57 //This ArrayList contains the auto-increment columns names
58 private ArrayList autoIncrement = new ArrayList();
59 //This holds the next index to use for default column name.
60 private int defaultColumnIndex = 1;
61 //table should be the DataTable this DataColumnCollection belongs to.
62 private DataTable parentTable = null;
63 // Keep reference to most recent columns passed to AddRange()
64 // so that they can be added when EndInit() is called.
65 DataColumn[] _mostRecentColumns = null;
67 // Internal Constructor. This Class can only be created from other classes in this assembly.
68 internal DataColumnCollection(DataTable table):base()
74 /// Gets the DataColumn from the collection at the specified index.
80 DataColumn this[int index]
84 if (index < 0 || index > base.List.Count) {
85 throw new IndexOutOfRangeException("Cannot find column " + index + ".");
87 return (DataColumn) base.List[index];
92 /// Gets the DataColumn from the collection with the specified name.
98 DataColumn this[string name]
102 DataColumn dc = columnFromName[name] as DataColumn;
107 int tmp = IndexOf(name, true);
115 /// Gets a list of the DataColumnCollection items.
117 protected override ArrayList List
125 internal ArrayList AutoIncrmentColumns
129 return autoIncrement;
136 //DefaultValue set and AutoInc set check
137 //?Validate Expression??
138 //Name check and creation
140 //Check Unique if true then add a unique constraint
141 //?Notify Rows of new column ?
146 /// Creates and adds a DataColumn object to the DataColumnCollection.
148 /// <returns></returns>
155 string defaultName = GetNextDefaultColumnName ();
156 DataColumn column = new DataColumn (defaultName);
161 public void CopyTo (DataColumn [] array, int index)
163 CopyTo ((Array) array, index);
166 internal void RegisterName(string name, DataColumn column)
168 if (columnFromName.Contains(name))
169 throw new DuplicateNameException("A DataColumn named '" + name + "' already belongs to this DataTable.");
171 columnFromName[name] = column;
173 if (name.StartsWith("Column") && name == MakeName(defaultColumnIndex + 1))
177 defaultColumnIndex++;
179 while (Contains(MakeName(defaultColumnIndex + 1)));
183 internal void UnregisterName(string name)
185 if (columnFromName.Contains(name))
186 columnFromName.Remove(name);
188 if (name.StartsWith("Column") && name == MakeName(defaultColumnIndex - 1))
192 defaultColumnIndex--;
194 while (!Contains(MakeName(defaultColumnIndex - 1)) && defaultColumnIndex > 1);
198 private string GetNextDefaultColumnName ()
200 string defColumnName = MakeName(defaultColumnIndex);
201 for (int index = defaultColumnIndex + 1; Contains(defColumnName); ++index) {
202 defColumnName = MakeName(index);
203 defaultColumnIndex++;
205 defaultColumnIndex++;
206 return defColumnName;
209 static readonly string[] TenColumns = { "Column0", "Column1", "Column2", "Column3", "Column4", "Column5", "Column6", "Column7", "Column8", "Column9" };
211 private string MakeName(int index)
214 return TenColumns[index];
216 return String.Concat("Column", index.ToString());
220 /// Creates and adds the specified DataColumn object to the DataColumnCollection.
222 /// <param name="column">The DataColumn to add.</param>
223 public void Add(DataColumn column)
227 throw new ArgumentNullException ("column", "'column' argument cannot be null.");
229 if (column.ColumnName.Equals(String.Empty))
231 column.ColumnName = GetNextDefaultColumnName ();
234 // if (Contains(column.ColumnName))
235 // throw new DuplicateNameException("A DataColumn named '" + column.ColumnName + "' already belongs to this DataTable.");
237 if (column.Table != null)
238 throw new ArgumentException ("Column '" + column.ColumnName + "' already belongs to this or another DataTable.");
240 CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Add, this);
242 column.SetTable (parentTable);
243 RegisterName(column.ColumnName, column);
244 int ordinal = base.List.Add(column);
246 column.Ordinal = ordinal;
248 column.SetOrdinal (ordinal);
251 // Check if the Column Expression is ok
252 if (column.CompiledExpression != null)
253 if (parentTable.Rows.Count == 0)
254 column.CompiledExpression.Eval (parentTable.NewRow());
256 column.CompiledExpression.Eval (parentTable.Rows[0]);
258 // if table already has rows we need to allocate space
259 // in the column data container
260 if ( parentTable.Rows.Count > 0 ) {
261 column.DataContainer.Capacity = parentTable.RecordCache.CurrentCapacity;
264 if (column.AutoIncrement) {
265 DataRowCollection rows = column.Table.Rows;
266 for (int i = 0; i < rows.Count; i++)
267 rows [i] [ordinal] = column.AutoIncrementValue ();
270 if (column.AutoIncrement)
271 autoIncrement.Add(column);
273 OnCollectionChanged (e);
277 /// Creates and adds a DataColumn object with the specified name to the DataColumnCollection.
279 /// <param name="columnName">The name of the column.</param>
280 /// <returns>The newly created DataColumn.</returns>
285 DataColumn Add(string columnName)
287 if (columnName == null || columnName == String.Empty)
289 columnName = GetNextDefaultColumnName();
292 DataColumn column = new DataColumn(columnName);
298 /// Creates and adds a DataColumn object with the specified name and type to the DataColumnCollection.
300 /// <param name="columnName">The ColumnName to use when cretaing the column.</param>
301 /// <param name="type">The DataType of the new column.</param>
302 /// <returns>The newly created DataColumn.</returns>
307 DataColumn Add(string columnName, Type type)
309 if (columnName == null || columnName == "")
311 columnName = GetNextDefaultColumnName ();
314 DataColumn column = new DataColumn(columnName, type);
320 /// Creates and adds a DataColumn object with the specified name, type, and expression to the DataColumnCollection.
322 /// <param name="columnName">The name to use when creating the column.</param>
323 /// <param name="type">The DataType of the new column.</param>
324 /// <param name="expression">The expression to assign to the Expression property.</param>
325 /// <returns>The newly created DataColumn.</returns>
330 DataColumn Add(string columnName, Type type, string expression)
332 if (columnName == null || columnName == "")
334 columnName = GetNextDefaultColumnName ();
337 DataColumn column = new DataColumn(columnName, type, expression);
343 /// Copies the elements of the specified DataColumn array to the end of the collection.
345 /// <param name="columns">The array of DataColumn objects to add to the collection.</param>
346 public void AddRange(DataColumn[] columns)
348 if (parentTable.InitInProgress){
349 _mostRecentColumns = columns;
356 foreach (DataColumn column in columns){
363 private string GetColumnDependency (DataColumn column)
366 foreach (DataRelation rel in parentTable.ParentRelations)
367 if (Array.IndexOf (rel.ChildColumns, column) != -1)
368 return String.Format (" child key for relationship {0}.", rel.RelationName);
369 foreach (DataRelation rel in parentTable.ChildRelations)
370 if (Array.IndexOf (rel.ParentColumns, column) != -1)
371 return String.Format (" parent key for relationship {0}.", rel.RelationName);
373 foreach (Constraint c in parentTable.Constraints)
374 if (c.IsColumnContained (column))
375 return String.Format (" constraint {0} on the table {1}.",
376 c.ConstraintName, parentTable);
379 // check if the foreign-key constraint on any table in the dataset refers to this column.
380 // though a forignkeyconstraint automatically creates a uniquecontrainton the parent
381 // table and would fail above, but we still need to check, as it is legal to manually remove
382 // the constraint on the parent table.
383 if (parentTable.DataSet != null)
384 foreach (DataTable table in parentTable.DataSet.Tables)
385 foreach (Constraint c in table.Constraints)
386 if (c is ForeignKeyConstraint && c.IsColumnContained(column))
387 return String.Format (" constraint {0} on the table {1}.",
388 c.ConstraintName, table.TableName);
390 foreach (DataColumn col in this)
391 if (col.CompiledExpression != null && col.CompiledExpression.DependsOn (column))
392 return col.Expression;
397 /// Checks whether a given column can be removed from the collection.
399 /// <param name="column">A DataColumn in the collection.</param>
400 /// <returns>true if the column can be removed; otherwise, false.</returns>
401 public bool CanRemove(DataColumn column)
403 if (column == null || column.Table != parentTable || GetColumnDependency(column) != String.Empty)
409 /// Clears the collection of any columns.
413 CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Refresh, this);
415 // its not necessary to check if each column in the collection can removed.
416 // Can simply check, if there are any constraints/relations related to the table,
417 // in which case, throw an exception.
418 // Also, shudnt check for expression columns since all the columns in the table
419 // are being removed.
420 if (parentTable.Constraints.Count != 0 ||
421 parentTable.ParentRelations.Count != 0 ||
422 parentTable.ChildRelations.Count != 0)
423 foreach (DataColumn col in this) {
424 string s = GetColumnDependency (col);
425 if (s != String.Empty)
426 throw new ArgumentException (
427 "Cannot remove this column, because it is part of the"+ s);
430 if (parentTable.DataSet != null)
431 foreach (DataTable table in parentTable.DataSet.Tables)
432 foreach (Constraint c in table.Constraints) {
433 if (!(c is ForeignKeyConstraint) ||
434 ((ForeignKeyConstraint)c).RelatedTable != parentTable)
436 throw new ArgumentException (String.Format ("Cannot remove this column, " +
437 "because it is part of the constraint {0} on " +
438 "the table {1}", c.ConstraintName, table.TableName));
441 foreach (DataColumn col in this)
442 col.ResetColumnInfo ();
444 columnFromName.Clear();
445 autoIncrement.Clear();
447 OnCollectionChanged(e);
451 /// Checks whether the collection contains a column with the specified name.
453 /// <param name="name">The ColumnName of the column to check for.</param>
454 /// <returns>true if a column exists with this name; otherwise, false.</returns>
455 public bool Contains(string name)
457 if (columnFromName.Contains(name))
460 return (IndexOf(name, false) != -1);
464 /// Gets the index of a column specified by name.
466 /// <param name="column">The name of the column to return.</param>
467 /// <returns>The index of the column specified by column if it is found; otherwise, -1.</returns>
472 int IndexOf(DataColumn column)
476 return base.List.IndexOf(column);
480 /// Gets the index of the column with the given name (the name is not case sensitive).
482 /// <param name="columnName">The name of the column to find.</param>
483 /// <returns>The zero-based index of the column with the specified name, or -1 if the column doesn't exist in the collection.</returns>
484 public int IndexOf(string columnName)
486 if (columnName == null)
488 DataColumn dc = columnFromName[columnName] as DataColumn;
493 return IndexOf(columnName, false);
497 /// Raises the OnCollectionChanged event.
499 /// <param name="ccevent">A CollectionChangeEventArgs that contains the event data.</param>
505 void OnCollectionChanged(CollectionChangeEventArgs ccevent)
507 parentTable.ResetPropertyDescriptorsCache();
508 if (CollectionChanged != null)
509 CollectionChanged(this, ccevent);
513 /// Raises the OnCollectionChanging event.
515 /// <param name="ccevent">A CollectionChangeEventArgs that contains the event data.</param>
517 protected internal virtual
521 void OnCollectionChanging(CollectionChangeEventArgs ccevent)
523 if (CollectionChanged != null) {
524 //FIXME: this is not right
525 //CollectionChanged(this, ccevent);
526 throw new NotImplementedException();
531 /// Removes the specified DataColumn object from the collection.
533 /// <param name="column">The DataColumn to remove.</param>
534 public void Remove(DataColumn column)
537 throw new ArgumentNullException ("column", "'column' argument cannot be null.");
539 if (!Contains(column.ColumnName))
540 throw new ArgumentException ("Cannot remove a column that doesn't belong to this table.");
542 string dependency = GetColumnDependency (column);
543 if (dependency != String.Empty)
544 throw new ArgumentException ("Cannot remove this column, because it is part of " + dependency);
546 CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Remove, this);
548 int ordinal = column.Ordinal;
549 UnregisterName(column.ColumnName);
550 base.List.Remove(column);
553 column.ResetColumnInfo ();
555 //Update the ordinals
556 for( int i = ordinal ; i < this.Count ; i ++ )
560 this[i].SetOrdinal(i);
563 if (parentTable != null)
564 parentTable.OnRemoveColumn(column);
566 if (column.AutoIncrement)
567 autoIncrement.Remove(column);
569 OnCollectionChanged(e);
573 /// Removes the DataColumn object with the specified name from the collection.
575 /// <param name="name">The name of the column to remove.</param>
576 public void Remove(string name)
578 DataColumn column = this[name];
581 throw new ArgumentException ("Column '" + name + "' does not belong to table " + ( parentTable == null ? "" : parentTable.TableName ) + ".");
586 /// Removes the column at the specified index from the collection.
588 /// <param name="index">The index of the column to remove.</param>
589 public void RemoveAt(int index)
592 throw new IndexOutOfRangeException ("Cannot find column " + index + ".");
594 DataColumn column = this[index];
598 // Helper AddRange() - Call this function when EndInit is called
599 internal void PostAddRange ()
601 if (_mostRecentColumns == null)
604 foreach (DataColumn column in _mostRecentColumns){
609 _mostRecentColumns = null;
614 /// Do the same as Constains -method but case sensitive
616 private bool CaseSensitiveContains(string columnName)
618 DataColumn column = this[columnName];
621 return string.Compare(column.ColumnName, columnName, false) == 0;
626 internal void UpdateAutoIncrement(DataColumn col,bool isAutoIncrement)
630 if (!autoIncrement.Contains(col))
631 autoIncrement.Add(col);
635 if (autoIncrement.Contains(col))
636 autoIncrement.Remove(col);
640 private int IndexOf (string name, bool error)
642 int count = 0, match = -1;
643 for (int i = 0; i < List.Count; i++)
645 String name2 = ((DataColumn) List[i]).ColumnName;
646 if (String.Compare (name, name2, true) == 0)
648 if (String.Compare (name, name2, false) == 0)
656 if (count > 1 && error)
657 throw new ArgumentException ("There is no match for the name in the same case and there are multiple matches in different case.");
664 /// Occurs when the columns collection changes, either by adding or removing a column.
666 [ResDescriptionAttribute ("Occurs whenever this collection's membership changes.")]
667 public event CollectionChangeEventHandler CollectionChanged;
672 internal void MoveColumn (int oldOrdinal, int newOrdinal)
674 if (newOrdinal == -1 || newOrdinal > this.Count)
675 throw new ArgumentOutOfRangeException ("ordinal", "Ordinal '" + newOrdinal + "' exceeds the maximum number.");
676 if (oldOrdinal == newOrdinal)
679 int start = newOrdinal > oldOrdinal ? oldOrdinal : newOrdinal;
680 int end = newOrdinal > oldOrdinal ? newOrdinal : oldOrdinal;
681 int direction = newOrdinal > oldOrdinal ? 1 : (-1);
683 DataColumn currColumn = this [start];
684 for (int i=start; i < end; i+=direction) {
685 List [i] = List [i+direction];
686 ((DataColumn)List [i]).Ordinal = i;
688 List [end] = currColumn;
689 currColumn.Ordinal = end;