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;
190 foreach (DataColumn Col in _dataColumns)
195 internal static void SetAsPrimaryKey(ConstraintCollection collection, UniqueConstraint newPrimaryKey)
198 if (null == collection) throw new ArgumentNullException("ConstraintCollection can't be null.");
200 //make sure newPrimaryKey belongs to the collection parm unless it is null
201 if ( collection.IndexOf(newPrimaryKey) < 0 && (null != newPrimaryKey) )
202 throw new ArgumentException("newPrimaryKey must belong to collection.");
205 UniqueConstraint uc = GetPrimaryKeyConstraint(collection);
208 if (null != uc) uc._isPrimaryKey = false;
211 if (null != newPrimaryKey) newPrimaryKey._isPrimaryKey = true;
216 internal static UniqueConstraint GetPrimaryKeyConstraint(ConstraintCollection collection)
218 if (null == collection) throw new ArgumentNullException("Collection can't be null.");
221 IEnumerator enumer = collection.GetEnumerator();
222 while (enumer.MoveNext())
224 uc = enumer.Current as UniqueConstraint;
225 if (null == uc) continue;
227 if (uc.IsPrimaryKey) return uc;
230 //if we got here there was no pk
235 internal static UniqueConstraint GetUniqueConstraintForColumnSet(ConstraintCollection collection,
236 DataColumn[] columns)
238 if (null == collection) throw new ArgumentNullException("Collection can't be null.");
239 if (null == columns ) return null;
241 UniqueConstraint uniqueConstraint;
242 IEnumerator enumer = collection.GetEnumerator();
243 while (enumer.MoveNext())
245 uniqueConstraint = enumer.Current as UniqueConstraint;
246 if (uniqueConstraint != null)
248 if ( DataColumn.AreColumnSetsTheSame(uniqueConstraint.Columns, columns) )
250 return uniqueConstraint;
261 [DataCategory ("Data")]
262 [DataSysDescription ("Indicates the columns of this constraint.")]
264 public virtual DataColumn[] Columns {
265 get { return _dataColumns; }
268 [DataCategory ("Data")]
269 [DataSysDescription ("Indicates if this constraint is a primary key.")]
271 public bool IsPrimaryKey {
272 get { return _isPrimaryKey; }
275 [DataCategory ("Data")]
276 [DataSysDescription ("Indicates the table of this constraint.")]
278 public override DataTable Table {
279 get { return _dataTable; }
282 #endregion // Properties
286 public override bool Equals(object key2) {
288 UniqueConstraint cst = key2 as UniqueConstraint;
289 if (null == cst) return false;
291 //according to spec if the cols are equal
292 //then two UniqueConstraints are equal
293 return DataColumn.AreColumnSetsTheSame(cst.Columns, this.Columns);
297 public override int GetHashCode()
299 //initialize hash with default value
303 //derive the hash code from the columns that way
304 //Equals and GetHashCode return Equal objects to be the
307 //Get the first column hash
308 if (this.Columns.Length > 0)
309 hash ^= this.Columns[0].GetHashCode();
311 //get the rest of the column hashes if there any
312 for (i = 1; i < this.Columns.Length; i++)
314 hash ^= this.Columns[1].GetHashCode();
322 internal override void AddToConstraintCollectionSetup(
323 ConstraintCollection collection)
325 //run Ctor rules again
326 _validateColumns(_dataColumns);
328 //make sure a unique constraint doesn't already exists for these columns
329 UniqueConstraint uc = UniqueConstraint.GetUniqueConstraintForColumnSet(collection, this.Columns);
330 if (null != uc) throw new ArgumentException("Unique constraint already exists for these" +
331 " columns. Existing ConstraintName is " + uc.ConstraintName);
333 //Allow only one primary key
334 if (this.IsPrimaryKey)
336 uc = GetPrimaryKeyConstraint(collection);
337 if (null != uc) uc._isPrimaryKey = false;
341 //FIXME: ConstraintCollection calls AssertContraint() again rigth after calling
342 //this method, so that it is executed twice. Need to investigate which
343 // call to remove as that migth affect other parts of the classes.
348 internal override void RemoveFromConstraintCollectionCleanup(
349 ConstraintCollection collection)
354 internal override void AssertConstraint()
357 if (_dataTable == null) return; //???
358 if (_dataColumns == null) return; //???
362 DataTable tbl = _dataTable;
364 //TODO: Investigate other ways of speeding up the validation work below.
365 //FIXME: This only works when only one DataColumn has been specified.
367 //validate no duplicates exists.
368 //Only validate when there are at least 2 rows
369 //so that a duplicate migth exist.
370 if(tbl.Rows.Count > 1) {
371 //get copy of rows collection first so that we do not modify the
373 DataRow[] rows = new DataRow [tbl.Rows.Count];
374 tbl.Rows.CopyTo (rows, 0);
375 ArrayList clonedDataList = new ArrayList (rows);
377 ArrayList newDataList = new ArrayList();
379 //copy to array list only the column we are interested in.
380 foreach (DataRow row in clonedDataList) {
382 object colvalue = row[this._dataColumns[0]];
383 if (colvalue == DBNull.Value)
384 newDataList.Add (null);
386 newDataList.Add (colvalue);
389 //sort ArrayList and check adjacent values for duplicates.
392 for (int i = 0 ; i < newDataList.Count - 1 ; i++)
394 if (newDataList [i] == null) {
395 if (newDataList [i+1] == null)
396 throw new InvalidConstraintException (String.Format ("Column '{0}' contains non-unique values", this._dataColumns[0]));
398 else if (newDataList[i].Equals (newDataList[i+1]))
399 throw new InvalidConstraintException (String.Format ("Column '{0}' contains non-unique values", this._dataColumns[0]));
406 internal override void AssertConstraint(DataRow row)
408 if (_dataTable == null) return; //???
409 if (_dataColumns == null) return; //???
412 // check that the row has values for all columns in the constarint.
414 for (int i = 0; i < _dataColumns.Length; i++)
417 val = row[_dataColumns[i]];
418 if (val == null || val == DBNull.Value)
419 throw new NoNullAllowedException("Column '" + _dataColumns[i].ColumnName + "' does not allow nulls.");
422 DataTable tbl = _dataTable;
424 foreach(DataRow compareRow in tbl.Rows)
427 //skip if it is the same row to be validated
428 if(!row.Equals(compareRow))
430 //FIXME: should we compare to compareRow[DataRowVersion.Current]?
431 //FIXME: We need to compare to all columns the constraint is set to.
433 for (int i = 0; i < _dataColumns.Length; i++)
435 if(row.HasVersion(DataRowVersion.Proposed))
436 rowVal = row[_dataColumns[i], DataRowVersion.Proposed];
438 rowVal = row[_dataColumns[i], DataRowVersion.Current];
440 // if the values in the row are not equal to the values of
441 // the original row from the table we can move to the next row.
442 if(!rowVal.Equals( compareRow[_dataColumns[i]]))
450 throw new ConstraintException(GetErrorMessage(compareRow));
458 private string GetErrorMessage(DataRow row)
462 System.Text.StringBuilder sb = new System.Text.StringBuilder(row[_dataColumns[0]].ToString());
463 for (i = 1; i < _dataColumns.Length; i++)
464 sb = sb.Append(", ").Append(row[_dataColumns[i].ColumnName]);
465 string valStr = sb.ToString();
466 sb = new System.Text.StringBuilder(_dataColumns[0].ColumnName);
467 for (i = 1; i < _dataColumns.Length; i++)
468 sb = sb.Append(", ").Append(_dataColumns[i].ColumnName);
469 string colStr = sb.ToString();
470 return "Column '" + colStr + "' is constrained to be unique. Value '" + valStr + "' is already present.";
473 #endregion // Methods