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 internal void RegisterName(string name, DataColumn column)
163 if (columnFromName.Contains(name))
164 throw new DuplicateNameException("A DataColumn named '" + name + "' already belongs to this DataTable.");
166 columnFromName[name] = column;
168 if (name.StartsWith("Column") && name == MakeName(defaultColumnIndex + 1))
172 defaultColumnIndex++;
174 while (Contains(MakeName(defaultColumnIndex + 1)));
178 internal void UnregisterName(string name)
180 if (columnFromName.Contains(name))
181 columnFromName.Remove(name);
183 if (name.StartsWith("Column") && name == MakeName(defaultColumnIndex - 1))
187 defaultColumnIndex--;
189 while (!Contains(MakeName(defaultColumnIndex - 1)) && defaultColumnIndex > 1);
193 private string GetNextDefaultColumnName ()
195 string defColumnName = MakeName(defaultColumnIndex);
196 for (int index = defaultColumnIndex + 1; Contains(defColumnName); ++index) {
197 defColumnName = MakeName(index);
198 defaultColumnIndex++;
200 defaultColumnIndex++;
201 return defColumnName;
204 static readonly string[] TenColumns = { "Column0", "Column1", "Column2", "Column3", "Column4", "Column5", "Column6", "Column7", "Column8", "Column9" };
206 private string MakeName(int index)
209 return TenColumns[index];
211 return String.Concat("Column", index.ToString());
215 /// Creates and adds the specified DataColumn object to the DataColumnCollection.
217 /// <param name="column">The DataColumn to add.</param>
218 public void Add(DataColumn column)
222 throw new ArgumentNullException ("column", "'column' argument cannot be null.");
224 if (column.ColumnName.Equals(String.Empty))
226 column.ColumnName = GetNextDefaultColumnName ();
229 // if (Contains(column.ColumnName))
230 // throw new DuplicateNameException("A DataColumn named '" + column.ColumnName + "' already belongs to this DataTable.");
232 if (column.Table != null)
233 throw new ArgumentException ("Column '" + column.ColumnName + "' already belongs to this or another DataTable.");
235 CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Add, this);
237 column.SetTable (parentTable);
238 RegisterName(column.ColumnName, column);
239 int ordinal = base.List.Add(column);
240 column.SetOrdinal (ordinal);
242 // Check if the Column Expression is ok
243 if (column.CompiledExpression != null)
244 if (parentTable.Rows.Count == 0)
245 column.CompiledExpression.Eval (parentTable.NewRow());
247 column.CompiledExpression.Eval (parentTable.Rows[0]);
249 // if table already has rows we need to allocate space
250 // in the column data container
251 if ( parentTable.Rows.Count > 0 ) {
252 column.DataContainer.Capacity = parentTable.RecordCache.CurrentCapacity;
255 if (column.AutoIncrement) {
256 DataRowCollection rows = column.Table.Rows;
257 for (int i = 0; i < rows.Count; i++)
258 rows [i] [ordinal] = column.AutoIncrementValue ();
261 if (column.AutoIncrement)
262 autoIncrement.Add(column);
264 OnCollectionChanged (e);
268 /// Creates and adds a DataColumn object with the specified name to the DataColumnCollection.
270 /// <param name="columnName">The name of the column.</param>
271 /// <returns>The newly created DataColumn.</returns>
276 DataColumn Add(string columnName)
278 if (columnName == null || columnName == String.Empty)
280 columnName = GetNextDefaultColumnName();
283 DataColumn column = new DataColumn(columnName);
289 /// Creates and adds a DataColumn object with the specified name and type to the DataColumnCollection.
291 /// <param name="columnName">The ColumnName to use when cretaing the column.</param>
292 /// <param name="type">The DataType of the new column.</param>
293 /// <returns>The newly created DataColumn.</returns>
298 DataColumn Add(string columnName, Type type)
300 if (columnName == null || columnName == "")
302 columnName = GetNextDefaultColumnName ();
305 DataColumn column = new DataColumn(columnName, type);
311 /// Creates and adds a DataColumn object with the specified name, type, and expression to the DataColumnCollection.
313 /// <param name="columnName">The name to use when creating the column.</param>
314 /// <param name="type">The DataType of the new column.</param>
315 /// <param name="expression">The expression to assign to the Expression property.</param>
316 /// <returns>The newly created DataColumn.</returns>
321 DataColumn Add(string columnName, Type type, string expression)
323 if (columnName == null || columnName == "")
325 columnName = GetNextDefaultColumnName ();
328 DataColumn column = new DataColumn(columnName, type, expression);
334 /// Copies the elements of the specified DataColumn array to the end of the collection.
336 /// <param name="columns">The array of DataColumn objects to add to the collection.</param>
337 public void AddRange(DataColumn[] columns)
339 if (parentTable.InitInProgress){
340 _mostRecentColumns = columns;
347 foreach (DataColumn column in columns){
354 private string GetColumnDependency (DataColumn column)
357 foreach (DataRelation rel in parentTable.ParentRelations)
358 if (Array.IndexOf (rel.ChildColumns, column) != -1)
359 return String.Format (" child key for relationship {0}.", rel.RelationName);
360 foreach (DataRelation rel in parentTable.ChildRelations)
361 if (Array.IndexOf (rel.ParentColumns, column) != -1)
362 return String.Format (" parent key for relationship {0}.", rel.RelationName);
364 foreach (Constraint c in parentTable.Constraints)
365 if (c.IsColumnContained (column))
366 return String.Format (" constraint {0} on the table {1}.",
367 c.ConstraintName, parentTable);
370 // check if the foreign-key constraint on any table in the dataset refers to this column.
371 // though a forignkeyconstraint automatically creates a uniquecontrainton the parent
372 // table and would fail above, but we still need to check, as it is legal to manually remove
373 // the constraint on the parent table.
374 if (parentTable.DataSet != null)
375 foreach (DataTable table in parentTable.DataSet.Tables)
376 foreach (Constraint c in table.Constraints)
377 if (c is ForeignKeyConstraint && c.IsColumnContained(column))
378 return String.Format (" constraint {0} on the table {1}.",
379 c.ConstraintName, table.TableName);
381 foreach (DataColumn col in this)
382 if (col.CompiledExpression != null && col.CompiledExpression.DependsOn (column))
383 return col.Expression;
388 /// Checks whether a given column can be removed from the collection.
390 /// <param name="column">A DataColumn in the collection.</param>
391 /// <returns>true if the column can be removed; otherwise, false.</returns>
392 public bool CanRemove(DataColumn column)
394 if (column == null || column.Table != parentTable || GetColumnDependency(column) != String.Empty)
400 /// Clears the collection of any columns.
404 CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Refresh, this);
406 // its not necessary to check if each column in the collection can removed.
407 // Can simply check, if there are any constraints/relations related to the table,
408 // in which case, throw an exception.
409 // Also, shudnt check for expression columns since all the columns in the table
410 // are being removed.
411 if (parentTable.Constraints.Count != 0 ||
412 parentTable.ParentRelations.Count != 0 ||
413 parentTable.ChildRelations.Count != 0)
414 foreach (DataColumn col in this) {
415 string s = GetColumnDependency (col);
416 if (s != String.Empty)
417 throw new ArgumentException (
418 "Cannot remove this column, because it is part of the"+ s);
421 if (parentTable.DataSet != null)
422 foreach (DataTable table in parentTable.DataSet.Tables)
423 foreach (Constraint c in table.Constraints) {
424 if (!(c is ForeignKeyConstraint) ||
425 ((ForeignKeyConstraint)c).RelatedTable != parentTable)
427 throw new ArgumentException (String.Format ("Cannot remove this column, " +
428 "because it is part of the constraint {0} on " +
429 "the table {1}", c.ConstraintName, table.TableName));
432 foreach (DataColumn col in this)
433 col.ResetColumnInfo ();
435 columnFromName.Clear();
436 autoIncrement.Clear();
438 OnCollectionChanged(e);
442 /// Checks whether the collection contains a column with the specified name.
444 /// <param name="name">The ColumnName of the column to check for.</param>
445 /// <returns>true if a column exists with this name; otherwise, false.</returns>
446 public bool Contains(string name)
448 if (columnFromName.Contains(name))
451 return (IndexOf(name, false) != -1);
455 /// Gets the index of a column specified by name.
457 /// <param name="column">The name of the column to return.</param>
458 /// <returns>The index of the column specified by column if it is found; otherwise, -1.</returns>
463 int IndexOf(DataColumn column)
467 return base.List.IndexOf(column);
471 /// Gets the index of the column with the given name (the name is not case sensitive).
473 /// <param name="columnName">The name of the column to find.</param>
474 /// <returns>The zero-based index of the column with the specified name, or -1 if the column doesn't exist in the collection.</returns>
475 public int IndexOf(string columnName)
477 if (columnName == null)
479 DataColumn dc = columnFromName[columnName] as DataColumn;
484 return IndexOf(columnName, false);
488 /// Raises the OnCollectionChanged event.
490 /// <param name="ccevent">A CollectionChangeEventArgs that contains the event data.</param>
495 void OnCollectionChanged(CollectionChangeEventArgs ccevent)
497 parentTable.ResetPropertyDescriptorsCache();
498 if (CollectionChanged != null)
500 CollectionChanged(this, ccevent);
505 /// Raises the OnCollectionChanging event.
507 /// <param name="ccevent">A CollectionChangeEventArgs that contains the event data.</param>
512 void OnCollectionChanging(CollectionChangeEventArgs ccevent)
514 if (CollectionChanged != null)
516 //FIXME: this is not right
517 //CollectionChanged(this, ccevent);
518 throw new NotImplementedException();
523 /// Removes the specified DataColumn object from the collection.
525 /// <param name="column">The DataColumn to remove.</param>
526 public void Remove(DataColumn column)
529 throw new ArgumentNullException ("column", "'column' argument cannot be null.");
531 if (!Contains(column.ColumnName))
532 throw new ArgumentException ("Cannot remove a column that doesn't belong to this table.");
534 string dependency = GetColumnDependency (column);
535 if (dependency != String.Empty)
536 throw new ArgumentException ("Cannot remove this column, because it is part of " + dependency);
538 CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Remove, this);
540 int ordinal = column.Ordinal;
541 UnregisterName(column.ColumnName);
542 base.List.Remove(column);
545 column.ResetColumnInfo ();
547 //Update the ordinals
548 for( int i = ordinal ; i < this.Count ; i ++ )
549 this[i].SetOrdinal( i );
551 if (parentTable != null)
552 parentTable.OnRemoveColumn(column);
554 if (column.AutoIncrement)
555 autoIncrement.Remove(column);
557 OnCollectionChanged(e);
561 /// Removes the DataColumn object with the specified name from the collection.
563 /// <param name="name">The name of the column to remove.</param>
564 public void Remove(string name)
566 DataColumn column = this[name];
569 throw new ArgumentException ("Column '" + name + "' does not belong to table " + ( parentTable == null ? "" : parentTable.TableName ) + ".");
574 /// Removes the column at the specified index from the collection.
576 /// <param name="index">The index of the column to remove.</param>
577 public void RemoveAt(int index)
580 throw new IndexOutOfRangeException ("Cannot find column " + index + ".");
582 DataColumn column = this[index];
586 // Helper AddRange() - Call this function when EndInit is called
587 internal void PostAddRange ()
589 if (_mostRecentColumns == null)
592 foreach (DataColumn column in _mostRecentColumns){
597 _mostRecentColumns = null;
602 /// Do the same as Constains -method but case sensitive
604 private bool CaseSensitiveContains(string columnName)
606 DataColumn column = this[columnName];
609 return string.Compare(column.ColumnName, columnName, false) == 0;
614 internal void UpdateAutoIncrement(DataColumn col,bool isAutoIncrement)
618 if (!autoIncrement.Contains(col))
619 autoIncrement.Add(col);
623 if (autoIncrement.Contains(col))
624 autoIncrement.Remove(col);
628 private int IndexOf (string name, bool error)
630 int count = 0, match = -1;
631 for (int i = 0; i < List.Count; i++)
633 String name2 = ((DataColumn) List[i]).ColumnName;
634 if (String.Compare (name, name2, true) == 0)
636 if (String.Compare (name, name2, false) == 0)
644 if (count > 1 && error)
645 throw new ArgumentException ("There is no match for the name in the same case and there are multiple matches in different case.");
652 /// Occurs when the columns collection changes, either by adding or removing a column.
654 [ResDescriptionAttribute ("Occurs whenever this collection's membership changes.")]
655 public event CollectionChangeEventHandler CollectionChanged;