// // System.Data.DataColumn.cs // // Author: // Franklin Wise (gracenote@earthlink.net) // Christopher Podurgiel (cpodurgiel@msn.com) // Rodrigo Moya (rodrigo@ximian.com) // Daniel Morgan (danmorg@sc.rr.com) // Tim Coleman (tim@timcoleman.com) // // (C) Copyright 2002, Franklin Wise // (C) Chris Podurgiel // (C) Ximian, Inc 2002 // Copyright (C) Tim Coleman, 2002 // Copyright (C) Daniel Morgan, 2002, 2003 // // // Copyright (C) 2004 Novell, Inc (http://www.novell.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.ComponentModel; using System.Reflection; using System.Collections; using System.Data.Common; using System.Globalization; using Mono.Data.SqlExpressions; namespace System.Data { internal delegate void DelegateColumnValueChange(DataColumn column, DataRow row, object proposedValue); /// /// Summary description for DataColumn. /// [Editor ("Microsoft.VSDesigner.Data.Design.DataColumnEditor, " + Consts.AssemblyMicrosoft_VSDesigner, "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)] [ToolboxItem (false)] [DefaultProperty ("ColumnName")] [DesignTimeVisible (false)] public class DataColumn : MarshalByValueComponent { #region Events [MonoTODO] //used for constraint validation //if an exception is fired during this event the change should be canceled internal event DelegateColumnValueChange ValidateColumnValueChange; //used for FK Constraint Cascading rules internal event DelegateColumnValueChange ColumnValueChanging; internal event PropertyChangedEventHandler PropertyChanged; #endregion //Events #region Fields private bool _allowDBNull = true; private bool _autoIncrement; private long _autoIncrementSeed; private long _autoIncrementStep = 1; private long _nextAutoIncrementValue; private string _caption; private MappingType _columnMapping; private string _columnName = String.Empty; private object _defaultValue = DBNull.Value; private string _expression; private IExpression _compiledExpression; private PropertyCollection _extendedProperties = new PropertyCollection (); private int _maxLength = -1; //-1 represents no length limit private string _nameSpace; private int _ordinal = -1; //-1 represents not part of a collection private string _prefix = String.Empty; private bool _readOnly; private DataTable _table; private bool _unique; private AbstractDataContainer _dataContainer; #endregion // Fields #region Constructors public DataColumn() : this(String.Empty, typeof (string), String.Empty, MappingType.Element) { } //TODO: Ctor init vars directly public DataColumn(string columnName): this(columnName, typeof (string), String.Empty, MappingType.Element) { } public DataColumn(string columnName, Type dataType): this(columnName, dataType, String.Empty, MappingType.Element) { } public DataColumn( string columnName, Type dataType, string expr): this(columnName, dataType, expr, MappingType.Element) { } public DataColumn(string columnName, Type dataType, string expr, MappingType type) { ColumnName = (columnName == null ? String.Empty : columnName); if(dataType == null) { throw new ArgumentNullException("dataType can't be null."); } DataType = dataType; Expression = expr == null ? String.Empty : expr; ColumnMapping = type; } #endregion #region Properties internal object this[int index] { get { return DataContainer[index]; } set { if ( !(value == null && AutoIncrement) ) { try { DataContainer[index] = value; } catch(Exception e) { throw new ArgumentException(e.Message + String.Format("Couldn't store <{0}> in {1} Column. Expected type is {2}.", value, ColumnName, DataType.Name), e); } } if ( AutoIncrement && !DataContainer.IsNull(index) ) { long value64 = Convert.ToInt64(value); UpdateAutoIncrementValue(value64); } } } #if NET_2_0 DataSetDateTime _datetimeMode = DataSetDateTime.UnspecifiedLocal; [DefaultValue (DataSetDateTime.UnspecifiedLocal)] [RefreshProperties (RefreshProperties.All)] public DataSetDateTime DateTimeMode { get { return _datetimeMode; } set { if (DataType != typeof (DateTime)) throw new InvalidOperationException ("The DateTimeMode can be set only on DataColumns of type DateTime."); if (!Enum.IsDefined (typeof (DataSetDateTime), value)) throw new InvalidEnumArgumentException ("The DataSetDateTime enumeration value, " + (int)value + ", is invalid."); if (_datetimeMode == value) return; if (_table == null || _table.Rows.Count == 0) { _datetimeMode = value; return; } if ((_datetimeMode == DataSetDateTime.Unspecified || _datetimeMode == DataSetDateTime.UnspecifiedLocal) && (value == DataSetDateTime.Unspecified || value == DataSetDateTime.UnspecifiedLocal)) { _datetimeMode = value; return; } throw new InvalidOperationException ( String.Format ( "Cannot change DateTimeMode from '{0}' to '{1}' " + "once the table has data.",_datetimeMode, value)); } } #endif [DataCategory ("Data")] #if !NET_2_0 [DataSysDescription ("Indicates whether null values are allowed in this column.")] #endif [DefaultValue (true)] public bool AllowDBNull { get { return _allowDBNull; } set { //TODO: If we are a part of the table and this value changes //we need to validate that all the existing values conform to the new setting if (true == value) { _allowDBNull = true; return; } //if Value == false case if (null != _table) { if (_table.Rows.Count > 0) { bool nullsFound = false; for(int r = 0; r < _table.Rows.Count; r++) { DataRow row = _table.Rows[r]; if(row.IsNull(this)) { nullsFound = true; break; } } if (nullsFound) throw new DataException("Column '" + ColumnName + "' has null values in it."); //TODO: Validate no null values exist //do we also check different versions of the row?? } } _allowDBNull = value; } } /// /// Gets or sets a value indicating whether the column automatically increments the value of the column for new rows added to the table. /// /// /// If the type of this column is not Int16, Int32, or Int64 when this property is set, /// the DataType property is coerced to Int32. An exception is generated if this is a computed column /// (that is, the Expression property is set.) The incremented value is used only if the row's value for this column, /// when added to the columns collection, is equal to the default value. /// [DataCategory ("Data")] #if !NET_2_0 [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.")] #endif [DefaultValue (false)] [RefreshProperties (RefreshProperties.All)] public bool AutoIncrement { get { return _autoIncrement; } set { if(value == true) { //Can't be true if this is a computed column if (Expression != string.Empty) { throw new ArgumentException("Can not Auto Increment a computed column."); } if ( DefaultValue != DBNull.Value ) { throw new ArgumentException("Can not set AutoIncrement while" + " default value exists for this column."); } if(!CanAutoIncrement(DataType)) { DataType = typeof(Int32); } if (_table != null) _table.Columns.UpdateAutoIncrement(this,true); } else { if (_table != null) _table.Columns.UpdateAutoIncrement(this,false); } _autoIncrement = value; } } [DataCategory ("Data")] #if !NET_2_0 [DataSysDescription ("Indicates the starting value for an AutoIncrement column.")] #endif [DefaultValue (0)] public long AutoIncrementSeed { get { return _autoIncrementSeed; } set { _autoIncrementSeed = value; _nextAutoIncrementValue = _autoIncrementSeed; } } [DataCategory ("Data")] #if !NET_2_0 [DataSysDescription ("Indicates the increment used by an AutoIncrement column.")] #endif [DefaultValue (1)] public long AutoIncrementStep { get { return _autoIncrementStep; } set { _autoIncrementStep = value; } } internal void UpdateAutoIncrementValue(long value64) { if (_autoIncrementStep > 0 ) { if (value64 >= _nextAutoIncrementValue) { _nextAutoIncrementValue = value64; AutoIncrementValue (); } } else if (value64 <= _nextAutoIncrementValue) { AutoIncrementValue (); } } internal long AutoIncrementValue () { long currentValue = _nextAutoIncrementValue; _nextAutoIncrementValue += AutoIncrementStep; return currentValue; } internal long GetAutoIncrementValue () { return _nextAutoIncrementValue; } internal void SetDefaultValue(int index) { if (AutoIncrement) this[index] = _nextAutoIncrementValue; else DataContainer.CopyValue(Table.DefaultValuesRowIndex, index); } [DataCategory ("Data")] #if !NET_2_0 [DataSysDescription ("Indicates the default user-interface caption for this column.")] #endif public string Caption { get { if(_caption == null) return ColumnName; else return _caption; } set { if (value == null) value = String.Empty; _caption = value; } } #if !NET_2_0 [DataSysDescription ("Indicates how this column persists in XML: as an attribute, element, simple content node, or nothing.")] #endif [DefaultValue (MappingType.Element)] public virtual MappingType ColumnMapping { get { return _columnMapping; } set { _columnMapping = value; } } [DataCategory ("Data")] #if !NET_2_0 [DataSysDescription ("Indicates the name used to look up this column in the Columns collection of a DataTable.")] #endif [RefreshProperties (RefreshProperties.All)] [DefaultValue ("")] public string ColumnName { get { return _columnName; } set { if (value == null) value = String.Empty; CultureInfo info = Table != null ? Table.Locale : CultureInfo.CurrentCulture; if (String.Compare(value, _columnName, true, info) != 0) { if (Table != null) { if (value.Length == 0) throw new ArgumentException("ColumnName is required when it is part of a DataTable."); Table.Columns.RegisterName(value, this); if (_columnName.Length > 0) Table.Columns.UnregisterName(_columnName); } RaisePropertyChanging("ColumnName"); _columnName = value; if (Table != null) Table.ResetPropertyDescriptorsCache(); } else if (String.Compare(value, _columnName, false, info) != 0) { RaisePropertyChanging("ColumnName"); _columnName = value; if (Table != null) Table.ResetPropertyDescriptorsCache(); } } } [DataCategory ("Data")] #if !NET_2_0 [DataSysDescription ("Indicates the type of data stored in this column.")] #endif [DefaultValue (typeof (string))] [RefreshProperties (RefreshProperties.All)] [TypeConverterAttribute (typeof (ColumnTypeConverter))] public Type DataType { get { return DataContainer.Type; } set { if ( value == null ) return; if ( _dataContainer != null ) { if ( value == _dataContainer.Type ) return; // check if data already exists can we change the datatype if ( _dataContainer.Capacity > 0 ) throw new ArgumentException("The column already has data stored."); } if (null != GetParentRelation () || null != GetChildRelation ()) throw new InvalidConstraintException ("Cannot change datatype, " + "when column is part of a relation"); #if NET_2_0 if (_dataContainer != null && _dataContainer.Type == typeof (DateTime)) _datetimeMode = DataSetDateTime.UnspecifiedLocal; #endif _dataContainer = AbstractDataContainer.CreateInstance(value, this); //Check AutoIncrement status, make compatible datatype if(AutoIncrement == true) { // we want to check that the datatype is supported? TypeCode typeCode = Type.GetTypeCode(value); if(typeCode != TypeCode.Int16 && typeCode != TypeCode.Int32 && typeCode != TypeCode.Int64) { AutoIncrement = false; } } } } /// /// /// /// When AutoIncrement is set to true, there can be no default value. /// /// [DataCategory ("Data")] #if !NET_2_0 [DataSysDescription ("Indicates the default column value used when adding new rows to the table.")] #endif [TypeConverterAttribute (typeof (System.Data.DefaultValueTypeConverter))] public object DefaultValue { get { return _defaultValue; } set { if (AutoIncrement) { throw new ArgumentException("Can not set default value while" + " AutoIncrement is true on this column."); } object tmpObj; if (!this._defaultValue.Equals(value)) { if (value == null) { tmpObj = DBNull.Value; } else { tmpObj = value; } if ((this.DataType != typeof (object))&& (tmpObj != DBNull.Value)) { try { //Casting to the new type tmpObj= Convert.ChangeType(tmpObj,this.DataType); } catch (InvalidCastException) { throw new InvalidCastException("Default Value type is not compatible with" + " column type."); } } _defaultValue = tmpObj; } // store default value in the table if already belongs to if (Table != null && Table.DefaultValuesRowIndex != -1) { DataContainer[Table.DefaultValuesRowIndex] = _defaultValue; } } } [DataCategory ("Data")] #if !NET_2_0 [DataSysDescription ("Indicates the value that this column computes for each row based on other columns instead of taking user input.")] #endif [DefaultValue ("")] [RefreshProperties (RefreshProperties.All)] public string Expression { get { return _expression; } set { if (value == null) value = String.Empty; if (value != String.Empty) { if (AutoIncrement || Unique) throw new ArgumentException("Cannot create an expression on a column that has AutoIncrement or Unique."); if (Table != null) { for (int i = 0; i < Table.Constraints.Count; i++) { if (Table.Constraints[i].IsColumnContained(this)) throw new ArgumentException(String.Format("Cannot set Expression property on column {0}, because it is a part of a constraint.", ColumnName)); } } Parser parser = new Parser (); IExpression compiledExpression = parser.Compile (value); if (Table != null) { if (compiledExpression.DependsOn(this)) throw new ArgumentException("Cannot set Expression property due to circular reference in the expression."); // Check if expression is ok if (Table.Rows.Count == 0) compiledExpression.Eval (Table.NewRow()); else compiledExpression.Eval (Table.Rows[0]); } ReadOnly = true; _compiledExpression = compiledExpression; } else { _compiledExpression = null; if (Table != null) { int defaultValuesRowIndex = Table.DefaultValuesRowIndex; if ( defaultValuesRowIndex != -1) DataContainer.FillValues(defaultValuesRowIndex); } } _expression = value; } } internal IExpression CompiledExpression { get { return _compiledExpression; } } [Browsable (false)] [DataCategory ("Data")] #if !NET_2_0 [DataSysDescription ("The collection that holds custom user information.")] #endif public PropertyCollection ExtendedProperties { get { return _extendedProperties; } #if NET_2_0 internal set { _extendedProperties = value; } #endif } [DataCategory ("Data")] #if !NET_2_0 [DataSysDescription ("Indicates the maximum length of the value this column allows. ")] #endif [DefaultValue (-1)] public int MaxLength { get { //Default == -1 no max length return _maxLength; } set { if (value >= 0 && _columnMapping == MappingType.SimpleContent) throw new ArgumentException (String.Format ("Cannot set MaxLength property on '{0}' column which is mapped to SimpleContent.", ColumnName)); //only applies to string columns _maxLength = value; } } [DataCategory ("Data")] #if !NET_2_0 [DataSysDescription ("Indicates the XML uri for elements or attributes stored in this column.")] #endif public string Namespace { get { if (_nameSpace != null) { return _nameSpace; } if ((Table != null) && (_columnMapping != MappingType.Attribute)) { return Table.Namespace; } return String.Empty; } set { _nameSpace = value; } } //Need a good way to set the Ordinal when the column is added to a columnCollection. [Browsable (false)] [DataCategory ("Data")] #if !NET_2_0 [DataSysDescription ("Indicates the index of this column in the Columns collection.")] #endif [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] public int Ordinal { get { return _ordinal; } #if NET_2_0 internal set { _ordinal = value; } #endif } #if NET_2_0 public void SetOrdinal (int ordinal) { if (_ordinal == -1) throw new ArgumentException ("Column must belong to a table."); _table.Columns.MoveColumn (_ordinal, ordinal); _ordinal = ordinal; } #else internal void SetOrdinal(int ordinal) { _ordinal = ordinal; } #endif [DataCategory ("Data")] #if !NET_2_0 [DataSysDescription ("Indicates the Prefix used for this DataColumn in xml representation.")] #endif [DefaultValue ("")] public string Prefix { get { return _prefix; } set { if (value == null) value = String.Empty; _prefix = value; } } [DataCategory ("Data")] #if !NET_2_0 [DataSysDescription ("Indicates whether this column allows changes once a row has been added to the table.")] #endif [DefaultValue (false)] public bool ReadOnly { get { return _readOnly; } set { _readOnly = value; } } [Browsable (false)] [DataCategory ("Data")] #if !NET_2_0 [DataSysDescription ("Returns the DataTable to which this column belongs.")] #endif [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] public DataTable Table { get { return _table; } } [DataCategory ("Data")] #if !NET_2_0 [DataSysDescription ("Indicates whether this column should restrict its values in the rows of the table to be unique.")] #endif [DefaultValue (false)] [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] public bool Unique { get { return _unique; } set { if (_unique == value) return; // Set the property value, so that when adding/removing the constraint // we dont run into recursive issues. _unique = value; if (_table == null) return; try { if (value) { if (Expression != null && Expression != String.Empty) throw new ArgumentException("Cannot change Unique property for the expression column."); _table.Constraints.Add(null, this, false); } else { UniqueConstraint uc = UniqueConstraint.GetUniqueConstraintForColumnSet (_table.Constraints, new DataColumn[] {this}); _table.Constraints.Remove (uc); } } catch (Exception e) { _unique = !value; throw e; } } } internal AbstractDataContainer DataContainer { get { return _dataContainer; } } internal static bool CanAutoIncrement(Type type) { switch (Type.GetTypeCode(type)) { case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: case TypeCode.Decimal: return true; } return false; } #endregion // Properties #region Methods /* ?? [MonoTODO] protected internal void CheckNotAllowNull() { } [MonoTODO] protected void CheckUnique() { } */ [MonoTODO] internal DataColumn Clone() { DataColumn copy = new DataColumn (); // Copy all the properties of column copy._allowDBNull = _allowDBNull; copy._autoIncrement = _autoIncrement; copy._autoIncrementSeed = _autoIncrementSeed; copy._autoIncrementStep = _autoIncrementStep; copy._caption = _caption; copy._columnMapping = _columnMapping; copy._columnName = _columnName; //Copy.Container copy.DataType = DataType; copy._defaultValue = _defaultValue; copy._expression = _expression; //Copy.ExtendedProperties copy._maxLength = _maxLength; copy._nameSpace = _nameSpace; copy._prefix = _prefix; copy._readOnly = _readOnly; //Copy.Site //we do not copy the unique value - it will be copyied when copying the constraints. //Copy.Unique = Column.Unique; #if NET_2_0 if (DataType == typeof (DateTime)) copy.DateTimeMode = _datetimeMode; #endif return copy; } /// /// Sets unique true whithout creating Constraint /// internal void SetUnique() { _unique = true; } [MonoTODO] internal void AssertCanAddToCollection() { //Check if Default Value is set and AutoInc is set } [MonoTODO] protected internal void CheckNotAllowNull () { throw new NotImplementedException (); } [MonoTODO] protected void CheckUnique () { throw new NotImplementedException (); } protected internal virtual void OnPropertyChanging (PropertyChangedEventArgs pcevent) { if (PropertyChanged != null) PropertyChanged (this, pcevent); } protected internal void RaisePropertyChanging(string name) { PropertyChangedEventArgs e = new PropertyChangedEventArgs (name); OnPropertyChanging (e); } /// /// Gets the Expression of the column, if one exists. /// /// The Expression value, if the property is set; /// otherwise, the ColumnName property. public override string ToString() { if (_expression != string.Empty) return ColumnName + " + " + _expression; return ColumnName; } internal void SetTable(DataTable table) { if(_table!=null) { // serves as double check while adding to a table throw new ArgumentException("The column already belongs to a different table"); } _table = table; // this will get called by DataTable // and DataColumnCollection if(_unique) { // if the DataColumn is marked as Unique and then // added to a DataTable , then a UniqueConstraint // should be created UniqueConstraint uc = new UniqueConstraint(this); _table.Constraints.Add(uc); } // allocate space in the column data container DataContainer.Capacity = _table.RecordCache.CurrentCapacity; int defaultValuesRowIndex = _table.DefaultValuesRowIndex; if ( defaultValuesRowIndex != -1) { // store default value in the table DataContainer[defaultValuesRowIndex] = _defaultValue; // Set all the values in data container to default // it's cheaper that raise event on each row. DataContainer.FillValues(defaultValuesRowIndex); } } // Returns true if all the same collumns are in columnSet and compareSet internal static bool AreColumnSetsTheSame(DataColumn[] columnSet, DataColumn[] compareSet) { if (null == columnSet && null == compareSet) { return true; } if (null == columnSet || null == compareSet) { return false; } if (columnSet.Length != compareSet.Length) { return false; } foreach (DataColumn col in columnSet) { bool matchFound = false; foreach (DataColumn compare in compareSet) { if (col == compare) { matchFound = true; } } if (! matchFound) { return false; } } return true; } internal int CompareValues (int index1, int index2) { return DataContainer.CompareValues(index1, index2); } /// /// Returns the data relation, which contains this column. /// This searches in current table's parent relations. /// /// /// DataRelation if found otherwise null. /// private DataRelation GetParentRelation () { if (_table == null) return null; foreach (DataRelation rel in _table.ParentRelations) if (rel.Contains (this)) return rel; return null; } /// /// Returns the data relation, which contains this column. /// This searches in current table's child relations. /// /// /// DataRelation if found otherwise null. /// private DataRelation GetChildRelation () { if (_table == null) return null; foreach (DataRelation rel in _table.ChildRelations) if (rel.Contains (this)) return rel; return null; } internal void ResetColumnInfo () { _ordinal = -1; _table = null; if (_compiledExpression != null) _compiledExpression.ResetExpression (); } internal bool DataTypeMatches (DataColumn col) { if (DataType != col.DataType) return false; #if NET_2_0 if (DataType != typeof (DateTime)) return true; if (DateTimeMode == col.DateTimeMode) return true; if (DateTimeMode == DataSetDateTime.Local || DateTimeMode == DataSetDateTime.Utc) return false; if (col.DateTimeMode == DataSetDateTime.Local || col.DateTimeMode == DataSetDateTime.Utc) return false; #endif return true; } #endregion // Methods } }