2 // System.Data.DataColumn.cs
\r
5 // Franklin Wise (gracenote@earthlink.net)
\r
6 // Christopher Podurgiel (cpodurgiel@msn.com)
\r
7 // Rodrigo Moya (rodrigo@ximian.com)
\r
8 // Daniel Morgan (danmorg@sc.rr.com)
\r
9 // Tim Coleman (tim@timcoleman.com)
\r
11 // (C) Copyright 2002, Franklin Wise
\r
12 // (C) Chris Podurgiel
\r
13 // (C) Ximian, Inc 2002
\r
14 // Copyright (C) Tim Coleman, 2002
\r
15 // Copyright (C) Daniel Morgan, 2002, 2003
\r
19 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
\r
21 // Permission is hereby granted, free of charge, to any person obtaining
\r
22 // a copy of this software and associated documentation files (the
\r
23 // "Software"), to deal in the Software without restriction, including
\r
24 // without limitation the rights to use, copy, modify, merge, publish,
\r
25 // distribute, sublicense, and/or sell copies of the Software, and to
\r
26 // permit persons to whom the Software is furnished to do so, subject to
\r
27 // the following conditions:
\r
29 // The above copyright notice and this permission notice shall be
\r
30 // included in all copies or substantial portions of the Software.
\r
32 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
\r
33 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
\r
34 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
\r
35 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
\r
36 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
\r
37 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
\r
38 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\r
42 using System.ComponentModel;
\r
43 using System.Reflection;
\r
44 using System.Collections;
\r
45 using System.Data.Common;
\r
46 using System.Globalization;
\r
47 using Mono.Data.SqlExpressions;
\r
49 namespace System.Data {
\r
50 internal delegate void DelegateColumnValueChange(DataColumn column, DataRow row, object proposedValue);
\r
53 /// Summary description for DataColumn.
\r
56 [Editor ("Microsoft.VSDesigner.Data.Design.DataColumnEditor, " + Consts.AssemblyMicrosoft_VSDesigner,
\r
57 "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
\r
58 [ToolboxItem (false)]
\r
59 [DefaultProperty ("ColumnName")]
\r
60 [DesignTimeVisible (false)]
\r
61 public class DataColumn : MarshalByValueComponent
\r
65 //used for constraint validation
\r
66 //if an exception is fired during this event the change should be canceled
\r
67 internal event DelegateColumnValueChange ValidateColumnValueChange;
\r
69 //used for FK Constraint Cascading rules
\r
70 internal event DelegateColumnValueChange ColumnValueChanging;
\r
75 private bool _allowDBNull = true;
\r
76 private bool _autoIncrement;
\r
77 private long _autoIncrementSeed;
\r
78 private long _autoIncrementStep = 1;
\r
79 private long _nextAutoIncrementValue;
\r
80 private string _caption;
\r
81 private MappingType _columnMapping;
\r
82 private string _columnName = String.Empty;
\r
83 private object _defaultValue = DBNull.Value;
\r
84 private string _expression;
\r
85 private IExpression _compiledExpression;
\r
86 private PropertyCollection _extendedProperties = new PropertyCollection ();
\r
87 private int _maxLength = -1; //-1 represents no length limit
\r
88 private string _nameSpace;
\r
89 private int _ordinal = -1; //-1 represents not part of a collection
\r
90 private string _prefix = String.Empty;
\r
91 private bool _readOnly;
\r
92 private DataTable _table;
\r
93 private bool _unique;
\r
94 private AbstractDataContainer _dataContainer;
\r
96 #endregion // Fields
\r
98 #region Constructors
\r
100 public DataColumn() : this(String.Empty, typeof (string), String.Empty, MappingType.Element)
\r
104 //TODO: Ctor init vars directly
\r
105 public DataColumn(string columnName): this(columnName, typeof (string), String.Empty, MappingType.Element)
\r
109 public DataColumn(string columnName, Type dataType): this(columnName, dataType, String.Empty, MappingType.Element)
\r
113 public DataColumn( string columnName, Type dataType,
\r
114 string expr): this(columnName, dataType, expr, MappingType.Element)
\r
118 public DataColumn(string columnName, Type dataType,
\r
119 string expr, MappingType type)
\r
121 ColumnName = (columnName == null ? String.Empty : columnName);
\r
123 if(dataType == null) {
\r
124 throw new ArgumentNullException("dataType can't be null.");
\r
127 DataType = dataType;
\r
128 Expression = expr == null ? String.Empty : expr;
\r
129 ColumnMapping = type;
\r
135 internal object this[int index] {
\r
137 return DataContainer[index];
\r
140 if ( !(value == null && AutoIncrement) ) {
\r
142 DataContainer[index] = value;
\r
144 catch(Exception e) {
\r
145 throw new ArgumentException(e.Message +
\r
146 String.Format("Couldn't store <{0}> in {1} Column. Expected type is {2}.",
\r
147 value, ColumnName, DataType.Name), e);
\r
151 if ( AutoIncrement && !DataContainer.IsNull(index) ) {
\r
152 long value64 = Convert.ToInt64(value);
\r
153 UpdateAutoIncrementValue(value64);
\r
158 [DataCategory ("Data")]
\r
159 [DataSysDescription ("Indicates whether null values are allowed in this column.")]
\r
160 [DefaultValue (true)]
\r
161 public bool AllowDBNull
\r
164 return _allowDBNull;
\r
167 //TODO: If we are a part of the table and this value changes
\r
168 //we need to validate that all the existing values conform to the new setting
\r
172 _allowDBNull = true;
\r
176 //if Value == false case
\r
177 if (null != _table)
\r
179 if (_table.Rows.Count > 0)
\r
181 bool nullsFound = false;
\r
182 for(int r = 0; r < _table.Rows.Count; r++) {
\r
183 DataRow row = _table.Rows[r];
\r
184 if(row.IsNull(this)) {
\r
191 throw new DataException("Column '" + ColumnName + "' has null values in it.");
\r
192 //TODO: Validate no null values exist
\r
193 //do we also check different versions of the row??
\r
197 _allowDBNull = value;
\r
202 /// Gets or sets a value indicating whether the column automatically increments the value of the column for new rows added to the table.
\r
205 /// If the type of this column is not Int16, Int32, or Int64 when this property is set,
\r
206 /// the DataType property is coerced to Int32. An exception is generated if this is a computed column
\r
207 /// (that is, the Expression property is set.) The incremented value is used only if the row's value for this column,
\r
208 /// when added to the columns collection, is equal to the default value.
\r
210 [DataCategory ("Data")]
\r
211 [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.")]
\r
212 [DefaultValue (false)]
\r
213 [RefreshProperties (RefreshProperties.All)]
\r
214 public bool AutoIncrement
\r
217 return _autoIncrement;
\r
222 //Can't be true if this is a computed column
\r
223 if (Expression != string.Empty)
\r
225 throw new ArgumentException("Can not Auto Increment a computed column.");
\r
228 if ( DefaultValue != DBNull.Value ) {
\r
229 throw new ArgumentException("Can not set AutoIncrement while" +
\r
230 " default value exists for this column.");
\r
233 if(!CanAutoIncrement(DataType))
\r
235 DataType = typeof(Int32);
\r
238 if (_table != null)
\r
239 _table.Columns.UpdateAutoIncrement(this,true);
\r
243 if (_table != null)
\r
244 _table.Columns.UpdateAutoIncrement(this,false);
\r
246 _autoIncrement = value;
\r
250 [DataCategory ("Data")]
\r
251 [DataSysDescription ("Indicates the starting value for an AutoIncrement column.")]
\r
253 public long AutoIncrementSeed
\r
256 return _autoIncrementSeed;
\r
259 _autoIncrementSeed = value;
\r
260 _nextAutoIncrementValue = _autoIncrementSeed;
\r
264 [DataCategory ("Data")]
\r
265 [DataSysDescription ("Indicates the increment used by an AutoIncrement column.")]
\r
267 public long AutoIncrementStep
\r
270 return _autoIncrementStep;
\r
273 _autoIncrementStep = value;
\r
277 internal void UpdateAutoIncrementValue(long value64)
\r
279 if (_autoIncrementStep > 0 ) {
\r
280 if (value64 >= _nextAutoIncrementValue) {
\r
281 _nextAutoIncrementValue = value64;
\r
282 AutoIncrementValue ();
\r
285 else if (value64 <= _nextAutoIncrementValue) {
\r
286 AutoIncrementValue ();
\r
290 internal long AutoIncrementValue ()
\r
292 long currentValue = _nextAutoIncrementValue;
\r
293 _nextAutoIncrementValue += AutoIncrementStep;
\r
294 return currentValue;
\r
297 internal long GetAutoIncrementValue ()
\r
299 return _nextAutoIncrementValue;
\r
302 internal void SetDefaultValue(int index) {
\r
304 this[index] = _nextAutoIncrementValue;
\r
306 DataContainer.CopyValue(Table.DefaultValuesRowIndex, index);
\r
309 [DataCategory ("Data")]
\r
310 [DataSysDescription ("Indicates the default user-interface caption for this column.")]
\r
311 public string Caption
\r
314 if(_caption == null)
\r
321 value = String.Empty;
\r
326 [DataSysDescription ("Indicates how this column persists in XML: as an attribute, element, simple content node, or nothing.")]
\r
327 [DefaultValue (MappingType.Element)]
\r
328 public virtual MappingType ColumnMapping
\r
331 return _columnMapping;
\r
334 _columnMapping = value;
\r
338 [DataCategory ("Data")]
\r
339 [DataSysDescription ("Indicates the name used to look up this column in the Columns collection of a DataTable.")]
\r
340 [RefreshProperties (RefreshProperties.All)]
\r
341 [DefaultValue ("")]
\r
342 public string ColumnName
\r
345 return _columnName;
\r
349 value = String.Empty;
\r
351 CultureInfo info = Table != null ? Table.Locale : CultureInfo.CurrentCulture;
\r
352 if (String.Compare(value, _columnName, true, info) != 0) {
\r
353 if (Table != null) {
\r
354 if (value.Length == 0)
\r
355 throw new ArgumentException("ColumnName is required when it is part of a DataTable.");
\r
357 Table.Columns.RegisterName(value, this);
\r
358 if (_columnName.Length > 0)
\r
359 Table.Columns.UnregisterName(_columnName);
\r
362 RaisePropertyChanging("ColumnName");
\r
363 _columnName = value;
\r
366 Table.ResetPropertyDescriptorsCache();
\r
368 else if (String.Compare(value, _columnName, false, info) != 0) {
\r
369 RaisePropertyChanging("ColumnName");
\r
370 _columnName = value;
\r
373 Table.ResetPropertyDescriptorsCache();
\r
378 [DataCategory ("Data")]
\r
379 [DataSysDescription ("Indicates the type of data stored in this column.")]
\r
380 [DefaultValue (typeof (string))]
\r
381 [RefreshProperties (RefreshProperties.All)]
\r
382 [TypeConverterAttribute (typeof (ColumnTypeConverter))]
\r
383 public Type DataType
\r
386 return DataContainer.Type;
\r
390 if ( value == null )
\r
393 if ( _dataContainer != null ) {
\r
394 if ( value == _dataContainer.Type )
\r
397 // check if data already exists can we change the datatype
\r
398 if ( _dataContainer.Capacity > 0 )
\r
399 throw new ArgumentException("The column already has data stored.");
\r
402 if (null != GetParentRelation () || null != GetChildRelation ())
\r
403 throw new InvalidConstraintException ("Cannot change datatype, " +
\r
404 "when column is part of a relation");
\r
406 _dataContainer = AbstractDataContainer.CreateInstance(value, this);
\r
408 //Check AutoIncrement status, make compatible datatype
\r
409 if(AutoIncrement == true) {
\r
410 // we want to check that the datatype is supported?
\r
411 TypeCode typeCode = Type.GetTypeCode(value);
\r
413 if(typeCode != TypeCode.Int16 &&
\r
414 typeCode != TypeCode.Int32 &&
\r
415 typeCode != TypeCode.Int64) {
\r
416 AutoIncrement = false;
\r
425 /// <remarks>When AutoIncrement is set to true, there can be no default value.</remarks>
\r
426 /// <exception cref="System.InvalidCastException"></exception>
\r
427 /// <exception cref="System.ArgumentException"></exception>
\r
428 [DataCategory ("Data")]
\r
429 [DataSysDescription ("Indicates the default column value used when adding new rows to the table.")]
\r
430 [TypeConverterAttribute (typeof (System.Data.DefaultValueTypeConverter))]
\r
431 public object DefaultValue
\r
434 return _defaultValue;
\r
438 if (AutoIncrement) {
\r
439 throw new ArgumentException("Can not set default value while" +
\r
440 " AutoIncrement is true on this column.");
\r
444 if (!this._defaultValue.Equals(value)) {
\r
445 if (value == null) {
\r
446 tmpObj = DBNull.Value;
\r
452 if ((this.DataType != typeof (object))&& (tmpObj != DBNull.Value)) {
\r
454 //Casting to the new type
\r
455 tmpObj= Convert.ChangeType(tmpObj,this.DataType);
\r
457 catch (InvalidCastException) {
\r
458 throw new InvalidCastException("Default Value type is not compatible with" +
\r
462 _defaultValue = tmpObj;
\r
465 // store default value in the table if already belongs to
\r
466 if (Table != null && Table.DefaultValuesRowIndex != -1) {
\r
467 DataContainer[Table.DefaultValuesRowIndex] = _defaultValue;
\r
472 [DataCategory ("Data")]
\r
473 [DataSysDescription ("Indicates the value that this column computes for each row based on other columns instead of taking user input.")]
\r
474 [DefaultValue ("")]
\r
475 [RefreshProperties (RefreshProperties.All)]
\r
476 public string Expression
\r
479 return _expression;
\r
483 value = String.Empty;
\r
485 if (value != String.Empty)
\r
488 if (AutoIncrement || Unique)
\r
489 throw new ArgumentException("Cannot create an expression on a column that has AutoIncrement or Unique.");
\r
493 for (int i = 0; i < Table.Constraints.Count; i++)
\r
495 if (Table.Constraints[i].IsColumnContained(this))
\r
496 throw new ArgumentException(String.Format("Cannot set Expression property on column {0}, because it is a part of a constraint.", ColumnName));
\r
500 Parser parser = new Parser ();
\r
501 IExpression compiledExpression = parser.Compile (value);
\r
505 if (compiledExpression.DependsOn(this))
\r
506 throw new ArgumentException("Cannot set Expression property due to circular reference in the expression.");
\r
510 _compiledExpression = compiledExpression;
\r
514 _compiledExpression = null;
\r
517 int defaultValuesRowIndex = Table.DefaultValuesRowIndex;
\r
518 if ( defaultValuesRowIndex != -1)
\r
519 DataContainer.FillValues(defaultValuesRowIndex);
\r
522 _expression = value;
\r
526 internal IExpression CompiledExpression {
\r
527 get { return _compiledExpression; }
\r
530 [Browsable (false)]
\r
531 [DataCategory ("Data")]
\r
532 [DataSysDescription ("The collection that holds custom user information.")]
\r
533 public PropertyCollection ExtendedProperties
\r
536 return _extendedProperties;
\r
540 [DataCategory ("Data")]
\r
541 [DataSysDescription ("Indicates the maximum length of the value this column allows. ")]
\r
542 [DefaultValue (-1)]
\r
543 public int MaxLength
\r
546 //Default == -1 no max length
\r
551 _columnMapping == MappingType.SimpleContent)
\r
552 throw new ArgumentException (String.Format ("Cannot set MaxLength property on '{0}' column which is mapped to SimpleContent.", ColumnName));
\r
553 //only applies to string columns
\r
554 _maxLength = value;
\r
558 [DataCategory ("Data")]
\r
559 [DataSysDescription ("Indicates the XML uri for elements or attributes stored in this column.")]
\r
560 public string Namespace
\r
563 if (_nameSpace != null)
\r
567 if ((Table != null) && (_columnMapping != MappingType.Attribute))
\r
569 return Table.Namespace;
\r
571 return String.Empty;
\r
574 _nameSpace = value;
\r
578 //Need a good way to set the Ordinal when the column is added to a columnCollection.
\r
579 [Browsable (false)]
\r
580 [DataCategory ("Data")]
\r
581 [DataSysDescription ("Indicates the index of this column in the Columns collection.")]
\r
582 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
\r
586 //value is -1 if not part of a collection
\r
591 internal void SetOrdinal(int ordinal)
\r
593 _ordinal = ordinal;
\r
596 [DataCategory ("Data")]
\r
597 [DataSysDescription ("Indicates the Prefix used for this DataColumn in xml representation.")]
\r
598 [DefaultValue ("")]
\r
599 public string Prefix
\r
606 value = String.Empty;
\r
611 [DataCategory ("Data")]
\r
612 [DataSysDescription ("Indicates whether this column allows changes once a row has been added to the table.")]
\r
613 [DefaultValue (false)]
\r
614 public bool ReadOnly
\r
624 [Browsable (false)]
\r
625 [DataCategory ("Data")]
\r
626 [DataSysDescription ("Returns the DataTable to which this column belongs.")]
\r
627 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
\r
628 public DataTable Table
\r
635 [DataCategory ("Data")]
\r
636 [DataSysDescription ("Indicates whether this column should restrict its values in the rows of the table to be unique.")]
\r
637 [DefaultValue (false)]
\r
638 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
\r
639 public bool Unique
\r
645 //NOTE: In .NET 1.1 the Unique property
\r
646 //is left unchanged when it is added
\r
647 //to a UniqueConstraint
\r
649 if(_unique != value)
\r
655 if (Expression != null && Expression != String.Empty)
\r
656 throw new ArgumentException("Cannot change Unique property for the expression column.");
\r
657 if( _table != null )
\r
659 UniqueConstraint uc = new UniqueConstraint(this);
\r
660 _table.Constraints.Add(uc);
\r
665 if( _table != null )
\r
667 ConstraintCollection cc = _table.Constraints;
\r
668 //foreach (Constraint c in cc)
\r
669 for (int i = 0; i < cc.Count; i++)
\r
671 Constraint c = cc[i];
\r
672 if (c is UniqueConstraint)
\r
674 DataColumn[] cols = ((UniqueConstraint)c).Columns;
\r
676 if (cols.Length == 1 && cols[0] == this)
\r
690 internal AbstractDataContainer DataContainer {
\r
692 return _dataContainer;
\r
696 internal static bool CanAutoIncrement(Type type) {
\r
697 switch (Type.GetTypeCode(type)) {
\r
698 case TypeCode.Int16:
\r
699 case TypeCode.Int32:
\r
700 case TypeCode.Int64:
\r
701 case TypeCode.Decimal:
\r
708 #endregion // Properties
\r
714 protected internal void CheckNotAllowNull() {
\r
718 protected void CheckUnique() {
\r
722 internal DataColumn Clone() {
\r
723 DataColumn copy = new DataColumn ();
\r
725 // Copy all the properties of column
\r
726 copy._allowDBNull = _allowDBNull;
\r
727 copy._autoIncrement = _autoIncrement;
\r
728 copy._autoIncrementSeed = _autoIncrementSeed;
\r
729 copy._autoIncrementStep = _autoIncrementStep;
\r
730 copy._caption = _caption;
\r
731 copy._columnMapping = _columnMapping;
\r
732 copy._columnName = _columnName;
\r
734 copy.DataType = DataType;
\r
735 copy._defaultValue = _defaultValue;
\r
736 copy._expression = _expression;
\r
737 //Copy.ExtendedProperties
\r
738 copy._maxLength = _maxLength;
\r
739 copy._nameSpace = _nameSpace;
\r
740 copy._prefix = _prefix;
\r
741 copy._readOnly = _readOnly;
\r
743 //we do not copy the unique value - it will be copyied when copying the constraints.
\r
744 //Copy.Unique = Column.Unique;
\r
750 /// Sets unique true whithout creating Constraint
\r
752 internal void SetUnique()
\r
758 internal void AssertCanAddToCollection()
\r
760 //Check if Default Value is set and AutoInc is set
\r
764 protected internal void CheckNotAllowNull ()
\r
766 throw new NotImplementedException ();
\r
770 protected void CheckUnique ()
\r
772 throw new NotImplementedException ();
\r
776 protected internal virtual void
\r
777 OnPropertyChanging (PropertyChangedEventArgs pcevent) {
\r
781 protected internal void RaisePropertyChanging(string name) {
\r
785 /// Gets the Expression of the column, if one exists.
\r
787 /// <returns>The Expression value, if the property is set;
\r
788 /// otherwise, the ColumnName property.</returns>
\r
789 public override string ToString()
\r
791 if (_expression != string.Empty)
\r
792 return ColumnName + " + " + _expression;
\r
797 internal void SetTable(DataTable table) {
\r
798 if(_table!=null) { // serves as double check while adding to a table
\r
799 throw new ArgumentException("The column already belongs to a different table");
\r
802 // this will get called by DataTable
\r
803 // and DataColumnCollection
\r
805 // if the DataColumn is marked as Unique and then
\r
806 // added to a DataTable , then a UniqueConstraint
\r
807 // should be created
\r
808 UniqueConstraint uc = new UniqueConstraint(this);
\r
809 _table.Constraints.Add(uc);
\r
812 // allocate space in the column data container
\r
813 DataContainer.Capacity = _table.RecordCache.CurrentCapacity;
\r
815 int defaultValuesRowIndex = _table.DefaultValuesRowIndex;
\r
816 if ( defaultValuesRowIndex != -1) {
\r
817 // store default value in the table
\r
818 DataContainer[defaultValuesRowIndex] = _defaultValue;
\r
819 // Set all the values in data container to default
\r
820 // it's cheaper that raise event on each row.
\r
821 DataContainer.FillValues(defaultValuesRowIndex);
\r
825 // Returns true if all the same collumns are in columnSet and compareSet
\r
826 internal static bool AreColumnSetsTheSame(DataColumn[] columnSet, DataColumn[] compareSet)
\r
828 if (null == columnSet && null == compareSet) {
\r
832 if (null == columnSet || null == compareSet) {
\r
836 if (columnSet.Length != compareSet.Length) {
\r
840 foreach (DataColumn col in columnSet) {
\r
841 bool matchFound = false;
\r
842 foreach (DataColumn compare in compareSet) {
\r
843 if (col == compare) {
\r
844 matchFound = true;
\r
847 if (! matchFound) {
\r
855 internal int CompareValues (int index1, int index2)
\r
857 return DataContainer.CompareValues(index1, index2);
\r
861 /// Returns the data relation, which contains this column.
\r
862 /// This searches in current table's parent relations.
\r
865 /// DataRelation if found otherwise null.
\r
867 private DataRelation GetParentRelation ()
\r
869 if (_table == null)
\r
871 foreach (DataRelation rel in _table.ParentRelations)
\r
872 if (rel.Contains (this))
\r
879 /// Returns the data relation, which contains this column.
\r
880 /// This searches in current table's child relations.
\r
883 /// DataRelation if found otherwise null.
\r
885 private DataRelation GetChildRelation ()
\r
887 if (_table == null)
\r
889 foreach (DataRelation rel in _table.ChildRelations)
\r
890 if (rel.Contains (this))
\r
896 #endregion // Methods
\r