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
35 public UniqueConstraint (DataColumn column)
37 _uniqueConstraint ("", column, false);
40 public UniqueConstraint (DataColumn[] columns)
42 _uniqueConstraint ("", columns, false);
45 public UniqueConstraint (DataColumn column, bool isPrimaryKey)
47 _uniqueConstraint ("", column, isPrimaryKey);
50 public UniqueConstraint (DataColumn[] columns, bool isPrimaryKey)
52 _uniqueConstraint ("", columns, isPrimaryKey);
55 public UniqueConstraint (string name, DataColumn column)
57 _uniqueConstraint (name, column, false);
60 public UniqueConstraint (string name, DataColumn[] columns)
62 _uniqueConstraint (name, columns, false);
65 public UniqueConstraint (string name, DataColumn column, bool isPrimaryKey)
67 _uniqueConstraint (name, column, isPrimaryKey);
70 public UniqueConstraint (string name, DataColumn[] columns, bool isPrimaryKey)
72 _uniqueConstraint (name, columns, isPrimaryKey);
75 //Special case. Can only be added to the Collection with AddRange
78 public UniqueConstraint (string name, string[] columnNames, bool isPrimaryKey)
80 throw new NotImplementedException(); //need to finish related logic
82 base.ConstraintName = name;
85 //must set unique when added to the collection
87 //keep list of names to resolve later
88 _dataColumnNames = columnNames;
90 _isPrimaryKey = isPrimaryKey;
95 private void _uniqueConstraint(string name, DataColumn column, bool isPrimaryKey)
98 _validateColumn (column);
100 //Set Constraint Name
101 base.ConstraintName = name;
103 __isPrimaryKey = isPrimaryKey;
106 _dataColumns = new DataColumn [] {column};
108 //Get table reference
109 _dataTable = column.Table;
115 private void _uniqueConstraint(string name, DataColumn[] columns, bool isPrimaryKey)
118 _validateColumns (columns, out _dataTable);
120 //Set Constraint Name
121 base.ConstraintName = name;
124 _dataColumns = columns;
127 __isPrimaryKey = isPrimaryKey;
132 #endregion // Constructors
136 private void _validateColumns(DataColumn [] columns)
139 _validateColumns(columns, out table);
142 //Validates a collection of columns with the ctor rules
143 private void _validateColumns(DataColumn [] columns, out DataTable table) {
147 if (null == columns) throw new ArgumentNullException();
149 //check that there is at least one column
150 //LAMESPEC: not in spec
151 if (columns.Length < 1)
152 throw new InvalidConstraintException("Must be at least one column.");
154 DataTable compareTable = columns[0].Table;
156 foreach (DataColumn col in columns){
158 //check individual column rules
159 _validateColumn (col);
162 //check that columns are all from the same table??
163 //LAMESPEC: not in spec
164 if (compareTable != col.Table)
165 throw new InvalidConstraintException("Columns must be from the same table.");
169 table = compareTable;
172 //validates a column with the ctor rules
173 private void _validateColumn(DataColumn column) {
176 if (null == column) // FIXME: This is little weird, but here it goes...
177 throw new NullReferenceException("Object reference not set to an instance of an object.");
180 //column must belong to a table
181 //LAMESPEC: not in spec
182 if (null == column.Table)
183 throw new ArgumentException ("Column must belong to a table.");
187 /// If IsPrimaryKey is set to be true, this sets it true
189 internal void UpdatePrimaryKey ()
191 _isPrimaryKey = __isPrimaryKey;
192 foreach (DataColumn Col in _dataColumns)
197 internal static void SetAsPrimaryKey(ConstraintCollection collection, UniqueConstraint newPrimaryKey)
200 if (null == collection) throw new ArgumentNullException("ConstraintCollection can't be null.");
202 //make sure newPrimaryKey belongs to the collection parm unless it is null
203 if ( collection.IndexOf(newPrimaryKey) < 0 && (null != newPrimaryKey) )
204 throw new ArgumentException("newPrimaryKey must belong to collection.");
207 UniqueConstraint uc = GetPrimaryKeyConstraint(collection);
210 if (null != uc) uc._isPrimaryKey = false;
213 if (null != newPrimaryKey) newPrimaryKey._isPrimaryKey = true;
218 internal static UniqueConstraint GetPrimaryKeyConstraint(ConstraintCollection collection)
220 if (null == collection) throw new ArgumentNullException("Collection can't be null.");
223 IEnumerator enumer = collection.GetEnumerator();
224 while (enumer.MoveNext())
226 uc = enumer.Current as UniqueConstraint;
227 if (null == uc) continue;
229 if (uc.IsPrimaryKey) return uc;
232 //if we got here there was no pk
237 internal static UniqueConstraint GetUniqueConstraintForColumnSet(ConstraintCollection collection,
238 DataColumn[] columns)
240 if (null == collection) throw new ArgumentNullException("Collection can't be null.");
241 if (null == columns ) return null;
243 UniqueConstraint uniqueConstraint;
244 IEnumerator enumer = collection.GetEnumerator();
245 while (enumer.MoveNext())
247 uniqueConstraint = enumer.Current as UniqueConstraint;
248 if (uniqueConstraint != null)
250 if ( DataColumn.AreColumnSetsTheSame(uniqueConstraint.Columns, columns) )
252 return uniqueConstraint;
263 [DataCategory ("Data")]
264 [DataSysDescription ("Indicates the columns of this constraint.")]
266 public virtual DataColumn[] Columns {
267 get { return _dataColumns; }
270 [DataCategory ("Data")]
271 [DataSysDescription ("Indicates if this constraint is a primary key.")]
273 public bool IsPrimaryKey {
274 get { return _isPrimaryKey; }
277 [DataCategory ("Data")]
278 [DataSysDescription ("Indicates the table of this constraint.")]
280 public override DataTable Table {
281 get { return _dataTable; }
284 #endregion // Properties
288 public override bool Equals(object key2) {
290 UniqueConstraint cst = key2 as UniqueConstraint;
291 if (null == cst) return false;
293 //according to spec if the cols are equal
294 //then two UniqueConstraints are equal
295 return DataColumn.AreColumnSetsTheSame(cst.Columns, this.Columns);
299 public override int GetHashCode()
301 //initialize hash with default value
305 //derive the hash code from the columns that way
306 //Equals and GetHashCode return Equal objects to be the
309 //Get the first column hash
310 if (this.Columns.Length > 0)
311 hash ^= this.Columns[0].GetHashCode();
313 //get the rest of the column hashes if there any
314 for (i = 1; i < this.Columns.Length; i++)
316 hash ^= this.Columns[1].GetHashCode();
324 internal override void AddToConstraintCollectionSetup(
325 ConstraintCollection collection)
327 //run Ctor rules again
328 _validateColumns(_dataColumns);
330 //make sure a unique constraint doesn't already exists for these columns
331 UniqueConstraint uc = UniqueConstraint.GetUniqueConstraintForColumnSet(collection, this.Columns);
332 if (null != uc) throw new ArgumentException("Unique constraint already exists for these" +
333 " columns. Existing ConstraintName is " + uc.ConstraintName);
335 //Allow only one primary key
336 if (this.IsPrimaryKey)
338 uc = GetPrimaryKeyConstraint(collection);
339 if (null != uc) uc._isPrimaryKey = false;
343 //FIXME: ConstraintCollection calls AssertContraint() again rigth after calling
344 //this method, so that it is executed twice. Need to investigate which
345 // call to remove as that migth affect other parts of the classes.
350 internal override void RemoveFromConstraintCollectionCleanup(
351 ConstraintCollection collection)
353 Index index = this.Index;
355 // if a foreign key constraint references the same index -
356 // change the index be to not unique.
357 // In this case we can not just drop the index
358 ICollection fkCollection = collection.ForeignKeyConstraints;
359 foreach (ForeignKeyConstraint fkc in fkCollection) {
360 if (index == fkc.Index) {
361 fkc.Index.SetUnique (false);
362 // this is not referencing the index anymore
367 // if we are here no one is using this index so we can remove it.
368 // There is no need calling drop index here
369 // since two unique constraints never references the same index
370 // and we already check that there is no foreign key constraint referencing it.
371 Table.RemoveIndex (index);
375 internal override void AssertConstraint()
377 if (_dataTable == null) return; //???
378 if (_dataColumns == null) return; //???
380 Index fromTableIndex = null;
382 fromTableIndex = Table.GetIndexByColumns (Columns);
383 if (fromTableIndex == null) {
384 Index = new Index (ConstraintName, _dataTable, _dataColumns, true);
387 fromTableIndex.SetUnique (true);
388 Index = fromTableIndex;
393 Table.InitializeIndex (Index);
395 catch (ConstraintException) {
397 throw new ArgumentException (String.Format ("Column '{0}' contains non-unique values", this._dataColumns[0]));
400 // if there is no index with same columns - add the new index to the table.
401 if (fromTableIndex == null)
402 Table.AddIndex (Index);
406 internal override void AssertConstraint(DataRow row)
408 if (_dataTable == null) return; //???
409 if (_dataColumns == null) return; //???
412 Index = Table.GetIndexByColumns (Columns, true);
414 Index = new Index (ConstraintName, _dataTable, _dataColumns, true);
415 Table.AddIndex (Index);
420 for (int i = 0; i < _dataColumns.Length; i++) {
422 val = row[_dataColumns[i]];
423 if (val == null || val == DBNull.Value)
424 throw new NoNullAllowedException("Column '" + _dataColumns[i].ColumnName + "' does not allow nulls.");
430 catch (ConstraintException) {
431 throw new ConstraintException(GetErrorMessage(row));
435 private string GetErrorMessage(DataRow row)
439 System.Text.StringBuilder sb = new System.Text.StringBuilder(row[_dataColumns[0]].ToString());
440 for (i = 1; i < _dataColumns.Length; i++)
441 sb = sb.Append(", ").Append(row[_dataColumns[i].ColumnName]);
442 string valStr = sb.ToString();
443 sb = new System.Text.StringBuilder(_dataColumns[0].ColumnName);
444 for (i = 1; i < _dataColumns.Length; i++)
445 sb = sb.Append(", ").Append(_dataColumns[i].ColumnName);
446 string colStr = sb.ToString();
447 return "Column '" + colStr + "' is constrained to be unique. Value '" + valStr + "' is already present.";
451 #endregion // Methods