2 // System.Data.UniqueConstraint.cs
5 // Franklin Wise <gracenote@earthlink.net>
6 // Daniel Morgan <danmorg@sc.rr.com>
8 // (C) 2002 Franklin Wise
9 // (C) 2002 Daniel Morgan
12 using System.Collections;
13 using System.ComponentModel;
14 using System.Runtime.InteropServices;
18 public class UniqueConstraint : Constraint
20 private bool _isPrimaryKey = false;
21 private DataTable _dataTable; //set by ctor except when unique case
23 private DataColumn [] _dataColumns;
25 //TODO:provide helpers for this case
26 private string [] _dataColumnNames; //unique case
31 public UniqueConstraint(DataColumn column) {
33 _uniqueConstraint ("", column, false);
36 public UniqueConstraint(DataColumn[] columns) {
38 _uniqueConstraint ("", columns, false);
41 public UniqueConstraint(DataColumn column,
44 _uniqueConstraint ("", column, isPrimaryKey);
47 public UniqueConstraint(DataColumn[] columns, bool isPrimaryKey) {
49 _uniqueConstraint ("", columns, isPrimaryKey);
52 public UniqueConstraint(string name, DataColumn column) {
54 _uniqueConstraint (name, column, false);
57 public UniqueConstraint(string name, DataColumn[] columns) {
59 _uniqueConstraint (name, columns, false);
62 public UniqueConstraint(string name, DataColumn column,
65 _uniqueConstraint (name, column, isPrimaryKey);
68 public UniqueConstraint(string name,
69 DataColumn[] columns, bool isPrimaryKey) {
71 _uniqueConstraint (name, columns, isPrimaryKey);
74 //Special case. Can only be added to the Collection with AddRange
76 public UniqueConstraint(string name,
77 string[] columnNames, bool isPrimaryKey) {
79 throw new NotImplementedException(); //need to finish related logic
81 base.ConstraintName = name;
84 //must set unique when added to the collection
86 //keep list of names to resolve later
87 _dataColumnNames = columnNames;
89 _isPrimaryKey = isPrimaryKey;
94 private void _uniqueConstraint(string name,
95 DataColumn column, bool isPrimaryKey) {
98 _validateColumn (column);
100 //Set Constraint Name
101 base.ConstraintName = name;
104 column.Unique = true;
107 _dataColumns = new DataColumn [] {column};
110 _isPrimaryKey = isPrimaryKey;
112 //Get table reference
113 _dataTable = column.Table;
117 public void _uniqueConstraint(string name,
118 DataColumn[] columns, bool isPrimaryKey) {
121 _validateColumns (columns, out _dataTable);
123 //Set Constraint Name
124 base.ConstraintName = name;
127 if (columns.Length == 1) columns[0].Unique = true;
130 _dataColumns = columns;
133 _isPrimaryKey = isPrimaryKey;
137 #endregion // Constructors
141 private void _validateColumns(DataColumn [] columns)
144 _validateColumns(columns, out table);
147 //Validates a collection of columns with the ctor rules
148 private void _validateColumns(DataColumn [] columns, out DataTable table) {
152 if (null == columns) throw new ArgumentNullException();
154 //check that there is at least one column
155 //LAMESPEC: not in spec
156 if (columns.Length < 1)
157 throw new InvalidConstraintException("Must be at least one column.");
159 DataTable compareTable = columns[0].Table;
161 foreach (DataColumn col in columns){
163 //check individual column rules
164 _validateColumn (col);
167 //check that columns are all from the same table??
168 //LAMESPEC: not in spec
169 if (compareTable != col.Table)
170 throw new InvalidConstraintException("Columns must be from the same table.");
174 table = compareTable;
177 //validates a column with the ctor rules
178 private void _validateColumn(DataColumn column) {
181 if (null == column) throw new ArgumentNullException();
183 //column must belong to a table
184 //LAMESPEC: not in spec
185 if (null == column.Table)
186 throw new ArgumentException("Column " + column.ColumnName + " must belong to a table.");
190 internal static void SetAsPrimaryKey(ConstraintCollection collection, UniqueConstraint newPrimaryKey)
193 if (null == collection) throw new ArgumentNullException("ConstraintCollection can't be null.");
195 //make sure newPrimaryKey belongs to the collection parm unless it is null
196 if ( collection.IndexOf(newPrimaryKey) < 1 && (null != newPrimaryKey) )
197 throw new ArgumentException("newPrimaryKey must belong to collection.");
200 UniqueConstraint uc = GetPrimaryKeyConstraint(collection);
203 if (null != uc) uc._isPrimaryKey = false;
206 if (null != newPrimaryKey) newPrimaryKey._isPrimaryKey = true;
211 internal static UniqueConstraint GetPrimaryKeyConstraint(ConstraintCollection collection)
213 if (null == collection) throw new ArgumentNullException("Collection can't be null.");
216 IEnumerator enumer = collection.GetEnumerator();
217 while (enumer.MoveNext())
219 uc = enumer.Current as UniqueConstraint;
220 if (null == uc) continue;
222 if (uc.IsPrimaryKey) return uc;
225 //if we got here there was no pk
230 internal static UniqueConstraint GetUniqueConstraintForColumnSet(ConstraintCollection collection,
231 DataColumn[] columns)
233 if (null == collection) throw new ArgumentNullException("Collection can't be null.");
234 if (null == columns ) return null;
236 UniqueConstraint uniqueConstraint;
237 IEnumerator enumer = collection.GetEnumerator();
238 while (enumer.MoveNext())
240 uniqueConstraint = enumer.Current as UniqueConstraint;
241 if (uniqueConstraint != null)
243 if ( DataColumn.AreColumnSetsTheSame(uniqueConstraint.Columns, columns) )
245 return uniqueConstraint;
258 public virtual DataColumn[] Columns {
264 public bool IsPrimaryKey {
266 return _isPrimaryKey;
270 public override DataTable Table {
276 #endregion // Properties
280 public override bool Equals(object key2) {
282 UniqueConstraint cst = key2 as UniqueConstraint;
283 if (null == cst) return false;
285 //according to spec if the cols are equal
286 //then two UniqueConstraints are equal
287 return DataColumn.AreColumnSetsTheSame(cst.Columns, this.Columns);
291 public override int GetHashCode()
293 //initialize hash with default value
297 //derive the hash code from the columns that way
298 //Equals and GetHashCode return Equal objects to be the
301 //Get the first column hash
302 if (this.Columns.Length > 0)
303 hash ^= this.Columns[0].GetHashCode();
305 //get the rest of the column hashes if there any
306 for (i = 1; i < this.Columns.Length; i++)
308 hash ^= this.Columns[1].GetHashCode();
316 internal override void AddToConstraintCollectionSetup(
317 ConstraintCollection collection)
319 //run Ctor rules again
320 _validateColumns(_dataColumns);
322 //make sure a unique constraint doesn't already exists for these columns
323 UniqueConstraint uc = UniqueConstraint.GetUniqueConstraintForColumnSet(collection, this.Columns);
324 if (null != uc) throw new ArgumentException("Unique constraint already exists for these" +
325 " columns. Existing ConstraintName is " + uc.ConstraintName);
327 //Allow only one primary key
328 if (this.IsPrimaryKey)
330 uc = GetPrimaryKeyConstraint(collection);
331 if (null != uc) uc._isPrimaryKey = false;
335 //FIXME: ConstraintCollection calls AssertContraint() again rigth after calling
336 //this method, so that it is executed twice. Need to investigate which
337 // call to remove as that migth affect other parts of the classes.
342 internal override void RemoveFromConstraintCollectionCleanup(
343 ConstraintCollection collection)
348 internal override void AssertConstraint()
351 if (_dataTable == null) return; //???
352 if (_dataColumns == null) return; //???
356 DataTable tbl = _dataTable;
358 //TODO: Investigate other ways of speeding up the validation work below.
359 //FIXME: This only works when only one DataColumn has been specified.
361 //validate no duplicates exists.
362 //Only validate when there are at least 2 rows
363 //so that a duplicate migth exist.
364 if(tbl.Rows.Count > 1)
368 //get copy of rows collection first so that we do not modify the
370 ArrayList clonedDataList = (ArrayList)tbl.Rows.List.Clone();
372 ArrayList newDataList = new ArrayList();
374 //copy to array list only the column we are interested in.
375 foreach (DataRow row in clonedDataList)
377 newDataList.Add(row[this._dataColumns[0]]);
380 //sort ArrayList and check adjacent values for duplicates.
383 for( int i = 0 ; i < newDataList.Count - 1 ; i++)
385 if( newDataList[i].Equals(newDataList[i+1]) )
387 string msg = "Column '" + this._dataColumns[0] + "' contains non-unique values";
388 throw new InvalidConstraintException( msg );
397 internal override void AssertConstraint(DataRow row)
400 if (_dataTable == null) return; //???
401 if (_dataColumns == null) return; //???
405 DataTable tbl = _dataTable;
407 foreach(DataRow compareRow in tbl.Rows)
409 //skip if it is the same row to be validated
410 if(!row.Equals(compareRow))
412 if(compareRow.HasVersion (DataRowVersion.Original))
414 //FIXME: should we compare to compareRow[DataRowVersion.Current]?
415 //FIXME: We need to compare to all columns the constraint is set to.
416 if(row[_dataColumns[0], DataRowVersion.Proposed].Equals( compareRow[_dataColumns[0], DataRowVersion.Current]))
418 string ExceptionMessage;
419 ExceptionMessage = "Column '" + _dataColumns[0].ColumnName + "' is constrained to be unique.";
420 ExceptionMessage += " Value '" + row[_dataColumns[0], DataRowVersion.Proposed].ToString();
421 ExceptionMessage += "' is already present.";
423 throw new ConstraintException (ExceptionMessage);
434 #endregion // Methods