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 _dataColumnNames = new string [columnNames.Length];
111 for (int i = 0; i < columnNames.Length; i++)
112 _dataColumnNames[i] = columnNames[i];
114 base.ConstraintName = name;
115 _isPrimaryKey = isPrimaryKey;
119 private void _uniqueConstraint(string name, DataColumn column, bool isPrimaryKey)
122 _validateColumn (column);
124 //Set Constraint Name
125 base.ConstraintName = name;
127 _isPrimaryKey = isPrimaryKey;
130 _dataColumns = new DataColumn [] {column};
132 //Get table reference
133 _dataTable = column.Table;
137 private void _uniqueConstraint(string name, DataColumn[] columns, bool isPrimaryKey)
140 _validateColumns (columns, out _dataTable);
142 //Set Constraint Name
143 base.ConstraintName = name;
145 //copy the columns - Do not keep reference #672113
146 //_dataColumns = columns;
150 _isPrimaryKey = isPrimaryKey;
153 #endregion // Constructors
157 private void _validateColumns(DataColumn [] columns)
160 _validateColumns(columns, out table);
163 //Validates a collection of columns with the ctor rules
164 private void _validateColumns(DataColumn [] columns, out DataTable table) {
168 if (null == columns) throw new ArgumentNullException();
170 //check that there is at least one column
171 //LAMESPEC: not in spec
172 if (columns.Length < 1)
173 throw new InvalidConstraintException("Must be at least one column.");
175 DataTable compareTable = columns[0].Table;
177 foreach (DataColumn col in columns){
179 //check individual column rules
180 _validateColumn (col);
183 //check that columns are all from the same table??
184 //LAMESPEC: not in spec
185 if (compareTable != col.Table)
186 throw new InvalidConstraintException("Columns must be from the same table.");
190 table = compareTable;
193 //validates a column with the ctor rules
194 private void _validateColumn(DataColumn column) {
197 if (null == column) // FIXME: This is little weird, but here it goes...
198 throw new NullReferenceException("Object reference not set to an instance of an object.");
201 //column must belong to a table
202 //LAMESPEC: not in spec
203 if (null == column.Table)
204 throw new ArgumentException ("Column must belong to a table.");
207 internal static void SetAsPrimaryKey(ConstraintCollection collection, UniqueConstraint newPrimaryKey)
210 if (null == collection) throw new ArgumentNullException("ConstraintCollection can't be null.");
212 //make sure newPrimaryKey belongs to the collection parm unless it is null
213 if ( collection.IndexOf(newPrimaryKey) < 0 && (null != newPrimaryKey) )
214 throw new ArgumentException("newPrimaryKey must belong to collection.");
217 UniqueConstraint uc = GetPrimaryKeyConstraint(collection);
220 if (null != uc) uc._isPrimaryKey = false;
223 if (null != newPrimaryKey) newPrimaryKey._isPrimaryKey = true;
228 internal static UniqueConstraint GetPrimaryKeyConstraint(ConstraintCollection collection)
230 if (null == collection) throw new ArgumentNullException("Collection can't be null.");
233 IEnumerator enumer = collection.GetEnumerator();
234 while (enumer.MoveNext())
236 uc = enumer.Current as UniqueConstraint;
237 if (null == uc) continue;
239 if (uc.IsPrimaryKey) return uc;
242 //if we got here there was no pk
247 internal static UniqueConstraint GetUniqueConstraintForColumnSet(ConstraintCollection collection,
248 DataColumn[] columns)
250 if (null == collection) throw new ArgumentNullException("Collection can't be null.");
251 if (null == columns ) return null;
253 foreach(Constraint constraint in collection) {
254 if (constraint is UniqueConstraint) {
255 UniqueConstraint uc = constraint as UniqueConstraint;
256 if ( DataColumn.AreColumnSetsTheSame(uc.Columns, columns) ) {
264 internal ForeignKeyConstraint ChildConstraint {
265 get { return _childConstraint; }
266 set { _childConstraint = value; }
269 // Helper Special Ctor
270 // Set the _dataTable property to the table to which this instance is bound when AddRange()
271 // is called with the special constructor.
272 // Validate whether the named columns exist in the _dataTable
273 internal override void FinishInit (DataTable _setTable)
275 _dataTable = _setTable;
276 if (_isPrimaryKey == true && _setTable.PrimaryKey.Length != 0)
277 throw new ArgumentException ("Cannot add primary key constraint since primary key" +
278 "is already set for the table");
280 DataColumn[] cols = new DataColumn [_dataColumnNames.Length];
283 foreach (string _columnName in _dataColumnNames) {
284 if (_setTable.Columns.Contains (_columnName)) {
285 cols [i] = _setTable.Columns [_columnName];
289 throw(new InvalidConstraintException ("The named columns must exist in the table"));
292 _validateColumns (cols);
293 InitInProgress = false;
301 [DataCategory ("Data")]
303 [DataSysDescription ("Indicates the columns of this constraint.")]
306 public virtual DataColumn[] Columns {
307 get { return _dataColumns; }
309 _dataColumns = new DataColumn [value.Length];
310 for (int i = 0; i < value.Length; i++)
311 _dataColumns[i] = value[i];
315 [DataCategory ("Data")]
317 [DataSysDescription ("Indicates if this constraint is a primary key.")]
319 public bool IsPrimaryKey {
321 if (Table == null || (!_belongsToCollection)) {
324 return _isPrimaryKey;
328 [DataCategory ("Data")]
330 [DataSysDescription ("Indicates the table of this constraint.")]
333 public override DataTable Table {
334 get { return _dataTable; }
337 #endregion // Properties
341 internal void SetIsPrimaryKey (bool value)
343 _isPrimaryKey = value;
346 public override bool Equals(object key2) {
348 UniqueConstraint cst = key2 as UniqueConstraint;
349 if (null == cst) return false;
351 //according to spec if the cols are equal
352 //then two UniqueConstraints are equal
353 return DataColumn.AreColumnSetsTheSame(cst.Columns, this.Columns);
357 public override int GetHashCode()
359 //initialize hash with default value
363 //derive the hash code from the columns that way
364 //Equals and GetHashCode return Equal objects to be the
367 //Get the first column hash
368 if (this.Columns.Length > 0)
369 hash ^= this.Columns[0].GetHashCode();
371 //get the rest of the column hashes if there any
372 for (i = 1; i < this.Columns.Length; i++)
374 hash ^= this.Columns[1].GetHashCode();
381 internal override void AddToConstraintCollectionSetup(
382 ConstraintCollection collection)
384 for (int i = 0; i < Columns.Length; i++)
385 if (Columns[i].Table != collection.Table)
386 throw new ArgumentException("These columns don't point to this table.");
387 //run Ctor rules again
388 _validateColumns(_dataColumns);
390 //make sure a unique constraint doesn't already exists for these columns
391 UniqueConstraint uc = UniqueConstraint.GetUniqueConstraintForColumnSet(collection, this.Columns);
392 if (null != uc) throw new ArgumentException("Unique constraint already exists for these" +
393 " columns. Existing ConstraintName is " + uc.ConstraintName);
395 //Allow only one primary key
396 if (this.IsPrimaryKey) {
397 uc = GetPrimaryKeyConstraint(collection);
398 if (null != uc) uc._isPrimaryKey = false;
401 // if constraint is based on one column only
402 // this column becomes unique
403 if (_dataColumns.Length == 1) {
404 _dataColumns[0].SetUnique();
407 if (IsConstraintViolated())
408 throw new ArgumentException("These columns don't currently have unique values.");
410 _belongsToCollection = true;
414 internal override void RemoveFromConstraintCollectionCleanup(
415 ConstraintCollection collection)
417 if (Columns.Length == 1)
418 Columns [0].Unique = false;
420 _belongsToCollection = false;
424 internal override bool IsConstraintViolated()
427 Index = Table.GetIndex(Columns,null,DataViewRowState.None,null,false);
430 if (Index.HasDuplicates) {
431 int[] dups = Index.Duplicates;
432 for (int i = 0; i < dups.Length; i++){
433 DataRow row = Table.RecordCache[dups[i]];
434 ArrayList columns = new ArrayList();
435 ArrayList values = new ArrayList();
436 foreach (DataColumn col in Columns){
437 columns.Add(col.ColumnName);
438 values.Add(row[col].ToString());
441 string columnNames = String.Join(", ", (string[])columns.ToArray(typeof(string)));
442 string columnValues = String.Join(", ", (string[])values.ToArray(typeof(string)));
444 row.RowError = String.Format("Column '{0}' is constrained to be unique. Value '{1}' is already present.", columnNames, columnValues);
445 for (int j=0; j < Columns.Length; ++j)
446 row.SetColumnError (Columns [j], row.RowError);
453 internal override void AssertConstraint(DataRow row)
455 if (IsPrimaryKey && row.HasVersion(DataRowVersion.Default)) {
456 for (int i = 0; i < Columns.Length; i++) {
457 if (row.IsNull(Columns[i])) {
458 throw new NoNullAllowedException("Column '" + Columns[i].ColumnName + "' does not allow nulls.");
464 Index = Table.GetIndex(Columns,null,DataViewRowState.None,null,false);
467 if (Index.HasDuplicates) {
468 throw new ConstraintException(GetErrorMessage(row));
472 internal override bool IsColumnContained(DataColumn column)
474 for (int i = 0; i < _dataColumns.Length; i++)
475 if (column == _dataColumns[i])
481 internal override bool CanRemoveFromCollection(ConstraintCollection col, bool shouldThrow){
484 throw new ArgumentException("Cannot remove unique constraint since it's the primary key of a table.");
489 if (Table.DataSet == null)
492 if (ChildConstraint != null) {
495 throw new ArgumentException (String.Format (
496 "Cannot remove unique constraint '{0}'." +
497 "Remove foreign key constraint '{1}' first.",
498 ConstraintName,ChildConstraint.ConstraintName));
503 private string GetErrorMessage(DataRow row)
507 System.Text.StringBuilder sb = new System.Text.StringBuilder(row[_dataColumns[0]].ToString());
508 for (i = 1; i < _dataColumns.Length; i++) {
509 sb = sb.Append(", ").Append(row[_dataColumns[i].ColumnName]);
511 string valStr = sb.ToString();
512 sb = new System.Text.StringBuilder(_dataColumns[0].ColumnName);
513 for (i = 1; i < _dataColumns.Length; i++) {
514 sb = sb.Append(", ").Append(_dataColumns[i].ColumnName);
516 string colStr = sb.ToString();
517 return "Column '" + colStr + "' is constrained to be unique. Value '" + valStr + "' is already present.";
521 #endregion // Methods