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")]
49 public class UniqueConstraint : Constraint
51 private bool _isPrimaryKey = false;
52 private bool _belongsToCollection = false;
53 private DataTable _dataTable; //set by ctor except when unique case
55 //FIXME: create a class which will wrap this collection
56 private DataColumn [] _dataColumns;
58 //TODO:provide helpers for this case
59 private string [] _dataColumnNames; //unique case
60 private ForeignKeyConstraint _childConstraint = null;
64 public UniqueConstraint (DataColumn column)
66 _uniqueConstraint ("", column, false);
69 public UniqueConstraint (DataColumn[] columns)
71 _uniqueConstraint ("", columns, false);
74 public UniqueConstraint (DataColumn column, bool isPrimaryKey)
76 _uniqueConstraint ("", column, isPrimaryKey);
79 public UniqueConstraint (DataColumn[] columns, bool isPrimaryKey)
81 _uniqueConstraint ("", columns, isPrimaryKey);
84 public UniqueConstraint (string name, DataColumn column)
86 _uniqueConstraint (name, column, false);
89 public UniqueConstraint (string name, DataColumn[] columns)
91 _uniqueConstraint (name, columns, false);
94 public UniqueConstraint (string name, DataColumn column, bool isPrimaryKey)
96 _uniqueConstraint (name, column, isPrimaryKey);
99 public UniqueConstraint (string name, DataColumn[] columns, bool isPrimaryKey)
101 _uniqueConstraint (name, columns, isPrimaryKey);
104 //Special case. Can only be added to the Collection with AddRange
106 public UniqueConstraint (string name, string[] columnNames, bool isPrimaryKey)
108 InitInProgress = true;
110 //keep list of names to resolve later
111 _dataColumnNames = columnNames;
112 base.ConstraintName = name;
113 _isPrimaryKey = isPrimaryKey;
117 private void _uniqueConstraint(string name, DataColumn column, bool isPrimaryKey)
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;
135 private void _uniqueConstraint(string name, DataColumn[] columns, bool isPrimaryKey)
138 _validateColumns (columns, out _dataTable);
140 //Set Constraint Name
141 base.ConstraintName = name;
144 _dataColumns = columns;
147 _isPrimaryKey = isPrimaryKey;
150 #endregion // Constructors
154 private void _validateColumns(DataColumn [] columns)
157 _validateColumns(columns, out table);
160 //Validates a collection of columns with the ctor rules
161 private void _validateColumns(DataColumn [] columns, out DataTable table) {
165 if (null == columns) throw new ArgumentNullException();
167 //check that there is at least one column
168 //LAMESPEC: not in spec
169 if (columns.Length < 1)
170 throw new InvalidConstraintException("Must be at least one column.");
172 DataTable compareTable = columns[0].Table;
174 foreach (DataColumn col in columns){
176 //check individual column rules
177 _validateColumn (col);
180 //check that columns are all from the same table??
181 //LAMESPEC: not in spec
182 if (compareTable != col.Table)
183 throw new InvalidConstraintException("Columns must be from the same table.");
187 table = compareTable;
190 //validates a column with the ctor rules
191 private void _validateColumn(DataColumn column) {
194 if (null == column) // FIXME: This is little weird, but here it goes...
195 throw new NullReferenceException("Object reference not set to an instance of an object.");
198 //column must belong to a table
199 //LAMESPEC: not in spec
200 if (null == column.Table)
201 throw new ArgumentException ("Column must belong to a table.");
204 internal static void SetAsPrimaryKey(ConstraintCollection collection, UniqueConstraint newPrimaryKey)
207 if (null == collection) throw new ArgumentNullException("ConstraintCollection can't be null.");
209 //make sure newPrimaryKey belongs to the collection parm unless it is null
210 if ( collection.IndexOf(newPrimaryKey) < 0 && (null != newPrimaryKey) )
211 throw new ArgumentException("newPrimaryKey must belong to collection.");
214 UniqueConstraint uc = GetPrimaryKeyConstraint(collection);
217 if (null != uc) uc._isPrimaryKey = false;
220 if (null != newPrimaryKey) newPrimaryKey._isPrimaryKey = true;
225 internal static UniqueConstraint GetPrimaryKeyConstraint(ConstraintCollection collection)
227 if (null == collection) throw new ArgumentNullException("Collection can't be null.");
230 IEnumerator enumer = collection.GetEnumerator();
231 while (enumer.MoveNext())
233 uc = enumer.Current as UniqueConstraint;
234 if (null == uc) continue;
236 if (uc.IsPrimaryKey) return uc;
239 //if we got here there was no pk
244 internal static UniqueConstraint GetUniqueConstraintForColumnSet(ConstraintCollection collection,
245 DataColumn[] columns)
247 if (null == collection) throw new ArgumentNullException("Collection can't be null.");
248 if (null == columns ) return null;
250 foreach(Constraint constraint in collection) {
251 if (constraint is UniqueConstraint) {
252 UniqueConstraint uc = constraint as UniqueConstraint;
253 if ( DataColumn.AreColumnSetsTheSame(uc.Columns, columns) ) {
261 internal ForeignKeyConstraint ChildConstraint {
262 get { return _childConstraint; }
263 set { _childConstraint = value; }
266 // Helper Special Ctor
267 // Set the _dataTable property to the table to which this instance is bound when AddRange()
268 // is called with the special constructor.
269 // Validate whether the named columns exist in the _dataTable
270 internal override void FinishInit (DataTable _setTable)
272 _dataTable = _setTable;
273 if (_isPrimaryKey == true && _setTable.PrimaryKey.Length != 0)
274 throw new ArgumentException ("Cannot add primary key constraint since primary key" +
275 "is already set for the table");
277 DataColumn[] cols = new DataColumn [_dataColumnNames.Length];
280 foreach (string _columnName in _dataColumnNames) {
281 if (_setTable.Columns.Contains (_columnName)) {
282 cols [i] = _setTable.Columns [_columnName];
286 throw(new InvalidConstraintException ("The named columns must exist in the table"));
289 _validateColumns (cols);
290 InitInProgress = false;
298 [DataCategory ("Data")]
300 [DataSysDescription ("Indicates the columns of this constraint.")]
303 public virtual DataColumn[] Columns {
304 get { return _dataColumns; }
307 [DataCategory ("Data")]
309 [DataSysDescription ("Indicates if this constraint is a primary key.")]
311 public bool IsPrimaryKey {
313 if (Table == null || (!_belongsToCollection)) {
316 return _isPrimaryKey;
320 [DataCategory ("Data")]
322 [DataSysDescription ("Indicates the table of this constraint.")]
325 public override DataTable Table {
326 get { return _dataTable; }
329 #endregion // Properties
333 internal void SetIsPrimaryKey (bool value)
335 _isPrimaryKey = value;
338 public override bool Equals(object key2) {
340 UniqueConstraint cst = key2 as UniqueConstraint;
341 if (null == cst) return false;
343 //according to spec if the cols are equal
344 //then two UniqueConstraints are equal
345 return DataColumn.AreColumnSetsTheSame(cst.Columns, this.Columns);
349 public override int GetHashCode()
351 //initialize hash with default value
355 //derive the hash code from the columns that way
356 //Equals and GetHashCode return Equal objects to be the
359 //Get the first column hash
360 if (this.Columns.Length > 0)
361 hash ^= this.Columns[0].GetHashCode();
363 //get the rest of the column hashes if there any
364 for (i = 1; i < this.Columns.Length; i++)
366 hash ^= this.Columns[1].GetHashCode();
373 internal override void AddToConstraintCollectionSetup(
374 ConstraintCollection collection)
376 for (int i = 0; i < Columns.Length; i++)
377 if (Columns[i].Table != collection.Table)
378 throw new ArgumentException("These columns don't point to this table.");
379 //run Ctor rules again
380 _validateColumns(_dataColumns);
382 //make sure a unique constraint doesn't already exists for these columns
383 UniqueConstraint uc = UniqueConstraint.GetUniqueConstraintForColumnSet(collection, this.Columns);
384 if (null != uc) throw new ArgumentException("Unique constraint already exists for these" +
385 " columns. Existing ConstraintName is " + uc.ConstraintName);
387 //Allow only one primary key
388 if (this.IsPrimaryKey) {
389 uc = GetPrimaryKeyConstraint(collection);
390 if (null != uc) uc._isPrimaryKey = false;
393 // if constraint is based on one column only
394 // this column becomes unique
395 if (_dataColumns.Length == 1) {
396 _dataColumns[0].SetUnique();
399 if (IsConstraintViolated())
400 throw new ArgumentException("These columns don't currently have unique values.");
402 _belongsToCollection = true;
406 internal override void RemoveFromConstraintCollectionCleanup(
407 ConstraintCollection collection)
409 if (Columns.Length == 1)
410 Columns [0].Unique = false;
412 _belongsToCollection = false;
417 internal override bool IsConstraintViolated()
420 Index = Table.GetIndex(Columns,null,DataViewRowState.None,null,false);
423 if (Index.HasDuplicates) {
424 int[] dups = Index.Duplicates;
425 for (int i = 0; i < dups.Length; i++){
426 DataRow row = Table.RecordCache[dups[i]];
427 ArrayList columns = new ArrayList();
428 ArrayList values = new ArrayList();
429 foreach (DataColumn col in Columns){
430 columns.Add(col.ColumnName);
431 values.Add(row[col].ToString());
434 string columnNames = String.Join(", ", (string[])columns.ToArray(typeof(string)));
435 string columnValues = String.Join(", ", (string[])values.ToArray(typeof(string)));
437 row.RowError = String.Format("Column '{0}' is constrained to be unique. Value '{1}' is already present.", columnNames, columnValues);
438 for (int j=0; j < Columns.Length; ++j)
439 row.SetColumnError (Columns [j], row.RowError);
446 internal override void AssertConstraint(DataRow row)
448 if (IsPrimaryKey && row.HasVersion(DataRowVersion.Default)) {
449 for (int i = 0; i < Columns.Length; i++) {
450 if (row.IsNull(Columns[i])) {
451 throw new NoNullAllowedException("Column '" + Columns[i].ColumnName + "' does not allow nulls.");
457 Index = Table.GetIndex(Columns,null,DataViewRowState.None,null,false);
460 if (Index.HasDuplicates) {
461 throw new ConstraintException(GetErrorMessage(row));
465 internal override bool IsColumnContained(DataColumn column)
467 for (int i = 0; i < _dataColumns.Length; i++)
468 if (column == _dataColumns[i])
474 internal override bool CanRemoveFromCollection(ConstraintCollection col, bool shouldThrow){
477 throw new ArgumentException("Cannot remove unique constraint since it's the primary key of a table.");
482 if (Table.DataSet == null)
485 if (ChildConstraint != null) {
488 throw new ArgumentException (String.Format (
489 "Cannot remove unique constraint '{0}'." +
490 "Remove foreign key constraint '{1}' first.",
491 ConstraintName,ChildConstraint.ConstraintName));
496 private string GetErrorMessage(DataRow row)
500 System.Text.StringBuilder sb = new System.Text.StringBuilder(row[_dataColumns[0]].ToString());
501 for (i = 1; i < _dataColumns.Length; i++) {
502 sb = sb.Append(", ").Append(row[_dataColumns[i].ColumnName]);
504 string valStr = sb.ToString();
505 sb = new System.Text.StringBuilder(_dataColumns[0].ColumnName);
506 for (i = 1; i < _dataColumns.Length; i++) {
507 sb = sb.Append(", ").Append(_dataColumns[i].ColumnName);
509 string colStr = sb.ToString();
510 return "Column '" + colStr + "' is constrained to be unique. Value '" + valStr + "' is already present.";
514 #endregion // Methods