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 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
16 // Permission is hereby granted, free of charge, to any person obtaining
17 // a copy of this software and associated documentation files (the
18 // "Software"), to deal in the Software without restriction, including
19 // without limitation the rights to use, copy, modify, merge, publish,
20 // distribute, sublicense, and/or sell copies of the Software, and to
21 // permit persons to whom the Software is furnished to do so, subject to
22 // the following conditions:
24 // The above copyright notice and this permission notice shall be
25 // included in all copies or substantial portions of the Software.
27 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
37 using System.Collections;
38 using System.ComponentModel;
39 using System.Runtime.InteropServices;
41 namespace System.Data {
43 [DefaultProperty ("ConstraintName")]
45 public class UniqueConstraint : Constraint
47 private bool _isPrimaryKey = false;
48 private bool __isPrimaryKey = false;
49 private DataTable _dataTable; //set by ctor except when unique case
51 private DataColumn [] _dataColumns;
53 //TODO:provide helpers for this case
54 private string [] _dataColumnNames; //unique case
55 private bool _dataColsNotValidated;
60 public UniqueConstraint (DataColumn column)
62 _uniqueConstraint ("", column, false);
65 public UniqueConstraint (DataColumn[] columns)
67 _uniqueConstraint ("", columns, false);
70 public UniqueConstraint (DataColumn column, bool isPrimaryKey)
72 _uniqueConstraint ("", column, isPrimaryKey);
75 public UniqueConstraint (DataColumn[] columns, bool isPrimaryKey)
77 _uniqueConstraint ("", columns, isPrimaryKey);
80 public UniqueConstraint (string name, DataColumn column)
82 _uniqueConstraint (name, column, false);
85 public UniqueConstraint (string name, DataColumn[] columns)
87 _uniqueConstraint (name, columns, false);
90 public UniqueConstraint (string name, DataColumn column, bool isPrimaryKey)
92 _uniqueConstraint (name, column, isPrimaryKey);
95 public UniqueConstraint (string name, DataColumn[] columns, bool isPrimaryKey)
97 _uniqueConstraint (name, columns, isPrimaryKey);
100 //Special case. Can only be added to the Collection with AddRange
102 public UniqueConstraint (string name, string[] columnNames, bool isPrimaryKey)
104 _dataColsNotValidated = true;
106 //keep list of names to resolve later
107 _dataColumnNames = columnNames;
109 base.ConstraintName = name;
111 _isPrimaryKey = isPrimaryKey;
116 private void _uniqueConstraint(string name, DataColumn column, bool isPrimaryKey)
118 _dataColsNotValidated = false;
120 _validateColumn (column);
122 //Set Constraint Name
123 base.ConstraintName = name;
125 __isPrimaryKey = isPrimaryKey;
128 _dataColumns = new DataColumn [] {column};
130 //Get table reference
131 _dataTable = column.Table;
137 private void _uniqueConstraint(string name, DataColumn[] columns, bool isPrimaryKey)
139 _dataColsNotValidated = false;
142 _validateColumns (columns, out _dataTable);
144 //Set Constraint Name
145 base.ConstraintName = name;
148 _dataColumns = columns;
151 __isPrimaryKey = isPrimaryKey;
156 #endregion // Constructors
160 private void _validateColumns(DataColumn [] columns)
163 _validateColumns(columns, out table);
166 //Validates a collection of columns with the ctor rules
167 private void _validateColumns(DataColumn [] columns, out DataTable table) {
171 if (null == columns) throw new ArgumentNullException();
173 //check that there is at least one column
174 //LAMESPEC: not in spec
175 if (columns.Length < 1)
176 throw new InvalidConstraintException("Must be at least one column.");
178 DataTable compareTable = columns[0].Table;
180 foreach (DataColumn col in columns){
182 //check individual column rules
183 _validateColumn (col);
186 //check that columns are all from the same table??
187 //LAMESPEC: not in spec
188 if (compareTable != col.Table)
189 throw new InvalidConstraintException("Columns must be from the same table.");
193 table = compareTable;
196 //validates a column with the ctor rules
197 private void _validateColumn(DataColumn column) {
200 if (null == column) // FIXME: This is little weird, but here it goes...
201 throw new NullReferenceException("Object reference not set to an instance of an object.");
204 //column must belong to a table
205 //LAMESPEC: not in spec
206 if (null == column.Table)
207 throw new ArgumentException ("Column must belong to a table.");
211 /// If IsPrimaryKey is set to be true, this sets it true
213 internal void UpdatePrimaryKey ()
215 _isPrimaryKey = __isPrimaryKey;
216 // if unique constraint defined on single column
217 // the column becomes unique
218 if (_dataColumns.Length == 1){
219 // use SetUnique - because updating Unique property causes loop
220 _dataColumns[0].SetUnique();
224 internal static void SetAsPrimaryKey(ConstraintCollection collection, UniqueConstraint newPrimaryKey)
227 if (null == collection) throw new ArgumentNullException("ConstraintCollection can't be null.");
229 //make sure newPrimaryKey belongs to the collection parm unless it is null
230 if ( collection.IndexOf(newPrimaryKey) < 0 && (null != newPrimaryKey) )
231 throw new ArgumentException("newPrimaryKey must belong to collection.");
234 UniqueConstraint uc = GetPrimaryKeyConstraint(collection);
237 if (null != uc) uc._isPrimaryKey = false;
240 if (null != newPrimaryKey) newPrimaryKey._isPrimaryKey = true;
245 internal static UniqueConstraint GetPrimaryKeyConstraint(ConstraintCollection collection)
247 if (null == collection) throw new ArgumentNullException("Collection can't be null.");
250 IEnumerator enumer = collection.GetEnumerator();
251 while (enumer.MoveNext())
253 uc = enumer.Current as UniqueConstraint;
254 if (null == uc) continue;
256 if (uc.IsPrimaryKey) return uc;
259 //if we got here there was no pk
264 internal static UniqueConstraint GetUniqueConstraintForColumnSet(ConstraintCollection collection,
265 DataColumn[] columns)
267 if (null == collection) throw new ArgumentNullException("Collection can't be null.");
268 if (null == columns ) return null;
270 UniqueConstraint uniqueConstraint;
271 IEnumerator enumer = collection.GetEnumerator();
272 while (enumer.MoveNext())
274 uniqueConstraint = enumer.Current as UniqueConstraint;
275 if (uniqueConstraint != null)
277 if ( DataColumn.AreColumnSetsTheSame(uniqueConstraint.Columns, columns) )
279 return uniqueConstraint;
286 internal bool DataColsNotValidated {
288 get { return (_dataColsNotValidated);
292 // Helper Special Ctor
293 // Set the _dataTable property to the table to which this instance is bound when AddRange()
294 // is called with the special constructor.
295 // Validate whether the named columns exist in the _dataTable
296 internal void PostAddRange( DataTable _setTable ) {
298 _dataTable = _setTable;
299 DataColumn []cols = new DataColumn [_dataColumnNames.Length];
301 foreach ( string _columnName in _dataColumnNames ){
302 if ( _setTable.Columns.Contains (_columnName) ){
303 cols [i] = _setTable.Columns [_columnName];
307 throw( new InvalidConstraintException ( "The named columns must exist in the table" ));
317 [DataCategory ("Data")]
318 [DataSysDescription ("Indicates the columns of this constraint.")]
320 public virtual DataColumn[] Columns {
321 get { return _dataColumns; }
324 [DataCategory ("Data")]
325 [DataSysDescription ("Indicates if this constraint is a primary key.")]
326 public bool IsPrimaryKey {
327 get { return _isPrimaryKey; }
330 [DataCategory ("Data")]
331 [DataSysDescription ("Indicates the table of this constraint.")]
333 public override DataTable Table {
334 get { return _dataTable; }
337 #endregion // Properties
341 public override bool Equals(object key2) {
343 UniqueConstraint cst = key2 as UniqueConstraint;
344 if (null == cst) return false;
346 //according to spec if the cols are equal
347 //then two UniqueConstraints are equal
348 return DataColumn.AreColumnSetsTheSame(cst.Columns, this.Columns);
352 public override int GetHashCode()
354 //initialize hash with default value
358 //derive the hash code from the columns that way
359 //Equals and GetHashCode return Equal objects to be the
362 //Get the first column hash
363 if (this.Columns.Length > 0)
364 hash ^= this.Columns[0].GetHashCode();
366 //get the rest of the column hashes if there any
367 for (i = 1; i < this.Columns.Length; i++)
369 hash ^= this.Columns[1].GetHashCode();
377 internal override void AddToConstraintCollectionSetup(
378 ConstraintCollection collection)
380 //run Ctor rules again
381 _validateColumns(_dataColumns);
383 //make sure a unique constraint doesn't already exists for these columns
384 UniqueConstraint uc = UniqueConstraint.GetUniqueConstraintForColumnSet(collection, this.Columns);
385 if (null != uc) throw new ArgumentException("Unique constraint already exists for these" +
386 " columns. Existing ConstraintName is " + uc.ConstraintName);
388 //Allow only one primary key
389 if (this.IsPrimaryKey)
391 uc = GetPrimaryKeyConstraint(collection);
392 if (null != uc) uc._isPrimaryKey = false;
396 //FIXME: ConstraintCollection calls AssertContraint() again rigth after calling
397 //this method, so that it is executed twice. Need to investigate which
398 // call to remove as that migth affect other parts of the classes.
403 internal override void RemoveFromConstraintCollectionCleanup(
404 ConstraintCollection collection)
406 Index index = this.Index;
408 // if a foreign key constraint references the same index -
409 // change the index be to not unique.
410 // In this case we can not just drop the index
411 ICollection fkCollection = collection.ForeignKeyConstraints;
412 foreach (ForeignKeyConstraint fkc in fkCollection) {
413 if (index == fkc.Index) {
414 fkc.Index.SetUnique (false);
415 // this is not referencing the index anymore
420 // if we are here no one is using this index so we can remove it.
421 // There is no need calling drop index here
422 // since two unique constraints never references the same index
423 // and we already check that there is no foreign key constraint referencing it.
424 Table.RemoveIndex (index);
428 internal override void AssertConstraint()
430 if (_dataTable == null) return; //???
431 if (_dataColumns == null) return; //???
433 Index fromTableIndex = null;
435 fromTableIndex = Table.GetIndexByColumns (Columns);
436 if (fromTableIndex == null) {
437 Index = new Index (ConstraintName, _dataTable, _dataColumns, true);
440 fromTableIndex.SetUnique (true);
441 Index = fromTableIndex;
446 Table.InitializeIndex (Index);
448 catch (ConstraintException) {
453 throw new ArgumentException (String.Format ("Column '{0}' contains non-unique values", this._dataColumns[0]));
457 // if there is no index with same columns - add the new index to the table.
458 if (fromTableIndex == null)
459 Table.AddIndex (Index);
463 internal override void AssertConstraint(DataRow row)
465 if (_dataTable == null) return; //???
466 if (_dataColumns == null) return; //???
469 Index = Table.GetIndexByColumns (Columns, true);
471 Index = new Index (ConstraintName, _dataTable, _dataColumns, true);
472 Table.AddIndex (Index);
478 for (int i = 0; i < _dataColumns.Length; i++) {
479 val = row[_dataColumns[i]];
480 if (val == null || val == DBNull.Value)
481 throw new NoNullAllowedException("Column '" + _dataColumns[i].ColumnName + "' does not allow nulls.");
488 catch (ConstraintException) {
489 throw new ConstraintException(GetErrorMessage(row));
493 private string GetErrorMessage(DataRow row)
497 System.Text.StringBuilder sb = new System.Text.StringBuilder(row[_dataColumns[0]].ToString());
498 for (i = 1; i < _dataColumns.Length; i++)
499 sb = sb.Append(", ").Append(row[_dataColumns[i].ColumnName]);
500 string valStr = sb.ToString();
501 sb = new System.Text.StringBuilder(_dataColumns[0].ColumnName);
502 for (i = 1; i < _dataColumns.Length; i++)
503 sb = sb.Append(", ").Append(_dataColumns[i].ColumnName);
504 string colStr = sb.ToString();
505 return "Column '" + colStr + "' is constrained to be unique. Value '" + valStr + "' is already present.";
508 internal bool Contains (DataColumn c)
510 foreach (DataColumn col in Columns)
517 #endregion // Methods