2 // System.Data.DataColumn.cs
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)
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
19 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
21 // Permission is hereby granted, free of charge, to any person obtaining
22 // a copy of this software and associated documentation files (the
23 // "Software"), to deal in the Software without restriction, including
24 // without limitation the rights to use, copy, modify, merge, publish,
25 // distribute, sublicense, and/or sell copies of the Software, and to
26 // permit persons to whom the Software is furnished to do so, subject to
27 // the following conditions:
29 // The above copyright notice and this permission notice shall be
30 // included in all copies or substantial portions of the Software.
32 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
33 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
34 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
35 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
36 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
37 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
38 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
42 using System
.ComponentModel
;
43 using System
.Reflection
;
44 using System
.Collections
;
45 using System
.Data
.Common
;
46 using Mono
.Data
.SqlExpressions
;
48 namespace System
.Data
{
49 internal delegate void DelegateColumnValueChange(DataColumn column
, DataRow row
, object proposedValue
);
52 /// Summary description for DataColumn.
57 [DefaultProperty ("ColumnName")]
58 [DesignTimeVisible (false)]
59 public class DataColumn
: MarshalByValueComponent
63 //used for constraint validation
64 //if an exception is fired during this event the change should be canceled
65 internal event DelegateColumnValueChange ValidateColumnValueChange
;
67 //used for FK Constraint Cascading rules
68 internal event DelegateColumnValueChange ColumnValueChanging
;
73 private bool _allowDBNull
= true;
74 private bool _autoIncrement
;
75 private long _autoIncrementSeed
;
76 private long _autoIncrementStep
= 1;
77 private long _nextAutoIncrementValue
;
78 private string _caption
;
79 private MappingType _columnMapping
;
80 private string _columnName
;
81 private object _defaultValue
= DBNull
.Value
;
82 private string expression
;
83 private IExpression compiledExpression
;
84 private PropertyCollection _extendedProperties
= new PropertyCollection ();
85 private int maxLength
= -1; //-1 represents no length limit
86 private string nameSpace
= String
.Empty
;
87 private int _ordinal
= -1; //-1 represents not part of a collection
88 private string prefix
= String
.Empty
;
89 private bool readOnly
;
90 private DataTable _table
;
92 private AbstractDataContainer _dataContainer
;
98 public DataColumn() : this(String
.Empty
, typeof (string), String
.Empty
, MappingType
.Element
)
102 //TODO: Ctor init vars directly
103 public DataColumn(string columnName
): this(columnName
, typeof (string), String
.Empty
, MappingType
.Element
)
107 public DataColumn(string columnName
, Type dataType
): this(columnName
, dataType
, String
.Empty
, MappingType
.Element
)
111 public DataColumn( string columnName
, Type dataType
,
112 string expr
): this(columnName
, dataType
, expr
, MappingType
.Element
)
116 public DataColumn(string columnName
, Type dataType
,
117 string expr
, MappingType type
)
119 ColumnName
= (columnName
== null ? String
.Empty
: columnName
);
121 if(dataType
== null) {
122 throw new ArgumentNullException("dataType can't be null.");
126 Expression
= expr
== null ? String
.Empty
: expr
;
127 ColumnMapping
= type
;
133 internal object this[int index
] {
135 return DataContainer
[index
];
138 if ( !(value == null && AutoIncrement
) ) {
139 DataContainer
[index
] = value;
142 if ( AutoIncrement
&& !DataContainer
.IsNull(index
) ) {
143 long value64
= Convert
.ToInt64(value);
144 UpdateAutoIncrementValue(value64
);
149 [DataCategory ("Data")]
150 [DataSysDescription ("Indicates whether null values are allowed in this column.")]
151 [DefaultValue (true)]
152 public bool AllowDBNull
158 //TODO: If we are a part of the table and this value changes
159 //we need to validate that all the existing values conform to the new setting
167 //if Value == false case
170 if (_table
.Rows
.Count
> 0)
172 bool nullsFound
= false;
173 for(int r
= 0; r
< _table
.Rows
.Count
; r
++) {
174 DataRow row
= _table
.Rows
[r
];
175 if(row
.IsNull(this)) {
182 throw new DataException("Column '" + ColumnName
+ "' has null values in it.");
183 //TODO: Validate no null values exist
184 //do we also check different versions of the row??
188 _allowDBNull
= value;
193 /// Gets or sets a value indicating whether the column automatically increments the value of the column for new rows added to the table.
196 /// If the type of this column is not Int16, Int32, or Int64 when this property is set,
197 /// the DataType property is coerced to Int32. An exception is generated if this is a computed column
198 /// (that is, the Expression property is set.) The incremented value is used only if the row's value for this column,
199 /// when added to the columns collection, is equal to the default value.
201 [DataCategory ("Data")]
202 [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.")]
203 [DefaultValue (false)]
204 [RefreshProperties (RefreshProperties
.All
)]
205 public bool AutoIncrement
208 return _autoIncrement
;
213 //Can't be true if this is a computed column
214 if (Expression
!= string.Empty
)
216 throw new ArgumentException("Can not Auto Increment a computed column.");
219 if ( DefaultValue
!= DBNull
.Value
) {
220 throw new ArgumentException("Can not set AutoIncrement while" +
221 " default value exists for this column.");
224 //If the DataType of this Column isn't an Int
226 TypeCode typeCode
= Type
.GetTypeCode(DataType
);
227 if(typeCode
!= TypeCode
.Int16
&&
228 typeCode
!= TypeCode
.Int32
&&
229 typeCode
!= TypeCode
.Int64
)
231 DataType
= typeof(Int32
);
235 _table
.Columns
.UpdateAutoIncrement(this,true);
240 _table
.Columns
.UpdateAutoIncrement(this,false);
242 _autoIncrement
= value;
246 [DataCategory ("Data")]
247 [DataSysDescription ("Indicates the starting value for an AutoIncrement column.")]
249 public long AutoIncrementSeed
252 return _autoIncrementSeed
;
255 _autoIncrementSeed
= value;
256 _nextAutoIncrementValue
= _autoIncrementSeed
;
260 [DataCategory ("Data")]
261 [DataSysDescription ("Indicates the increment used by an AutoIncrement column.")]
263 public long AutoIncrementStep
266 return _autoIncrementStep
;
269 _autoIncrementStep
= value;
273 internal void UpdateAutoIncrementValue(long value64
)
275 if (_autoIncrementStep
> 0 ) {
276 if (value64
>= _nextAutoIncrementValue
) {
277 _nextAutoIncrementValue
= value64
;
278 AutoIncrementValue ();
281 else if (value64
<= _nextAutoIncrementValue
) {
282 AutoIncrementValue ();
286 internal long AutoIncrementValue ()
288 long currentValue
= _nextAutoIncrementValue
;
289 _nextAutoIncrementValue
+= AutoIncrementStep
;
293 internal long GetAutoIncrementValue ()
295 return _nextAutoIncrementValue
;
298 [DataCategory ("Data")]
299 [DataSysDescription ("Indicates the default user-interface caption for this column.")]
300 public string Caption
310 value = String
.Empty
;
315 [DataSysDescription ("Indicates how this column persists in XML: as an attribute, element, simple content node, or nothing.")]
316 [DefaultValue (MappingType
.Element
)]
317 public virtual MappingType ColumnMapping
320 return _columnMapping
;
323 _columnMapping
= value;
327 [DataCategory ("Data")]
328 [DataSysDescription ("Indicates the name used to look up this column in the Columns collection of a DataTable.")]
329 [RefreshProperties (RefreshProperties
.All
)]
331 public string ColumnName
334 return (_columnName
== null ? String
.Empty
: _columnName
);
337 //Both are checked after the column is part of the collection
338 //TODO: Check Name duplicate
339 //TODO: check Name != null
344 [DataCategory ("Data")]
345 [DataSysDescription ("Indicates the type of data stored in this column.")]
346 [DefaultValue (typeof (string))]
347 [RefreshProperties (RefreshProperties
.All
)]
348 [TypeConverterAttribute (typeof (ColumnTypeConverter
))]
352 return DataContainer
.Type
;
355 if ( _dataContainer
!= null ) {
356 if ( value == _dataContainer
.Type
) {
360 // check if data already exists can we change the datatype
361 if ( _dataContainer
.Capacity
> 0 )
362 throw new ArgumentException("The column already has data stored.");
365 _dataContainer
= AbstractDataContainer
.CreateInstance(value, this);
367 //Check AutoIncrement status, make compatible datatype
368 if(AutoIncrement
== true) {
369 // we want to check that the datatype is supported?
370 TypeCode typeCode
= Type
.GetTypeCode(value);
372 if(typeCode
!= TypeCode
.Int16
&&
373 typeCode
!= TypeCode
.Int32
&&
374 typeCode
!= TypeCode
.Int64
) {
375 AutoIncrement
= false;
384 /// <remarks>When AutoIncrement is set to true, there can be no default value.</remarks>
385 /// <exception cref="System.InvalidCastException"></exception>
386 /// <exception cref="System.ArgumentException"></exception>
387 [DataCategory ("Data")]
388 [DataSysDescription ("Indicates the default column value used when adding new rows to the table.")]
389 [TypeConverterAttribute (typeof (System
.Data
.DefaultValueTypeConverter
))]
390 public object DefaultValue
393 return _defaultValue
;
398 throw new ArgumentException("Can not set default value while" +
399 " AutoIncrement is true on this column.");
403 if (!this._defaultValue
.Equals(value)) {
405 tmpObj
= DBNull
.Value
;
411 if ((this.DataType
!= typeof (object))&& (tmpObj
!= DBNull
.Value
)) {
413 //Casting to the new type
414 tmpObj
= Convert
.ChangeType(tmpObj
,this.DataType
);
416 catch (InvalidCastException
) {
417 throw new InvalidCastException("Default Value type is not compatible with" +
421 _defaultValue
= tmpObj
;
424 // store default value in the table if already belongs to
425 if (Table
!= null && Table
.DefaultValuesRowIndex
!= -1) {
426 DataContainer
[Table
.DefaultValuesRowIndex
] = _defaultValue
;
431 [DataCategory ("Data")]
432 [DataSysDescription ("Indicates the value that this column computes for each row based on other columns instead of taking user input.")]
434 [RefreshProperties (RefreshProperties
.All
)]
435 public string Expression
442 value = String
.Empty
;
444 if (value != String
.Empty
) {
445 Parser parser
= new Parser ();
446 compiledExpression
= parser
.Compile (value);
453 internal IExpression CompiledExpression
{
454 get { return compiledExpression; }
458 [DataCategory ("Data")]
459 [DataSysDescription ("The collection that holds custom user information.")]
460 public PropertyCollection ExtendedProperties
463 return _extendedProperties
;
467 [DataCategory ("Data")]
468 [DataSysDescription ("Indicates the maximum length of the value this column allows.")]
473 //Default == -1 no max length
477 //only applies to string columns
482 [DataCategory ("Data")]
483 [DataSysDescription ("Indicates the XML uri for elements stored in this column.")]
484 public string Namespace
491 value = String
.Empty
;
496 //Need a good way to set the Ordinal when the column is added to a columnCollection.
498 [DataCategory ("Data")]
499 [DataSysDescription ("Indicates the index of this column in the Columns collection.")]
500 [DesignerSerializationVisibility (DesignerSerializationVisibility
.Hidden
)]
504 //value is -1 if not part of a collection
509 internal void SetOrdinal(int ordinal
)
514 [DataCategory ("Data")]
515 [DataSysDescription ("Indicates the prefix used for this DataColumn in the xml representation.")]
524 value = String
.Empty
;
529 [DataCategory ("Data")]
530 [DataSysDescription ("Indicates whether this column allows changes once a row has been added to the table.")]
531 [DefaultValue (false)]
543 [DataCategory ("Data")]
544 [DataSysDescription ("Returns the DataTable to which this column belongs.")]
545 [DesignerSerializationVisibility (DesignerSerializationVisibility
.Hidden
)]
546 public DataTable Table
553 [DataCategory ("Data")]
554 [DataSysDescription ("Indicates whether this column should restrict its values in the rows of the table to be unique.")]
555 [DefaultValue (false)]
556 [DesignerSerializationVisibility (DesignerSerializationVisibility
.Hidden
)]
563 //NOTE: In .NET 1.1 the Unique property
564 //is left unchanged when it is added
565 //to a UniqueConstraint
573 if (Expression
!= null && Expression
!= String
.Empty
)
574 throw new ArgumentException("Cannot change Unique property for the expression column.");
577 UniqueConstraint uc
= new UniqueConstraint(this);
578 _table
.Constraints
.Add(uc
);
585 ConstraintCollection cc
= _table
.Constraints
;
586 //foreach (Constraint c in cc)
587 for (int i
= 0; i
< cc
.Count
; i
++)
589 Constraint c
= cc
[i
];
590 if (c
is UniqueConstraint
)
592 DataColumn
[] cols
= ((UniqueConstraint
)c
).Columns
;
594 if (cols
.Length
== 1 && cols
[0] == this)
596 if (!cc
.CanRemove(c
))
597 throw new ArgumentException("Cannot remove unique constraint '" + c
.ConstraintName
+ "'. Remove foreign key constraint first.");
611 internal AbstractDataContainer DataContainer
{
613 return _dataContainer
;
617 #endregion // Properties
623 protected internal void CheckNotAllowNull() {
627 protected void CheckUnique() {
632 /// Sets unique true whithout creating Constraint
634 internal void SetUnique()
640 internal void AssertCanAddToCollection()
642 //Check if Default Value is set and AutoInc is set
646 protected internal void CheckNotAllowNull ()
648 throw new NotImplementedException ();
652 protected void CheckUnique ()
654 throw new NotImplementedException ();
658 protected internal virtual void
659 OnPropertyChanging (PropertyChangedEventArgs pcevent
) {
663 protected internal void RaisePropertyChanging(string name
) {
667 /// Gets the Expression of the column, if one exists.
669 /// <returns>The Expression value, if the property is set;
670 /// otherwise, the ColumnName property.</returns>
671 public override string ToString()
673 if (expression
!= string.Empty
)
674 return ColumnName
+ " + " + expression
;
679 internal void SetTable(DataTable table
) {
680 if(_table
!=null) { // serves as double check while adding to a table
681 throw new ArgumentException("The column already belongs to a different table");
684 // this will get called by DataTable
685 // and DataColumnCollection
687 // if the DataColumn is marked as Unique and then
688 // added to a DataTable , then a UniqueConstraint
690 UniqueConstraint uc
= new UniqueConstraint(this);
691 _table
.Constraints
.Add(uc
);
694 // allocate space in the column data container
695 DataContainer
.Capacity
= _table
.RecordCache
.CurrentCapacity
;
697 int defaultValuesRowIndex
= _table
.DefaultValuesRowIndex
;
698 if ( defaultValuesRowIndex
!= -1) {
699 // store default value in the table
700 DataContainer
[defaultValuesRowIndex
] = _defaultValue
;
701 // Set all the values in data container to default
702 // it's cheaper that raise event on each row.
703 DataContainer
.FillValues(_table
.DefaultValuesRowIndex
);
708 // Returns true if all the same collumns are in columnSet and compareSet
709 internal static bool AreColumnSetsTheSame(DataColumn
[] columnSet
, DataColumn
[] compareSet
)
711 if (null == columnSet
&& null == compareSet
) return true;
712 if (null == columnSet
|| null == compareSet
) return false;
714 if (columnSet
.Length
!= compareSet
.Length
) return false;
716 foreach (DataColumn col
in columnSet
)
718 bool matchFound
= false;
719 foreach (DataColumn compare
in compareSet
)
726 if (! matchFound
) return false;
732 internal int CompareValues (int index1
, int index2
)
734 return DataContainer
.CompareValues(index1
, index2
);
737 #endregion // Methods