* DataRow.cs : Throw exceptions if Row is Detached.
[mono.git] / mcs / class / System.Data / System.Data / DataColumn.cs
1 //
2 // System.Data.DataColumn.cs
3 //
4 // Author:
5 //   Franklin Wise (gracenote@earthlink.net)
6 //   Christopher Podurgiel (cpodurgiel@msn.com)
7 //   Rodrigo Moya (rodrigo@ximian.com)
8 //   Daniel Morgan (danmorg@sc.rr.com)
9 //   Tim Coleman (tim@timcoleman.com)
10 //
11 // (C) Copyright 2002, Franklin Wise
12 // (C) Chris Podurgiel
13 // (C) Ximian, Inc 2002
14 // Copyright (C) Tim Coleman, 2002
15 // Copyright (C) Daniel Morgan, 2002, 2003
16 //
17
18 using System;
19 using System.ComponentModel;
20 using System.Reflection;
21
22 namespace System.Data {
23         internal delegate void DelegateColumnValueChange(DataColumn column, DataRow row, object proposedValue);
24         
25         /// <summary>
26         /// Summary description for DataColumn.
27         /// </summary>
28
29         [Editor]
30         [ToolboxItem (false)]
31         [DefaultMember ("Item")]
32         [DefaultProperty ("ColumnName")]
33         [DesignTimeVisible (false)]
34         public class DataColumn : MarshalByValueComponent
35         {               
36                 #region Events
37                 [MonoTODO]
38                 //used for constraint validation
39                 //if an exception is fired during this event the change should be canceled
40                 internal event DelegateColumnValueChange ValidateColumnValueChange;
41
42                 //used for FK Constraint Cascading rules
43                 internal event DelegateColumnValueChange ColumnValueChanging;
44                 #endregion //Events
45                 
46                 #region Fields
47
48                 private bool _allowDBNull = true;
49                 private bool _autoIncrement = false;
50                 private long _autoIncrementSeed = 0;
51                 private long _autoIncrementStep = 1;
52                 private long _nextAutoIncrementValue = 0;
53                 private bool dataHasBeenSet = false;
54                 private string _caption = null;
55                 private MappingType _columnMapping = MappingType.Element;
56                 private string _columnName = null;
57                 private Type _dataType = Type.GetType ("System.String");
58                 private object _defaultValue = DBNull.Value;
59                 private string expression = "";
60                 private PropertyCollection _extendedProperties = new PropertyCollection ();
61                 private int maxLength = -1; //-1 represents no length limit
62                 private string nameSpace = "";
63                 private int _ordinal = -1; //-1 represents not part of a collection
64                 private string prefix = "";
65                 private bool readOnly = false;
66                 private DataTable _table = null;
67                 private bool unique = false;
68
69                 #endregion // Fields
70
71                 #region Constructors
72
73                 public DataColumn()
74                 {
75                 }
76
77                 //TODO: Ctor init vars directly
78                 public DataColumn(string columnName): this()
79                 {
80                         ColumnName = columnName;
81                 }
82
83                 public DataColumn(string columnName, Type dataType): this(columnName)
84                 {
85                         if(dataType == null) {
86                                 throw new ArgumentNullException("dataType can't be null.");
87                         }
88                         
89                         DataType = dataType;
90
91                 }
92
93                 public DataColumn( string columnName, Type dataType, 
94                         string expr): this(columnName, dataType)
95                 {
96                         if (expr != null) Expression = expr;
97                 }
98
99                 public DataColumn(string columnName, Type dataType, 
100                         string expr, MappingType type): this(columnName, dataType, expr)
101                 {
102                         ColumnMapping = type;
103                 }
104                 #endregion
105
106                 #region Properties
107
108                 [DataCategory ("Data")]
109                 [DataSysDescription ("Indicates whether null values are allowed in this column.")]
110                 [DefaultValue (true)]
111                 public bool AllowDBNull
112                 {
113                         get {
114                                 return _allowDBNull;
115                         }
116                         set {
117                                 //TODO: If we are a part of the table and this value changes
118                                 //we need to validate that all the existing values conform to the new setting
119
120                                 if (true == value)
121                                 {
122                                         _allowDBNull = true;
123                                         return;
124                                 }
125                                 
126                                 //if Value == false case
127                                 if (null != _table)
128                                 {
129                                         if (_table.Rows.Count > 0)
130                                         {
131                                                 bool nullsFound = false;
132                                                 for(int r = 0; r < _table.Rows.Count; r++) {
133                                                         DataRow row = _table.Rows[r];
134                                                         if(row.IsNull(this)) {
135                                                                 nullsFound = true;
136                                                                 break;
137                                                         }
138                                                 }
139                                                 
140                                                 if (nullsFound)
141                                                         throw new DataException("Column '" + ColumnName + "' has null values in it.");
142                                                 //TODO: Validate no null values exist
143                                                 //do we also check different versions of the row??
144                                         }
145                                 }
146                                         
147                                 _allowDBNull = value;
148                         }
149                 }
150         
151                 /// <summary>
152                 /// Gets or sets a value indicating whether the column automatically increments the value of the column for new rows added to the table.
153                 /// </summary>
154                 /// <remarks>
155                 ///             If the type of this column is not Int16, Int32, or Int64 when this property is set, 
156                 ///             the DataType property is coerced to Int32. An exception is generated if this is a computed column 
157                 ///             (that is, the Expression property is set.) The incremented value is used only if the row's value for this column, 
158                 ///             when added to the columns collection, is equal to the default value.
159                 ///     </remarks>
160                 [DataCategory ("Data")]
161                 [DataSysDescription ("Indicates whether the column automatically increments itself for new rows added to the table.  The type of this column must be Int16, Int32, or Int64.")]
162                 [DefaultValue (false)]
163                 [RefreshProperties (RefreshProperties.All)]
164                 public bool AutoIncrement
165                 {
166                         get {
167                                 return _autoIncrement;
168                         }
169                         set {
170                                 if(value == true)
171                                 {
172                                         //Can't be true if this is a computed column
173                                         if (Expression != string.Empty)
174                                         {
175                                                 throw new ArgumentException("Can not Auto Increment a computed column."); 
176                                         }
177
178                                         //If the DataType of this Column isn't an Int
179                                         //Make it an int
180                                         TypeCode typeCode = Type.GetTypeCode(_dataType);
181                                         if(typeCode != TypeCode.Int16 && 
182                                            typeCode != TypeCode.Int32 && 
183                                            typeCode != TypeCode.Int64)
184                                         {
185                                                 _dataType = typeof(Int32); 
186                                         }
187                                 }
188                                 _autoIncrement = value;
189                         }
190                 }
191
192                 [DataCategory ("Data")]
193                 [DataSysDescription ("Indicates the starting value for an AutoIncrement column.")]
194                 [DefaultValue (0)]
195                 public long AutoIncrementSeed
196                 {
197                         get {
198                                 return _autoIncrementSeed;
199                         }
200                         set {
201                                 _autoIncrementSeed = value;
202                                 _nextAutoIncrementValue = _autoIncrementSeed;
203                         }
204                 }
205
206                 [DataCategory ("Data")]
207                 [DataSysDescription ("Indicates the increment used by an AutoIncrement column.")]
208                 [DefaultValue (1)]
209                 public long AutoIncrementStep
210                 {
211                         get {
212                                 return _autoIncrementStep;
213                         }
214                         set {
215                                 _autoIncrementStep = value;
216                         }
217                 }
218
219                 internal void UpdateAutoIncrementValue (long value) 
220                 {
221                         if(value > _nextAutoIncrementValue) {
222                                 _nextAutoIncrementValue = value;
223                                 AutoIncrementValue ();
224                         }
225                 }
226
227                 internal long AutoIncrementValue () 
228                 {
229                         long currentValue = _nextAutoIncrementValue;
230                         _nextAutoIncrementValue += AutoIncrementStep;
231                         return currentValue;
232                 }
233
234                 internal bool DataHasBeenSet {
235                         get {
236                                 return dataHasBeenSet;
237                         }
238                         set {
239                                 dataHasBeenSet = value;
240                         }
241                 }
242
243                 [DataCategory ("Data")]
244                 [DataSysDescription ("Indicates the default user-interface caption for this column.")]
245                 public string Caption 
246                 {
247                         get {
248                                 if(_caption == null)
249                                         return ColumnName;
250                                 else
251                                         return _caption;
252                         }
253                         set {
254                                 _caption = value;
255                         }
256                 }
257                 [DataSysDescription ("Indicates how this column persists in XML: as an attribute, element, simple content node, or nothing.")]
258                 [DefaultValue (MappingType.Element)]
259                 public virtual MappingType ColumnMapping
260                 {
261                         get {
262                                 return _columnMapping;
263                         }
264                         set {
265                                 _columnMapping = value;
266                         }
267                 }
268
269                 [DataCategory ("Data")]
270                 [DataSysDescription ("Indicates the name used to look up this column in the Columns collection of a DataTable.")]
271                 [RefreshProperties (RefreshProperties.All)]
272                 [DefaultValue ("")]
273                 public string ColumnName
274                 {
275                         get {
276                                 return "" + _columnName;
277                         }
278                         set {
279                                 //Both are checked after the column is part of the collection
280                                 //TODO: Check Name duplicate
281                                 //TODO: check Name != null
282                                 _columnName = value;
283                         }
284                 }
285
286                 [DataCategory ("Data")]
287                 [DataSysDescription ("Indicates the type of data stored in this column.")]
288                 [DefaultValue (typeof (string))]
289                 [RefreshProperties (RefreshProperties.All)]
290                 public Type DataType
291                 {
292                         get {
293                                 return _dataType;
294                         }
295                         set {
296                                 // check if data already exists can we change the datatype
297                                 if(DataHasBeenSet == true)
298                                         throw new ArgumentException("The column already has data stored.");
299
300                                 // we want to check that the datatype is supported?
301                                 TypeCode typeCode = Type.GetTypeCode(value);
302                                 
303                                 //Check AutoIncrement status, make compatible datatype
304                                 if(AutoIncrement == true) {
305                                         if(typeCode != TypeCode.Int16 &&
306                                            typeCode != TypeCode.Int32 &&
307                                            typeCode != TypeCode.Int64)
308                                                 AutoIncrement = false;
309                                 }
310                                 _dataType = value;
311                         }
312                 }
313
314                 /// <summary>
315                 /// 
316                 /// </summary>
317                 /// <remarks>When AutoIncrement is set to true, there can be no default value.</remarks>
318                 /// <exception cref="System.InvalidCastException"></exception>
319                 /// <exception cref="System.ArgumentException"></exception>
320                 [DataCategory ("Data")]
321                 [DataSysDescription ("Indicates the default column value used when adding new rows to the table.")]
322                 public object DefaultValue
323                 {
324                         get {
325                                 return _defaultValue;
326                         }
327                         set {
328                                 object tmpObj;
329                                 if ((this._defaultValue == null) || (!this._defaultValue.Equals(value)))
330                                 {
331                                         //If autoIncrement == true throw
332                                         if (AutoIncrement) 
333                                         {
334                                                 throw new ArgumentException("Can not set default value while" +
335                                                         " AutoIncrement is true on this column.");
336                                         }
337
338                                         if (value == null) 
339                                         {
340                                                 tmpObj = DBNull.Value;
341                                         }
342                                         else
343                                                 tmpObj = value;
344
345                                         if ((this.DataType != typeof (object))&& (tmpObj != DBNull.Value))
346                                         {
347                                                 try
348                                                 {
349                                                         //Casting to the new type
350                                                         tmpObj= Convert.ChangeType(tmpObj,this.DataType);
351                                                 }
352                                                 catch (InvalidCastException)
353                                                 {
354                                                         throw new InvalidCastException("Default Value type is not compatible with" + 
355                                                                 " column type.");
356                                                 }
357                                         }
358                                         _defaultValue = tmpObj;
359                                 }
360                         }
361                 }
362
363                 [MonoTODO]
364                 [DataCategory ("Data")]
365                 [DataSysDescription ("Indicates the value that this column computes for each row based on other columns instead of taking user input.")]
366                 [DefaultValue ("")]
367                 [RefreshProperties (RefreshProperties.All)]
368                 public string Expression
369                 {
370                         get {
371                                 return expression;
372                         }
373                         set {
374                                 //TODO: validation of the expression
375                                 expression = value;  //Check?
376                                 
377                                 if (expression != string.Empty)
378                                         ReadOnly = true;
379                         }
380                 }
381
382                 [Browsable (false)]
383                 [DataCategory ("Data")]
384                 [DataSysDescription ("The collection that holds custom user information.")]
385                 public PropertyCollection ExtendedProperties
386                 {
387                         get {
388                                 return _extendedProperties;
389                         }
390                 }
391
392                 [DataCategory ("Data")]
393                 [DataSysDescription ("Indicates the maximum length of the value this column allows.")]
394                 [DefaultValue (-1)]
395                 public int MaxLength
396                 {
397                         get {
398                                 //Default == -1 no max length
399                                 return maxLength;
400                         }
401                         set {
402                                 //only applies to string columns
403                                 maxLength = value;
404                         }
405                 }
406
407                 [DataCategory ("Data")]
408                 [DataSysDescription ("Indicates the XML uri for elements stored in this  column.")]
409                 public string Namespace
410                 {
411                         get {
412                                 return nameSpace;
413                         }
414                         set {
415                                 nameSpace = value;
416                         }
417                 }
418
419                 //Need a good way to set the Ordinal when the column is added to a columnCollection.
420                 [Browsable (false)]
421                 [DataCategory ("Data")]
422                 [DataSysDescription ("Indicates the index of this column in the Columns collection.")]
423                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
424                 public int Ordinal
425                 {
426                         get {
427                                 //value is -1 if not part of a collection
428                                 return _ordinal;
429                         }
430                 }
431
432                 internal void SetOrdinal(int ordinal)
433                 {
434                         _ordinal = ordinal;
435                 }
436
437                 [DataCategory ("Data")]
438                 [DataSysDescription ("Indicates the prefix used for this DataColumn in the xml representation.")]
439                 [DefaultValue ("")]
440                 public string Prefix
441                 {
442                         get {
443                                 return prefix;
444                         }
445                         set {
446                                 prefix = value;
447                         }
448                 }
449
450                 [DataCategory ("Data")]
451                 [DataSysDescription ("Indicates whether this column allows changes once a row has been added to the table.")]
452                 [DefaultValue (false)]
453                 public bool ReadOnly
454                 {
455                         get {
456                                 return readOnly;
457                         }
458                         set {
459                                 readOnly = value;
460                         }
461                 }
462
463                 [Browsable (false)]
464                 [DataCategory ("Data")]
465                 [DataSysDescription ("Returns the DataTable to which this column belongs.")]
466                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]      
467                 public DataTable Table
468                 {
469                         get {
470                                 return _table;
471                         }
472                 }
473
474                 [MonoTODO]
475                 [DataCategory ("Data")]
476                 [DataSysDescription ("Indicates whether this column should restrict its values in the rows of the table to be unique.")]
477                 [DefaultValue (false)]
478                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
479                 public bool Unique 
480                 {
481                         get {
482                                 return unique;
483                         }
484                         set {
485                                 //if Table == null then the UniqueConstraint is
486                                 //created on addition to the collection
487                                 
488                                 //FIXME?: need to check if value is the same
489                                 //because when calling "new UniqueConstraint"
490                                 //the new object tries to set "column.Unique = True"
491                                 //which creates an infinite loop.
492                                 if(unique != value)
493                                 {
494                                 unique = value;
495
496                                         if( value )
497                                         {
498                                                 if (Expression != null && Expression != "")
499                                                         throw new ArgumentException("Cannot change Unique property for the expression column.");
500                                                 if( _table != null )
501                                                 {
502                                                         UniqueConstraint uc = new UniqueConstraint(this);
503                                                         _table.Constraints.Add(uc);
504                                                 }
505                                         }
506                                         else
507                                         {
508                                                 if( _table != null )
509                                                 {
510                                                         ConstraintCollection cc = _table.Constraints;
511                                                         //foreach (Constraint c in cc) 
512                                                         for (int i = 0; i < cc.Count; i++)
513                                                         {
514                                                                 Constraint c = cc[i];
515                                                                 if (c is UniqueConstraint)
516                                                                 {
517                                                                         DataColumn[] cols = ((UniqueConstraint)c).Columns;
518                                                                         
519                                                                         if (cols.Length == 1 && cols[0] == this)
520                                                                         {
521                                                                                 if (!cc.CanRemove(c))
522                                                                                         throw new ArgumentException("Cannot remove unique constraint '" + c.ConstraintName + "'. Remove foreign key constraint first.");
523
524                                                                                 cc.Remove(c);
525                                                                         }
526                                                                         
527                                                                 }
528                                                         }
529                                                 }
530                                         }
531
532                                 }
533                         }
534                 }
535
536                 #endregion // Properties
537
538                 #region Methods
539                 
540 /* ??
541                 [MonoTODO]
542                 protected internal void CheckNotAllowNull() {
543                 }
544
545                 [MonoTODO]
546                 protected void CheckUnique() {
547                 }
548 */
549
550                 /// <summary>
551                 ///  Sets unique true whithout creating Constraint
552                 /// </summary>
553                 internal void SetUnique() 
554                 {
555                         unique = true;
556                 }
557
558
559                 [MonoTODO]
560                 internal void AssertCanAddToCollection()
561                 {
562                         //Check if Default Value is set and AutoInc is set
563                 }
564                 
565                 [MonoTODO]
566                 protected internal virtual void 
567                 OnPropertyChanging (PropertyChangedEventArgs pcevent) {
568                 }
569
570                 [MonoTODO]
571                 protected internal void RaisePropertyChanging(string name) {
572                 }
573
574                 /// <summary>
575                 /// Gets the Expression of the column, if one exists.
576                 /// </summary>
577                 /// <returns>The Expression value, if the property is set; 
578                 /// otherwise, the ColumnName property.</returns>
579                 [MonoTODO]
580                 public override string ToString()
581                 {
582                         if (expression != string.Empty)
583                                 return ColumnName + " + " + expression;
584                         
585                         return ColumnName;
586                 }
587
588                 [MonoTODO]
589                 internal void SetTable(DataTable table) {
590                         _table = table; 
591                         // this will get called by DataTable 
592                         // and DataColumnCollection
593                 }
594
595                 
596                 // Returns true if all the same collumns are in columnSet and compareSet
597                 internal static bool AreColumnSetsTheSame(DataColumn[] columnSet, DataColumn[] compareSet)
598                 {
599                         if (null == columnSet && null == compareSet) return true;
600                         if (null == columnSet || null == compareSet) return false;
601
602                         if (columnSet.Length != compareSet.Length) return false;
603                         
604                         foreach (DataColumn col in columnSet)
605                         {
606                                 bool matchFound = false;
607                                 foreach (DataColumn compare in compareSet)
608                                 {
609                                         if (col == compare)
610                                         {
611                                                 matchFound = true;                                      
612                                         }
613                                 }
614                                 if (! matchFound) return false;
615                         }
616                         
617                         return true;
618                 }
619                 
620                 #endregion // Methods
621
622         }
623 }