2 // System.Data.UniqueConstraint.cs
5 // Franklin Wise <gracenote@earthlink.net>
6 // Daniel Morgan <danmorg@sc.rr.com>
7 // Tim Coleman (tim@timcoleman.com)
9 // (C) 2002 Franklin Wise
10 // (C) 2002 Daniel Morgan
11 // Copyright (C) Tim Coleman, 2002
14 using System.Collections;
15 using System.ComponentModel;
16 using System.Runtime.InteropServices;
18 namespace System.Data {
20 [DefaultProperty ("ConstraintName")]
22 public class UniqueConstraint : Constraint
24 private bool _isPrimaryKey = false;
25 private bool __isPrimaryKey = false;
26 private DataTable _dataTable; //set by ctor except when unique case
28 private DataColumn [] _dataColumns;
30 //TODO:provide helpers for this case
31 private string [] _dataColumnNames; //unique case
36 public UniqueConstraint (DataColumn column)
38 _uniqueConstraint ("", column, false);
41 public UniqueConstraint (DataColumn[] columns)
43 _uniqueConstraint ("", columns, false);
46 public UniqueConstraint (DataColumn column, bool isPrimaryKey)
48 _uniqueConstraint ("", column, isPrimaryKey);
51 public UniqueConstraint (DataColumn[] columns, bool isPrimaryKey)
53 _uniqueConstraint ("", columns, isPrimaryKey);
56 public UniqueConstraint (string name, DataColumn column)
58 _uniqueConstraint (name, column, false);
61 public UniqueConstraint (string name, DataColumn[] columns)
63 _uniqueConstraint (name, columns, false);
66 public UniqueConstraint (string name, DataColumn column, bool isPrimaryKey)
68 _uniqueConstraint (name, column, isPrimaryKey);
71 public UniqueConstraint (string name, DataColumn[] columns, bool isPrimaryKey)
73 _uniqueConstraint (name, columns, isPrimaryKey);
76 //Special case. Can only be added to the Collection with AddRange
79 public UniqueConstraint (string name, string[] columnNames, bool isPrimaryKey)
81 throw new NotImplementedException(); //need to finish related logic
83 base.ConstraintName = name;
86 //must set unique when added to the collection
88 //keep list of names to resolve later
89 _dataColumnNames = columnNames;
91 _isPrimaryKey = isPrimaryKey;
96 private void _uniqueConstraint(string name, DataColumn column, bool isPrimaryKey)
99 _validateColumn (column);
101 //Set Constraint Name
102 base.ConstraintName = name;
104 __isPrimaryKey = isPrimaryKey;
107 _dataColumns = new DataColumn [] {column};
109 //Get table reference
110 _dataTable = column.Table;
114 private void _uniqueConstraint(string name, DataColumn[] columns, bool isPrimaryKey)
117 _validateColumns (columns, out _dataTable);
119 //Set Constraint Name
120 base.ConstraintName = name;
123 _dataColumns = columns;
126 __isPrimaryKey = isPrimaryKey;
129 #endregion // Constructors
133 private void _validateColumns(DataColumn [] columns)
136 _validateColumns(columns, out table);
139 //Validates a collection of columns with the ctor rules
140 private void _validateColumns(DataColumn [] columns, out DataTable table) {
144 if (null == columns) throw new ArgumentNullException();
146 //check that there is at least one column
147 //LAMESPEC: not in spec
148 if (columns.Length < 1)
149 throw new InvalidConstraintException("Must be at least one column.");
151 DataTable compareTable = columns[0].Table;
153 foreach (DataColumn col in columns){
155 //check individual column rules
156 _validateColumn (col);
159 //check that columns are all from the same table??
160 //LAMESPEC: not in spec
161 if (compareTable != col.Table)
162 throw new InvalidConstraintException("Columns must be from the same table.");
166 table = compareTable;
169 //validates a column with the ctor rules
170 private void _validateColumn(DataColumn column) {
173 if (null == column) // FIXME: This is little weird, but here it goes...
174 throw new NullReferenceException("Object reference not set to an instance of an object.");
177 //column must belong to a table
178 //LAMESPEC: not in spec
179 if (null == column.Table) {
180 string message = "Column ";
181 if (column.ColumnName != string.Empty)
182 message += column.ColumnName + " ";
184 message += "must belong to a table.";
186 throw new ArgumentException(message);
192 /// If IsPrimaryKey is set to be true, this sets it true
194 internal void UpdatePrimaryKey ()
196 _isPrimaryKey = __isPrimaryKey;
198 foreach (DataColumn Col in _dataColumns)
203 internal static void SetAsPrimaryKey(ConstraintCollection collection, UniqueConstraint newPrimaryKey)
206 if (null == collection) throw new ArgumentNullException("ConstraintCollection can't be null.");
208 //make sure newPrimaryKey belongs to the collection parm unless it is null
209 if ( collection.IndexOf(newPrimaryKey) < 0 && (null != newPrimaryKey) )
210 throw new ArgumentException("newPrimaryKey must belong to collection.");
213 UniqueConstraint uc = GetPrimaryKeyConstraint(collection);
216 if (null != uc) uc._isPrimaryKey = false;
219 if (null != newPrimaryKey) newPrimaryKey._isPrimaryKey = true;
224 internal static UniqueConstraint GetPrimaryKeyConstraint(ConstraintCollection collection)
226 if (null == collection) throw new ArgumentNullException("Collection can't be null.");
229 IEnumerator enumer = collection.GetEnumerator();
230 while (enumer.MoveNext())
232 uc = enumer.Current as UniqueConstraint;
233 if (null == uc) continue;
235 if (uc.IsPrimaryKey) return uc;
238 //if we got here there was no pk
243 internal static UniqueConstraint GetUniqueConstraintForColumnSet(ConstraintCollection collection,
244 DataColumn[] columns)
246 if (null == collection) throw new ArgumentNullException("Collection can't be null.");
247 if (null == columns ) return null;
249 UniqueConstraint uniqueConstraint;
250 IEnumerator enumer = collection.GetEnumerator();
251 while (enumer.MoveNext())
253 uniqueConstraint = enumer.Current as UniqueConstraint;
254 if (uniqueConstraint != null)
256 if ( DataColumn.AreColumnSetsTheSame(uniqueConstraint.Columns, columns) )
258 return uniqueConstraint;
269 [DataCategory ("Data")]
270 [DataSysDescription ("Indicates the columns of this constraint.")]
272 public virtual DataColumn[] Columns {
273 get { return _dataColumns; }
276 [DataCategory ("Data")]
277 [DataSysDescription ("Indicates if this constraint is a primary key.")]
279 public bool IsPrimaryKey {
280 get { return _isPrimaryKey; }
283 [DataCategory ("Data")]
284 [DataSysDescription ("Indicates the table of this constraint.")]
286 public override DataTable Table {
287 get { return _dataTable; }
290 #endregion // Properties
294 public override bool Equals(object key2) {
296 UniqueConstraint cst = key2 as UniqueConstraint;
297 if (null == cst) return false;
299 //according to spec if the cols are equal
300 //then two UniqueConstraints are equal
301 return DataColumn.AreColumnSetsTheSame(cst.Columns, this.Columns);
305 public override int GetHashCode()
307 //initialize hash with default value
311 //derive the hash code from the columns that way
312 //Equals and GetHashCode return Equal objects to be the
315 //Get the first column hash
316 if (this.Columns.Length > 0)
317 hash ^= this.Columns[0].GetHashCode();
319 //get the rest of the column hashes if there any
320 for (i = 1; i < this.Columns.Length; i++)
322 hash ^= this.Columns[1].GetHashCode();
330 internal override void AddToConstraintCollectionSetup(
331 ConstraintCollection collection)
333 //run Ctor rules again
334 _validateColumns(_dataColumns);
336 //make sure a unique constraint doesn't already exists for these columns
337 UniqueConstraint uc = UniqueConstraint.GetUniqueConstraintForColumnSet(collection, this.Columns);
338 if (null != uc) throw new ArgumentException("Unique constraint already exists for these" +
339 " columns. Existing ConstraintName is " + uc.ConstraintName);
341 //Allow only one primary key
342 if (this.IsPrimaryKey)
344 uc = GetPrimaryKeyConstraint(collection);
345 if (null != uc) uc._isPrimaryKey = false;
349 //FIXME: ConstraintCollection calls AssertContraint() again rigth after calling
350 //this method, so that it is executed twice. Need to investigate which
351 // call to remove as that migth affect other parts of the classes.
356 internal override void RemoveFromConstraintCollectionCleanup(
357 ConstraintCollection collection)
362 internal override void AssertConstraint()
365 if (_dataTable == null) return; //???
366 if (_dataColumns == null) return; //???
370 DataTable tbl = _dataTable;
372 //TODO: Investigate other ways of speeding up the validation work below.
373 //FIXME: This only works when only one DataColumn has been specified.
375 //validate no duplicates exists.
376 //Only validate when there are at least 2 rows
377 //so that a duplicate migth exist.
378 if(tbl.Rows.Count > 1) {
379 //get copy of rows collection first so that we do not modify the
381 DataRow[] rows = new DataRow [tbl.Rows.Count];
382 tbl.Rows.CopyTo (rows, 0);
383 ArrayList clonedDataList = new ArrayList (rows);
385 ArrayList newDataList = new ArrayList();
387 //copy to array list only the column we are interested in.
388 foreach (DataRow row in clonedDataList)
389 newDataList.Add (row[this._dataColumns[0]]);
391 //sort ArrayList and check adjacent values for duplicates.
394 for (int i = 0 ; i < newDataList.Count - 1 ; i++)
395 if (newDataList[i].Equals (newDataList[i+1]))
396 throw new InvalidConstraintException (String.Format ("Column '{0}' contains non-unique values", this._dataColumns[0]));
403 internal override void AssertConstraint(DataRow row)
406 if (_dataTable == null) return; //???
407 if (_dataColumns == null) return; //???
411 DataTable tbl = _dataTable;
413 foreach(DataRow compareRow in tbl.Rows)
415 //skip if it is the same row to be validated
416 if(!row.Equals(compareRow))
418 if(compareRow.HasVersion (DataRowVersion.Original))
420 //FIXME: should we compare to compareRow[DataRowVersion.Current]?
421 //FIXME: We need to compare to all columns the constraint is set to.
422 if(row[_dataColumns[0], DataRowVersion.Proposed].Equals( compareRow[_dataColumns[0], DataRowVersion.Current]))
424 string ExceptionMessage;
425 ExceptionMessage = "Column '" + _dataColumns[0].ColumnName + "' is constrained to be unique.";
426 ExceptionMessage += " Value '" + row[_dataColumns[0], DataRowVersion.Proposed].ToString();
427 ExceptionMessage += "' is already present.";
429 throw new ConstraintException (ExceptionMessage);
440 #endregion // Methods