// // 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 // using System; using System.ComponentModel; using System.Reflection; using System.Collections; using System.Data.Common; namespace System.Data { internal delegate void DelegateColumnValueChange(DataColumn column, DataRow row, object proposedValue); /// /// Summary description for DataColumn. /// [Editor] [ToolboxItem (false)] [DefaultMember ("Item")] [DefaultProperty ("ColumnName")] [DesignTimeVisible (false)] [TypeConverterAttribute (typeof (ComponentConverter))] 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; #endregion //Events #region Fields private bool _allowDBNull = true; private bool _autoIncrement; private long _autoIncrementSeed; private long _autoIncrementStep = 1; private long _nextAutoIncrementValue; private bool dataHasBeenSet; private string _caption; private MappingType _columnMapping; private string _columnName; private Type _dataType; private object _defaultValue = DBNull.Value; private string expression; 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 = ""; private bool readOnly; private DataTable _table; private bool unique; #endregion // Fields #region Constructors public DataColumn() : this("", typeof (string), "", MappingType.Element) { } //TODO: Ctor init vars directly public DataColumn(string columnName): this(columnName, typeof (string), "", MappingType.Element) { } public DataColumn(string columnName, Type dataType): this(columnName, dataType, "", 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 ? "" : columnName); if(dataType == null) { throw new ArgumentNullException("dataType can't be null."); } DataType = dataType; Expression = expr == null ? "" : expr; ColumnMapping = type; } #endregion #region Properties [DataCategory ("Data")] [DataSysDescription ("Indicates whether null values are allowed in this column.")] [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")] [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.")] [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 the DataType of this Column isn't an Int //Make it an int TypeCode typeCode = Type.GetTypeCode(_dataType); if(typeCode != TypeCode.Int16 && typeCode != TypeCode.Int32 && typeCode != TypeCode.Int64) { _dataType = typeof(Int32); } } _autoIncrement = value; } } [DataCategory ("Data")] [DataSysDescription ("Indicates the starting value for an AutoIncrement column.")] [DefaultValue (0)] public long AutoIncrementSeed { get { return _autoIncrementSeed; } set { _autoIncrementSeed = value; _nextAutoIncrementValue = _autoIncrementSeed; } } [DataCategory ("Data")] [DataSysDescription ("Indicates the increment used by an AutoIncrement column.")] [DefaultValue (1)] public long AutoIncrementStep { get { return _autoIncrementStep; } set { _autoIncrementStep = value; } } internal void UpdateAutoIncrementValue (long value) { if(value > _nextAutoIncrementValue) { _nextAutoIncrementValue = value; AutoIncrementValue (); } } internal long AutoIncrementValue () { long currentValue = _nextAutoIncrementValue; _nextAutoIncrementValue += AutoIncrementStep; return currentValue; } internal long GetAutoIncrementValue () { return _nextAutoIncrementValue; } internal bool DataHasBeenSet { get { return dataHasBeenSet; } set { dataHasBeenSet = value; } } [DataCategory ("Data")] [DataSysDescription ("Indicates the default user-interface caption for this column.")] public string Caption { get { if(_caption == null) return ColumnName; else return _caption; } set { _caption = value; } } [DataSysDescription ("Indicates how this column persists in XML: as an attribute, element, simple content node, or nothing.")] [DefaultValue (MappingType.Element)] public virtual MappingType ColumnMapping { get { return _columnMapping; } set { _columnMapping = value; } } [DataCategory ("Data")] [DataSysDescription ("Indicates the name used to look up this column in the Columns collection of a DataTable.")] [RefreshProperties (RefreshProperties.All)] [DefaultValue ("")] public string ColumnName { get { return "" + _columnName; } set { //Both are checked after the column is part of the collection //TODO: Check Name duplicate //TODO: check Name != null _columnName = value; } } [DataCategory ("Data")] [DataSysDescription ("Indicates the type of data stored in this column.")] [DefaultValue (typeof (string))] [RefreshProperties (RefreshProperties.All)] [TypeConverterAttribute (typeof (ColumnTypeConverter))] public Type DataType { get { return _dataType; } set { // check if data already exists can we change the datatype if(DataHasBeenSet == true) throw new ArgumentException("The column already has data stored."); // we want to check that the datatype is supported? TypeCode typeCode = Type.GetTypeCode(value); //Check AutoIncrement status, make compatible datatype if(AutoIncrement == true) { if(typeCode != TypeCode.Int16 && typeCode != TypeCode.Int32 && typeCode != TypeCode.Int64) AutoIncrement = false; } _dataType = value; } } /// /// /// /// When AutoIncrement is set to true, there can be no default value. /// /// [DataCategory ("Data")] [DataSysDescription ("Indicates the default column value used when adding new rows to the table.")] [TypeConverterAttribute (typeof (System.Data.DefaultValueTypeConverter))] public object DefaultValue { get { return _defaultValue; } set { object tmpObj; if ((this._defaultValue == null) || (!this._defaultValue.Equals(value))) { //If autoIncrement == true throw if (AutoIncrement) { throw new ArgumentException("Can not set default value while" + " AutoIncrement is true on this column."); } 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; } } } [MonoTODO] [DataCategory ("Data")] [DataSysDescription ("Indicates the value that this column computes for each row based on other columns instead of taking user input.")] [DefaultValue ("")] [RefreshProperties (RefreshProperties.All)] public string Expression { get { return expression; } set { if ( value != null ) { expression = value; //Check the validate expression of ExpressionElement with string expression = System.Data.ExpressionElement.ValidateExpression(expression); if (expression != string.Empty) ReadOnly = true; } } } [Browsable (false)] [DataCategory ("Data")] [DataSysDescription ("The collection that holds custom user information.")] public PropertyCollection ExtendedProperties { get { return _extendedProperties; } } [DataCategory ("Data")] [DataSysDescription ("Indicates the maximum length of the value this column allows.")] [DefaultValue (-1)] public int MaxLength { get { //Default == -1 no max length return maxLength; } set { //only applies to string columns maxLength = value; } } [DataCategory ("Data")] [DataSysDescription ("Indicates the XML uri for elements stored in this column.")] public string Namespace { get { return nameSpace; } set { nameSpace = value; } } //Need a good way to set the Ordinal when the column is added to a columnCollection. [Browsable (false)] [DataCategory ("Data")] [DataSysDescription ("Indicates the index of this column in the Columns collection.")] [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] public int Ordinal { get { //value is -1 if not part of a collection return _ordinal; } } internal void SetOrdinal(int ordinal) { _ordinal = ordinal; } [DataCategory ("Data")] [DataSysDescription ("Indicates the prefix used for this DataColumn in the xml representation.")] [DefaultValue ("")] public string Prefix { get { return prefix; } set { prefix = value; } } [DataCategory ("Data")] [DataSysDescription ("Indicates whether this column allows changes once a row has been added to the table.")] [DefaultValue (false)] public bool ReadOnly { get { return readOnly; } set { readOnly = value; } } [Browsable (false)] [DataCategory ("Data")] [DataSysDescription ("Returns the DataTable to which this column belongs.")] [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] public DataTable Table { get { return _table; } } [MonoTODO] [DataCategory ("Data")] [DataSysDescription ("Indicates whether this column should restrict its values in the rows of the table to be unique.")] [DefaultValue (false)] [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] public bool Unique { get { return unique; } set { //if Table == null then the UniqueConstraint is //created on addition to the collection //FIXME?: need to check if value is the same //because when calling "new UniqueConstraint" //the new object tries to set "column.Unique = True" //which creates an infinite loop. if(unique != value) { unique = value; if( value ) { if (Expression != null && Expression != "") throw new ArgumentException("Cannot change Unique property for the expression column."); if( _table != null ) { UniqueConstraint uc = new UniqueConstraint(this); _table.Constraints.Add(uc); } } else { if( _table != null ) { ConstraintCollection cc = _table.Constraints; //foreach (Constraint c in cc) for (int i = 0; i < cc.Count; i++) { Constraint c = cc[i]; if (c is UniqueConstraint) { DataColumn[] cols = ((UniqueConstraint)c).Columns; if (cols.Length == 1 && cols[0] == this) { if (!cc.CanRemove(c)) throw new ArgumentException("Cannot remove unique constraint '" + c.ConstraintName + "'. Remove foreign key constraint first."); cc.Remove(c); } } } } } } } } #endregion // Properties #region Methods /* ?? [MonoTODO] protected internal void CheckNotAllowNull() { } [MonoTODO] protected void CheckUnique() { } */ /// /// 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 virtual void OnPropertyChanging (PropertyChangedEventArgs pcevent) { } [MonoTODO] protected internal void RaisePropertyChanging(string name) { } /// /// Gets the Expression of the column, if one exists. /// /// The Expression value, if the property is set; /// otherwise, the ColumnName property. [MonoTODO] public override string ToString() { if (expression != string.Empty) return ColumnName + " + " + expression; return ColumnName; } [MonoTODO] internal void SetTable(DataTable table) { _table = table; // this will get called by DataTable // and DataColumnCollection } // 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; } static internal int CompareValues (object val1, object val2, Type t, bool ignoreCase) { IComparer comparer = DBComparerFactory.GetComparer (t, ignoreCase); return comparer.Compare (val1, val2); } #endregion // Methods } }