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 throw new ArgumentException ("Column must belong to a table.");
184 /// If IsPrimaryKey is set to be true, this sets it true
186 internal void UpdatePrimaryKey ()
188 _isPrimaryKey = __isPrimaryKey;
189 foreach (DataColumn Col in _dataColumns)
194 internal static void SetAsPrimaryKey(ConstraintCollection collection, UniqueConstraint newPrimaryKey)
197 if (null == collection) throw new ArgumentNullException("ConstraintCollection can't be null.");
199 //make sure newPrimaryKey belongs to the collection parm unless it is null
200 if ( collection.IndexOf(newPrimaryKey) < 0 && (null != newPrimaryKey) )
201 throw new ArgumentException("newPrimaryKey must belong to collection.");
204 UniqueConstraint uc = GetPrimaryKeyConstraint(collection);
207 if (null != uc) uc._isPrimaryKey = false;
210 if (null != newPrimaryKey) newPrimaryKey._isPrimaryKey = true;
215 internal static UniqueConstraint GetPrimaryKeyConstraint(ConstraintCollection collection)
217 if (null == collection) throw new ArgumentNullException("Collection can't be null.");
220 IEnumerator enumer = collection.GetEnumerator();
221 while (enumer.MoveNext())
223 uc = enumer.Current as UniqueConstraint;
224 if (null == uc) continue;
226 if (uc.IsPrimaryKey) return uc;
229 //if we got here there was no pk
234 internal static UniqueConstraint GetUniqueConstraintForColumnSet(ConstraintCollection collection,
235 DataColumn[] columns)
237 if (null == collection) throw new ArgumentNullException("Collection can't be null.");
238 if (null == columns ) return null;
240 UniqueConstraint uniqueConstraint;
241 IEnumerator enumer = collection.GetEnumerator();
242 while (enumer.MoveNext())
244 uniqueConstraint = enumer.Current as UniqueConstraint;
245 if (uniqueConstraint != null)
247 if ( DataColumn.AreColumnSetsTheSame(uniqueConstraint.Columns, columns) )
249 return uniqueConstraint;
260 [DataCategory ("Data")]
261 [DataSysDescription ("Indicates the columns of this constraint.")]
263 public virtual DataColumn[] Columns {
264 get { return _dataColumns; }
267 [DataCategory ("Data")]
268 [DataSysDescription ("Indicates if this constraint is a primary key.")]
270 public bool IsPrimaryKey {
271 get { return _isPrimaryKey; }
274 [DataCategory ("Data")]
275 [DataSysDescription ("Indicates the table of this constraint.")]
277 public override DataTable Table {
278 get { return _dataTable; }
281 #endregion // Properties
285 public override bool Equals(object key2) {
287 UniqueConstraint cst = key2 as UniqueConstraint;
288 if (null == cst) return false;
290 //according to spec if the cols are equal
291 //then two UniqueConstraints are equal
292 return DataColumn.AreColumnSetsTheSame(cst.Columns, this.Columns);
296 public override int GetHashCode()
298 //initialize hash with default value
302 //derive the hash code from the columns that way
303 //Equals and GetHashCode return Equal objects to be the
306 //Get the first column hash
307 if (this.Columns.Length > 0)
308 hash ^= this.Columns[0].GetHashCode();
310 //get the rest of the column hashes if there any
311 for (i = 1; i < this.Columns.Length; i++)
313 hash ^= this.Columns[1].GetHashCode();
321 internal override void AddToConstraintCollectionSetup(
322 ConstraintCollection collection)
324 //run Ctor rules again
325 _validateColumns(_dataColumns);
327 //make sure a unique constraint doesn't already exists for these columns
328 UniqueConstraint uc = UniqueConstraint.GetUniqueConstraintForColumnSet(collection, this.Columns);
329 if (null != uc) throw new ArgumentException("Unique constraint already exists for these" +
330 " columns. Existing ConstraintName is " + uc.ConstraintName);
332 //Allow only one primary key
333 if (this.IsPrimaryKey)
335 uc = GetPrimaryKeyConstraint(collection);
336 if (null != uc) uc._isPrimaryKey = false;
340 //FIXME: ConstraintCollection calls AssertContraint() again rigth after calling
341 //this method, so that it is executed twice. Need to investigate which
342 // call to remove as that migth affect other parts of the classes.
347 internal override void RemoveFromConstraintCollectionCleanup(
348 ConstraintCollection collection)
353 internal override void AssertConstraint()
356 if (_dataTable == null) return; //???
357 if (_dataColumns == null) return; //???
361 DataTable tbl = _dataTable;
363 //TODO: Investigate other ways of speeding up the validation work below.
365 //validate no duplicates exists.
366 //Only validate when there are at least 2 rows
367 //so that a duplicate migth exist.
368 if(tbl.Rows.Count > 1) {
369 //get copy of rows collection first so that we do not modify the
371 DataRow[] rows = new DataRow [tbl.Rows.Count];
372 tbl.Rows.CopyTo (rows, 0);
374 Array.Sort(rows, new RowsComparer(this));
375 for (int i = 0 ; i < rows.Length - 1 ; i++)
378 // check if the values in the constraints columns are equal
\r
379 for (int j = 0; j < _dataColumns.Length; j++)
\r
381 if (!rows[i][_dataColumns[j]].Equals(rows[i + 1][_dataColumns[j]]))
\r
388 throw new InvalidConstraintException (String.Format ("Column '{0}' contains non-unique values", this._dataColumns[0]));
396 internal override void AssertConstraint(DataRow row)
398 if (_dataTable == null) return; //???
399 if (_dataColumns == null) return; //???
402 // check that the row has values for all columns in the constarint.
404 for (int i = 0; i < _dataColumns.Length; i++)
407 val = row[_dataColumns[i]];
408 if (val == null || val == DBNull.Value)
409 throw new NoNullAllowedException("Column '" + _dataColumns[i].ColumnName + "' does not allow nulls.");
412 DataTable tbl = _dataTable;
414 object[] rowVals = new object[_dataColumns.Length];
415 for (int i = 0; i < _dataColumns.Length; i++)
417 if(row.HasVersion(DataRowVersion.Proposed))
418 rowVals[i] = row[_dataColumns[i], DataRowVersion.Proposed];
420 rowVals[i] = row[_dataColumns[i], DataRowVersion.Current];
423 foreach(DataRow compareRow in tbl.Rows)
425 if (compareRow.RowState != DataRowState.Deleted)
428 //skip if it is the same row to be validated
429 if(!row.Equals(compareRow))
431 //FIXME: should we compare to compareRow[DataRowVersion.Current]?
432 //FIXME: We need to compare to all columns the constraint is set to.
433 for (int i = 0; i < _dataColumns.Length; i++)
435 // if the values in the row are not equal to the values of
436 // the original row from the table we can move to the next row.
437 if(!rowVals[i].Equals( compareRow[_dataColumns[i]]))
445 throw new ConstraintException(GetErrorMessage(compareRow));
454 private string GetErrorMessage(DataRow row)
458 System.Text.StringBuilder sb = new System.Text.StringBuilder(row[_dataColumns[0]].ToString());
459 for (i = 1; i < _dataColumns.Length; i++)
460 sb = sb.Append(", ").Append(row[_dataColumns[i].ColumnName]);
461 string valStr = sb.ToString();
462 sb = new System.Text.StringBuilder(_dataColumns[0].ColumnName);
463 for (i = 1; i < _dataColumns.Length; i++)
464 sb = sb.Append(", ").Append(_dataColumns[i].ColumnName);
465 string colStr = sb.ToString();
466 return "Column '" + colStr + "' is constrained to be unique. Value '" + valStr + "' is already present.";
469 // generates a hash key for a given row based on the constraints columns.
470 internal int CalcHashValue(DataRow row)
474 for (int i = 0; i < _dataColumns.Length; i++)
476 o = row[_dataColumns[i]];
478 retVal += o.GetHashCode();
483 #endregion // Methods
485 private class RowsComparer : IComparer
487 private UniqueConstraint _uc;
489 public RowsComparer(UniqueConstraint uc)
494 public int Compare(object o1, object o2)
496 DataRow row1 = (DataRow) o1;
497 DataRow row2 = (DataRow) o2;
498 int val1 = _uc.CalcHashValue(row1);
499 int val2 = _uc.CalcHashValue(row2);