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;
40 using System.Data.Common;
42 namespace System.Data {
43 [Editor ("Microsoft.VSDesigner.Data.Design.UniqueConstraintEditor, " + Consts.AssemblyMicrosoft_VSDesigner,
44 "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
45 [DefaultProperty ("ConstraintName")]
47 public class UniqueConstraint : Constraint
49 private bool _isPrimaryKey = false;
50 private bool _belongsToCollection = false;
51 private DataTable _dataTable; //set by ctor except when unique case
53 //FIXME: create a class which will wrap this collection
54 private DataColumn [] _dataColumns;
56 //TODO:provide helpers for this case
57 private string [] _dataColumnNames; //unique case
58 private bool _dataColsNotValidated;
63 public UniqueConstraint (DataColumn column)
65 _uniqueConstraint ("", column, false);
68 public UniqueConstraint (DataColumn[] columns)
70 _uniqueConstraint ("", columns, false);
73 public UniqueConstraint (DataColumn column, bool isPrimaryKey)
75 _uniqueConstraint ("", column, isPrimaryKey);
78 public UniqueConstraint (DataColumn[] columns, bool isPrimaryKey)
80 _uniqueConstraint ("", columns, isPrimaryKey);
83 public UniqueConstraint (string name, DataColumn column)
85 _uniqueConstraint (name, column, false);
88 public UniqueConstraint (string name, DataColumn[] columns)
90 _uniqueConstraint (name, columns, false);
93 public UniqueConstraint (string name, DataColumn column, bool isPrimaryKey)
95 _uniqueConstraint (name, column, isPrimaryKey);
98 public UniqueConstraint (string name, DataColumn[] columns, bool isPrimaryKey)
100 _uniqueConstraint (name, columns, isPrimaryKey);
103 //Special case. Can only be added to the Collection with AddRange
105 public UniqueConstraint (string name, string[] columnNames, bool isPrimaryKey)
107 _dataColsNotValidated = true;
109 //keep list of names to resolve later
110 _dataColumnNames = columnNames;
112 base.ConstraintName = name;
114 _isPrimaryKey = isPrimaryKey;
119 private void _uniqueConstraint(string name, DataColumn column, bool isPrimaryKey)
121 _dataColsNotValidated = false;
123 _validateColumn (column);
125 //Set Constraint Name
126 base.ConstraintName = name;
128 _isPrimaryKey = isPrimaryKey;
131 _dataColumns = new DataColumn [] {column};
133 //Get table reference
134 _dataTable = column.Table;
138 private void _uniqueConstraint(string name, DataColumn[] columns, bool isPrimaryKey)
140 _dataColsNotValidated = false;
143 _validateColumns (columns, out _dataTable);
145 //Set Constraint Name
146 base.ConstraintName = name;
149 _dataColumns = columns;
152 _isPrimaryKey = isPrimaryKey;
155 #endregion // Constructors
159 private void _validateColumns(DataColumn [] columns)
162 _validateColumns(columns, out table);
165 //Validates a collection of columns with the ctor rules
166 private void _validateColumns(DataColumn [] columns, out DataTable table) {
170 if (null == columns) throw new ArgumentNullException();
172 //check that there is at least one column
173 //LAMESPEC: not in spec
174 if (columns.Length < 1)
175 throw new InvalidConstraintException("Must be at least one column.");
177 DataTable compareTable = columns[0].Table;
179 foreach (DataColumn col in columns){
181 //check individual column rules
182 _validateColumn (col);
185 //check that columns are all from the same table??
186 //LAMESPEC: not in spec
187 if (compareTable != col.Table)
188 throw new InvalidConstraintException("Columns must be from the same table.");
192 table = compareTable;
195 //validates a column with the ctor rules
196 private void _validateColumn(DataColumn column) {
199 if (null == column) // FIXME: This is little weird, but here it goes...
200 throw new NullReferenceException("Object reference not set to an instance of an object.");
203 //column must belong to a table
204 //LAMESPEC: not in spec
205 if (null == column.Table)
206 throw new ArgumentException ("Column must belong to a table.");
209 internal static void SetAsPrimaryKey(ConstraintCollection collection, UniqueConstraint newPrimaryKey)
212 if (null == collection) throw new ArgumentNullException("ConstraintCollection can't be null.");
214 //make sure newPrimaryKey belongs to the collection parm unless it is null
215 if ( collection.IndexOf(newPrimaryKey) < 0 && (null != newPrimaryKey) )
216 throw new ArgumentException("newPrimaryKey must belong to collection.");
219 UniqueConstraint uc = GetPrimaryKeyConstraint(collection);
222 if (null != uc) uc._isPrimaryKey = false;
225 if (null != newPrimaryKey) newPrimaryKey._isPrimaryKey = true;
230 internal static UniqueConstraint GetPrimaryKeyConstraint(ConstraintCollection collection)
232 if (null == collection) throw new ArgumentNullException("Collection can't be null.");
235 IEnumerator enumer = collection.GetEnumerator();
236 while (enumer.MoveNext())
238 uc = enumer.Current as UniqueConstraint;
239 if (null == uc) continue;
241 if (uc.IsPrimaryKey) return uc;
244 //if we got here there was no pk
249 internal static UniqueConstraint GetUniqueConstraintForColumnSet(ConstraintCollection collection,
250 DataColumn[] columns)
252 if (null == collection) throw new ArgumentNullException("Collection can't be null.");
253 if (null == columns ) return null;
255 foreach(Constraint constraint in collection) {
256 if (constraint is UniqueConstraint) {
257 UniqueConstraint uc = constraint as UniqueConstraint;
258 if ( DataColumn.AreColumnSetsTheSame(uc.Columns, columns) ) {
266 internal bool DataColsNotValidated
269 return (_dataColsNotValidated);
273 // Helper Special Ctor
274 // Set the _dataTable property to the table to which this instance is bound when AddRange()
275 // is called with the special constructor.
276 // Validate whether the named columns exist in the _dataTable
277 internal void PostAddRange( DataTable _setTable )
279 _dataTable = _setTable;
280 DataColumn []cols = new DataColumn [_dataColumnNames.Length];
282 foreach ( string _columnName in _dataColumnNames ) {
283 if ( _setTable.Columns.Contains (_columnName) ) {
284 cols [i] = _setTable.Columns [_columnName];
288 throw( new InvalidConstraintException ( "The named columns must exist in the table" ));
298 [DataCategory ("Data")]
299 [DataSysDescription ("Indicates the columns of this constraint.")]
301 public virtual DataColumn[] Columns {
302 get { return _dataColumns; }
305 [DataCategory ("Data")]
306 [DataSysDescription ("Indicates if this constraint is a primary key.")]
307 public bool IsPrimaryKey {
309 if (Table == null || (!_belongsToCollection)) {
312 return _isPrimaryKey;
316 [DataCategory ("Data")]
317 [DataSysDescription ("Indicates the table of this constraint.")]
319 public override DataTable Table {
320 get { return _dataTable; }
323 #endregion // Properties
327 public override bool Equals(object key2) {
329 UniqueConstraint cst = key2 as UniqueConstraint;
330 if (null == cst) return false;
332 //according to spec if the cols are equal
333 //then two UniqueConstraints are equal
334 return DataColumn.AreColumnSetsTheSame(cst.Columns, this.Columns);
338 public override int GetHashCode()
340 //initialize hash with default value
344 //derive the hash code from the columns that way
345 //Equals and GetHashCode return Equal objects to be the
348 //Get the first column hash
349 if (this.Columns.Length > 0)
350 hash ^= this.Columns[0].GetHashCode();
352 //get the rest of the column hashes if there any
353 for (i = 1; i < this.Columns.Length; i++)
355 hash ^= this.Columns[1].GetHashCode();
362 internal override void AddToConstraintCollectionSetup(
363 ConstraintCollection collection)
365 for (int i = 0; i < Columns.Length; i++)
366 if (Columns[i].Table != collection.Table)
367 throw new ArgumentException("These columns don't point to this table.");
368 //run Ctor rules again
369 _validateColumns(_dataColumns);
371 //make sure a unique constraint doesn't already exists for these columns
372 UniqueConstraint uc = UniqueConstraint.GetUniqueConstraintForColumnSet(collection, this.Columns);
373 if (null != uc) throw new ArgumentException("Unique constraint already exists for these" +
374 " columns. Existing ConstraintName is " + uc.ConstraintName);
376 //Allow only one primary key
377 if (this.IsPrimaryKey) {
378 uc = GetPrimaryKeyConstraint(collection);
379 if (null != uc) uc._isPrimaryKey = false;
382 // if constraint is based on one column only
383 // this column becomes unique
384 if (_dataColumns.Length == 1) {
385 _dataColumns[0].SetUnique();
388 //FIXME: ConstraintCollection calls AssertContraint() again rigth after calling
389 //this method, so that it is executed twice. Need to investigate which
390 // call to remove as that migth affect other parts of the classes.
391 //AssertConstraint();
392 if (IsConstraintViolated())
393 throw new ArgumentException("These columns don't currently have unique values.");
395 _belongsToCollection = true;
399 internal override void RemoveFromConstraintCollectionCleanup(
400 ConstraintCollection collection)
402 _belongsToCollection = false;
407 internal override bool IsConstraintViolated()
410 Index = Table.GetIndex(Columns,null,DataViewRowState.None,null,false);
413 if (Index.HasDuplicates) {
414 int[] dups = Index.Duplicates;
415 for (int i = 0; i < dups.Length; i++){
416 DataRow row = Table.RecordCache[dups[i]];
417 ArrayList columns = new ArrayList();
418 ArrayList values = new ArrayList();
419 foreach (DataColumn col in Columns){
420 columns.Add(col.ColumnName);
421 values.Add(row[col].ToString());
424 string columnNames = String.Join(",", (string[])columns.ToArray(typeof(string)));
425 string columnValues = String.Join(",", (string[])values.ToArray(typeof(string)));
427 row.RowError = String.Format("Column(s) '{0}' are constrained to be unique. Value(s) '{1}' are already present", columnNames, columnValues);
429 // FIXME : check the exception to be thrown here
430 // throw new ConstraintException("These columns don't currently have unique values");
431 //throw new ConstraintException ("Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints.");
438 internal override void AssertConstraint(DataRow row)
440 if (IsPrimaryKey && row.HasVersion(DataRowVersion.Default)) {
441 for (int i = 0; i < Columns.Length; i++) {
442 if (row.IsNull(Columns[i])) {
443 throw new NoNullAllowedException("Column '" + Columns[i].ColumnName + "' does not allow nulls.");
449 Index = Table.GetIndex(Columns,null,DataViewRowState.None,null,false);
452 if (Index.HasDuplicates) {
453 throw new ConstraintException(GetErrorMessage(row));
457 internal override bool IsColumnContained(DataColumn column)
459 for (int i = 0; i < _dataColumns.Length; i++)
460 if (column == _dataColumns[i])
466 internal override bool CanRemoveFromCollection(ConstraintCollection col, bool shouldThrow){
467 if (Equals(col.Table.PrimaryKey)){
469 throw new ArgumentException("Cannot remove unique constraint since it's the primary key of a table.");
474 if (Table.DataSet != null){
475 foreach (DataTable table in Table.DataSet.Tables){
476 foreach (Constraint constraint in table.Constraints){
477 if (constraint is ForeignKeyConstraint)
478 if (((ForeignKeyConstraint)constraint).RelatedTable == Table){
480 throw new ArgumentException(
481 String.Format("Cannot remove unique constraint '{0}'. Remove foreign key constraint '{1}' first.",
482 ConstraintName, constraint.ConstraintName)
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]);
501 string valStr = sb.ToString();
502 sb = new System.Text.StringBuilder(_dataColumns[0].ColumnName);
503 for (i = 1; i < _dataColumns.Length; i++) {
504 sb = sb.Append(", ").Append(_dataColumns[i].ColumnName);
506 string colStr = sb.ToString();
507 return "Column '" + colStr + "' is constrained to be unique. Value '" + valStr + "' is already present.";
511 #endregion // Methods