// // System.Data.UniqueConstraint.cs // // Author: // Franklin Wise // Daniel Morgan // Tim Coleman (tim@timcoleman.com) // // (C) 2002 Franklin Wise // (C) 2002 Daniel Morgan // Copyright (C) Tim Coleman, 2002 // // 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.Collections; using System.ComponentModel; using System.Runtime.InteropServices; using System.Data.Common; namespace System.Data { [Editor ("Microsoft.VSDesigner.Data.Design.UniqueConstraintEditor, " + Consts.AssemblyMicrosoft_VSDesigner, "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)] [DefaultProperty ("ConstraintName")] #if !NET_2_0 [Serializable] #endif public class UniqueConstraint : Constraint { private bool _isPrimaryKey = false; private bool _belongsToCollection = false; private DataTable _dataTable; //set by ctor except when unique case //FIXME: create a class which will wrap this collection private DataColumn [] _dataColumns; //TODO:provide helpers for this case private string [] _dataColumnNames; //unique case private ForeignKeyConstraint _childConstraint = null; #region Constructors public UniqueConstraint (DataColumn column) { _uniqueConstraint ("", column, false); } public UniqueConstraint (DataColumn[] columns) { _uniqueConstraint ("", columns, false); } public UniqueConstraint (DataColumn column, bool isPrimaryKey) { _uniqueConstraint ("", column, isPrimaryKey); } public UniqueConstraint (DataColumn[] columns, bool isPrimaryKey) { _uniqueConstraint ("", columns, isPrimaryKey); } public UniqueConstraint (string name, DataColumn column) { _uniqueConstraint (name, column, false); } public UniqueConstraint (string name, DataColumn[] columns) { _uniqueConstraint (name, columns, false); } public UniqueConstraint (string name, DataColumn column, bool isPrimaryKey) { _uniqueConstraint (name, column, isPrimaryKey); } public UniqueConstraint (string name, DataColumn[] columns, bool isPrimaryKey) { _uniqueConstraint (name, columns, isPrimaryKey); } //Special case. Can only be added to the Collection with AddRange [Browsable (false)] public UniqueConstraint (string name, string[] columnNames, bool isPrimaryKey) { InitInProgress = true; //keep list of names to resolve later _dataColumnNames = columnNames; base.ConstraintName = name; _isPrimaryKey = isPrimaryKey; } //helper ctor private void _uniqueConstraint(string name, DataColumn column, bool isPrimaryKey) { //validate _validateColumn (column); //Set Constraint Name base.ConstraintName = name; _isPrimaryKey = isPrimaryKey; //keep reference _dataColumns = new DataColumn [] {column}; //Get table reference _dataTable = column.Table; } //helpter ctor private void _uniqueConstraint(string name, DataColumn[] columns, bool isPrimaryKey) { //validate _validateColumns (columns, out _dataTable); //Set Constraint Name base.ConstraintName = name; //keep reference _dataColumns = columns; //PK? _isPrimaryKey = isPrimaryKey; } #endregion // Constructors #region Helpers private void _validateColumns(DataColumn [] columns) { DataTable table; _validateColumns(columns, out table); } //Validates a collection of columns with the ctor rules private void _validateColumns(DataColumn [] columns, out DataTable table) { table = null; //not null if (null == columns) throw new ArgumentNullException(); //check that there is at least one column //LAMESPEC: not in spec if (columns.Length < 1) throw new InvalidConstraintException("Must be at least one column."); DataTable compareTable = columns[0].Table; //foreach foreach (DataColumn col in columns){ //check individual column rules _validateColumn (col); //check that columns are all from the same table?? //LAMESPEC: not in spec if (compareTable != col.Table) throw new InvalidConstraintException("Columns must be from the same table."); } table = compareTable; } //validates a column with the ctor rules private void _validateColumn(DataColumn column) { //not null if (null == column) // FIXME: This is little weird, but here it goes... throw new NullReferenceException("Object reference not set to an instance of an object."); //column must belong to a table //LAMESPEC: not in spec if (null == column.Table) throw new ArgumentException ("Column must belong to a table."); } internal static void SetAsPrimaryKey(ConstraintCollection collection, UniqueConstraint newPrimaryKey) { //not null if (null == collection) throw new ArgumentNullException("ConstraintCollection can't be null."); //make sure newPrimaryKey belongs to the collection parm unless it is null if ( collection.IndexOf(newPrimaryKey) < 0 && (null != newPrimaryKey) ) throw new ArgumentException("newPrimaryKey must belong to collection."); //Get existing pk UniqueConstraint uc = GetPrimaryKeyConstraint(collection); //clear existing if (null != uc) uc._isPrimaryKey = false; //set new key if (null != newPrimaryKey) newPrimaryKey._isPrimaryKey = true; } internal static UniqueConstraint GetPrimaryKeyConstraint(ConstraintCollection collection) { if (null == collection) throw new ArgumentNullException("Collection can't be null."); UniqueConstraint uc; IEnumerator enumer = collection.GetEnumerator(); while (enumer.MoveNext()) { uc = enumer.Current as UniqueConstraint; if (null == uc) continue; if (uc.IsPrimaryKey) return uc; } //if we got here there was no pk return null; } internal static UniqueConstraint GetUniqueConstraintForColumnSet(ConstraintCollection collection, DataColumn[] columns) { if (null == collection) throw new ArgumentNullException("Collection can't be null."); if (null == columns ) return null; foreach(Constraint constraint in collection) { if (constraint is UniqueConstraint) { UniqueConstraint uc = constraint as UniqueConstraint; if ( DataColumn.AreColumnSetsTheSame(uc.Columns, columns) ) { return uc; } } } return null; } internal ForeignKeyConstraint ChildConstraint { get { return _childConstraint; } set { _childConstraint = value; } } // Helper Special Ctor // Set the _dataTable property to the table to which this instance is bound when AddRange() // is called with the special constructor. // Validate whether the named columns exist in the _dataTable internal override void FinishInit (DataTable _setTable) { _dataTable = _setTable; if (_isPrimaryKey == true && _setTable.PrimaryKey.Length != 0) throw new ArgumentException ("Cannot add primary key constraint since primary key" + "is already set for the table"); DataColumn[] cols = new DataColumn [_dataColumnNames.Length]; int i = 0; foreach (string _columnName in _dataColumnNames) { if (_setTable.Columns.Contains (_columnName)) { cols [i] = _setTable.Columns [_columnName]; i++; continue; } throw(new InvalidConstraintException ("The named columns must exist in the table")); } _dataColumns = cols; _validateColumns (cols); InitInProgress = false; } #endregion //Helpers #region Properties [DataCategory ("Data")] #if !NET_2_0 [DataSysDescription ("Indicates the columns of this constraint.")] #endif [ReadOnly (true)] public virtual DataColumn[] Columns { get { return _dataColumns; } } [DataCategory ("Data")] #if !NET_2_0 [DataSysDescription ("Indicates if this constraint is a primary key.")] #endif public bool IsPrimaryKey { get { if (Table == null || (!_belongsToCollection)) { return false; } return _isPrimaryKey; } } [DataCategory ("Data")] #if !NET_2_0 [DataSysDescription ("Indicates the table of this constraint.")] #endif [ReadOnly (true)] public override DataTable Table { get { return _dataTable; } } #endregion // Properties #region Methods internal void SetIsPrimaryKey (bool value) { _isPrimaryKey = value; } public override bool Equals(object key2) { UniqueConstraint cst = key2 as UniqueConstraint; if (null == cst) return false; //according to spec if the cols are equal //then two UniqueConstraints are equal return DataColumn.AreColumnSetsTheSame(cst.Columns, this.Columns); } public override int GetHashCode() { //initialize hash with default value int hash = 42; int i; //derive the hash code from the columns that way //Equals and GetHashCode return Equal objects to be the //same //Get the first column hash if (this.Columns.Length > 0) hash ^= this.Columns[0].GetHashCode(); //get the rest of the column hashes if there any for (i = 1; i < this.Columns.Length; i++) { hash ^= this.Columns[1].GetHashCode(); } return hash ; } internal override void AddToConstraintCollectionSetup( ConstraintCollection collection) { for (int i = 0; i < Columns.Length; i++) if (Columns[i].Table != collection.Table) throw new ArgumentException("These columns don't point to this table."); //run Ctor rules again _validateColumns(_dataColumns); //make sure a unique constraint doesn't already exists for these columns UniqueConstraint uc = UniqueConstraint.GetUniqueConstraintForColumnSet(collection, this.Columns); if (null != uc) throw new ArgumentException("Unique constraint already exists for these" + " columns. Existing ConstraintName is " + uc.ConstraintName); //Allow only one primary key if (this.IsPrimaryKey) { uc = GetPrimaryKeyConstraint(collection); if (null != uc) uc._isPrimaryKey = false; } // if constraint is based on one column only // this column becomes unique if (_dataColumns.Length == 1) { _dataColumns[0].SetUnique(); } if (IsConstraintViolated()) throw new ArgumentException("These columns don't currently have unique values."); _belongsToCollection = true; } internal override void RemoveFromConstraintCollectionCleanup( ConstraintCollection collection) { if (Columns.Length == 1) Columns [0].Unique = false; _belongsToCollection = false; Index index = Index; Index = null; } internal override bool IsConstraintViolated() { if (Index == null) { Index = Table.GetIndex(Columns,null,DataViewRowState.None,null,false); } if (Index.HasDuplicates) { int[] dups = Index.Duplicates; for (int i = 0; i < dups.Length; i++){ DataRow row = Table.RecordCache[dups[i]]; ArrayList columns = new ArrayList(); ArrayList values = new ArrayList(); foreach (DataColumn col in Columns){ columns.Add(col.ColumnName); values.Add(row[col].ToString()); } string columnNames = String.Join(", ", (string[])columns.ToArray(typeof(string))); string columnValues = String.Join(", ", (string[])values.ToArray(typeof(string))); row.RowError = String.Format("Column '{0}' is constrained to be unique. Value '{1}' is already present.", columnNames, columnValues); for (int j=0; j < Columns.Length; ++j) row.SetColumnError (Columns [j], row.RowError); } return true; } return false; } internal override void AssertConstraint(DataRow row) { if (IsPrimaryKey && row.HasVersion(DataRowVersion.Default)) { for (int i = 0; i < Columns.Length; i++) { if (row.IsNull(Columns[i])) { throw new NoNullAllowedException("Column '" + Columns[i].ColumnName + "' does not allow nulls."); } } } if (Index == null) { Index = Table.GetIndex(Columns,null,DataViewRowState.None,null,false); } if (Index.HasDuplicates) { throw new ConstraintException(GetErrorMessage(row)); } } internal override bool IsColumnContained(DataColumn column) { for (int i = 0; i < _dataColumns.Length; i++) if (column == _dataColumns[i]) return true; return false; } internal override bool CanRemoveFromCollection(ConstraintCollection col, bool shouldThrow){ if (IsPrimaryKey) { if (shouldThrow) throw new ArgumentException("Cannot remove unique constraint since it's the primary key of a table."); return false; } if (Table.DataSet == null) return true; if (ChildConstraint != null) { if (!shouldThrow) return false; throw new ArgumentException (String.Format ( "Cannot remove unique constraint '{0}'." + "Remove foreign key constraint '{1}' first.", ConstraintName,ChildConstraint.ConstraintName)); } return true; } private string GetErrorMessage(DataRow row) { int i; System.Text.StringBuilder sb = new System.Text.StringBuilder(row[_dataColumns[0]].ToString()); for (i = 1; i < _dataColumns.Length; i++) { sb = sb.Append(", ").Append(row[_dataColumns[i].ColumnName]); } string valStr = sb.ToString(); sb = new System.Text.StringBuilder(_dataColumns[0].ColumnName); for (i = 1; i < _dataColumns.Length; i++) { sb = sb.Append(", ").Append(_dataColumns[i].ColumnName); } string colStr = sb.ToString(); return "Column '" + colStr + "' is constrained to be unique. Value '" + valStr + "' is already present."; } #endregion // Methods } }