| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515 |
- //
- // System.Data.UniqueConstraint.cs
- //
- // Author:
- // Franklin Wise <[email protected]>
- // Daniel Morgan <[email protected]>
- // Tim Coleman ([email protected])
- //
- // (C) 2002 Franklin Wise
- // (C) 2002 Daniel Morgan
- // Copyright (C) Tim Coleman, 2002
- //
- // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
- //
- // Permission is hereby granted, free of charge, to any person obtaining
- // a copy of this software and associated documentation files (the
- // "Software"), to deal in the Software without restriction, including
- // without limitation the rights to use, copy, modify, merge, publish,
- // distribute, sublicense, and/or sell copies of the Software, and to
- // permit persons to whom the Software is furnished to do so, subject to
- // the following conditions:
- //
- // The above copyright notice and this permission notice shall be
- // included in all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- //
- using System;
- using System.Collections;
- using System.ComponentModel;
- using System.Runtime.InteropServices;
- using System.Data.Common;
- namespace System.Data {
- [Editor]
- [DefaultProperty ("ConstraintName")]
- [Serializable]
- public class UniqueConstraint : Constraint
- {
- private bool _isPrimaryKey = false;
- private bool _belongsToCollection = false;
- private DataTable _dataTable; //set by ctor except when unique case
-
- //FIXME: create a class which will wrap this collection
- private DataColumn [] _dataColumns;
- //TODO:provide helpers for this case
- private string [] _dataColumnNames; //unique case
- private bool _dataColsNotValidated;
- #region Constructors
- public UniqueConstraint (DataColumn column)
- {
- _uniqueConstraint ("", column, false);
- }
- public UniqueConstraint (DataColumn[] columns)
- {
- _uniqueConstraint ("", columns, false);
- }
- public UniqueConstraint (DataColumn column, bool isPrimaryKey)
- {
- _uniqueConstraint ("", column, isPrimaryKey);
- }
- public UniqueConstraint (DataColumn[] columns, bool isPrimaryKey)
- {
- _uniqueConstraint ("", columns, isPrimaryKey);
- }
- public UniqueConstraint (string name, DataColumn column)
- {
- _uniqueConstraint (name, column, false);
- }
- public UniqueConstraint (string name, DataColumn[] columns)
- {
- _uniqueConstraint (name, columns, false);
- }
- public UniqueConstraint (string name, DataColumn column, bool isPrimaryKey)
- {
- _uniqueConstraint (name, column, isPrimaryKey);
- }
- public UniqueConstraint (string name, DataColumn[] columns, bool isPrimaryKey)
- {
- _uniqueConstraint (name, columns, isPrimaryKey);
- }
- //Special case. Can only be added to the Collection with AddRange
- [Browsable (false)]
- public UniqueConstraint (string name, string[] columnNames, bool isPrimaryKey)
- {
- _dataColsNotValidated = true;
-
- //keep list of names to resolve later
- _dataColumnNames = columnNames;
-
- base.ConstraintName = name;
-
- _isPrimaryKey = isPrimaryKey;
- }
- //helper ctor
- private void _uniqueConstraint(string name, DataColumn column, bool isPrimaryKey)
- {
- _dataColsNotValidated = false;
- //validate
- _validateColumn (column);
- //Set Constraint Name
- base.ConstraintName = name;
- _isPrimaryKey = isPrimaryKey;
- //keep reference
- _dataColumns = new DataColumn [] {column};
-
- //Get table reference
- _dataTable = column.Table;
- }
- //helpter ctor
- private void _uniqueConstraint(string name, DataColumn[] columns, bool isPrimaryKey)
- {
- _dataColsNotValidated = false;
-
- //validate
- _validateColumns (columns, out _dataTable);
- //Set Constraint Name
- base.ConstraintName = name;
- //keep reference
- _dataColumns = columns;
- //PK?
- _isPrimaryKey = isPrimaryKey;
- }
-
- #endregion // Constructors
- #region Helpers
-
- private void _validateColumns(DataColumn [] columns)
- {
- DataTable table;
- _validateColumns(columns, out table);
- }
-
- //Validates a collection of columns with the ctor rules
- private void _validateColumns(DataColumn [] columns, out DataTable table) {
- table = null;
- //not null
- if (null == columns) throw new ArgumentNullException();
-
- //check that there is at least one column
- //LAMESPEC: not in spec
- if (columns.Length < 1)
- throw new InvalidConstraintException("Must be at least one column.");
-
- DataTable compareTable = columns[0].Table;
- //foreach
- foreach (DataColumn col in columns){
-
- //check individual column rules
- _validateColumn (col);
-
-
- //check that columns are all from the same table??
- //LAMESPEC: not in spec
- if (compareTable != col.Table)
- throw new InvalidConstraintException("Columns must be from the same table.");
-
- }
- table = compareTable;
- }
-
- //validates a column with the ctor rules
- private void _validateColumn(DataColumn column) {
-
- //not null
- if (null == column) // FIXME: This is little weird, but here it goes...
- throw new NullReferenceException("Object reference not set to an instance of an object.");
-
- //column must belong to a table
- //LAMESPEC: not in spec
- if (null == column.Table)
- throw new ArgumentException ("Column must belong to a table.");
- }
- internal static void SetAsPrimaryKey(ConstraintCollection collection, UniqueConstraint newPrimaryKey)
- {
- //not null
- if (null == collection) throw new ArgumentNullException("ConstraintCollection can't be null.");
-
- //make sure newPrimaryKey belongs to the collection parm unless it is null
- if ( collection.IndexOf(newPrimaryKey) < 0 && (null != newPrimaryKey) )
- throw new ArgumentException("newPrimaryKey must belong to collection.");
-
- //Get existing pk
- UniqueConstraint uc = GetPrimaryKeyConstraint(collection);
-
- //clear existing
- if (null != uc) uc._isPrimaryKey = false;
- //set new key
- if (null != newPrimaryKey) newPrimaryKey._isPrimaryKey = true;
-
-
- }
- internal static UniqueConstraint GetPrimaryKeyConstraint(ConstraintCollection collection)
- {
- if (null == collection) throw new ArgumentNullException("Collection can't be null.");
- UniqueConstraint uc;
- IEnumerator enumer = collection.GetEnumerator();
- while (enumer.MoveNext())
- {
- uc = enumer.Current as UniqueConstraint;
- if (null == uc) continue;
-
- if (uc.IsPrimaryKey) return uc;
- }
- //if we got here there was no pk
- return null;
-
- }
- internal static UniqueConstraint GetUniqueConstraintForColumnSet(ConstraintCollection collection,
- DataColumn[] columns)
- {
- if (null == collection) throw new ArgumentNullException("Collection can't be null.");
- if (null == columns ) return null;
-
- foreach(Constraint constraint in collection) {
- if (constraint is UniqueConstraint) {
- UniqueConstraint uc = constraint as UniqueConstraint;
- if ( DataColumn.AreColumnSetsTheSame(uc.Columns, columns) ) {
- return uc;
- }
- }
- }
- return null;
- }
- internal bool DataColsNotValidated
- {
- get {
- return (_dataColsNotValidated);
- }
- }
- // Helper Special Ctor
- // Set the _dataTable property to the table to which this instance is bound when AddRange()
- // is called with the special constructor.
- // Validate whether the named columns exist in the _dataTable
- internal void PostAddRange( DataTable _setTable )
- {
- _dataTable = _setTable;
- DataColumn []cols = new DataColumn [_dataColumnNames.Length];
- int i = 0;
- foreach ( string _columnName in _dataColumnNames ) {
- if ( _setTable.Columns.Contains (_columnName) ) {
- cols [i] = _setTable.Columns [_columnName];
- i++;
- continue;
- }
- throw( new InvalidConstraintException ( "The named columns must exist in the table" ));
- }
- _dataColumns = cols;
- }
-
- #endregion //Helpers
- #region Properties
- [DataCategory ("Data")]
- [DataSysDescription ("Indicates the columns of this constraint.")]
- [ReadOnly (true)]
- public virtual DataColumn[] Columns {
- get { return _dataColumns; }
- }
- [DataCategory ("Data")]
- [DataSysDescription ("Indicates if this constraint is a primary key.")]
- public bool IsPrimaryKey {
- get {
- if (Table == null || (!_belongsToCollection)) {
- return false;
- }
- return _isPrimaryKey;
- }
- }
- [DataCategory ("Data")]
- [DataSysDescription ("Indicates the table of this constraint.")]
- [ReadOnly (true)]
- public override DataTable Table {
- get { return _dataTable; }
- }
- #endregion // Properties
- #region Methods
- public override bool Equals(object key2) {
- UniqueConstraint cst = key2 as UniqueConstraint;
- if (null == cst) return false;
- //according to spec if the cols are equal
- //then two UniqueConstraints are equal
- return DataColumn.AreColumnSetsTheSame(cst.Columns, this.Columns);
- }
- public override int GetHashCode()
- {
- //initialize hash with default value
- int hash = 42;
- int i;
- //derive the hash code from the columns that way
- //Equals and GetHashCode return Equal objects to be the
- //same
- //Get the first column hash
- if (this.Columns.Length > 0)
- hash ^= this.Columns[0].GetHashCode();
-
- //get the rest of the column hashes if there any
- for (i = 1; i < this.Columns.Length; i++)
- {
- hash ^= this.Columns[1].GetHashCode();
-
- }
-
- return hash ;
- }
-
- internal override void AddToConstraintCollectionSetup(
- ConstraintCollection collection)
- {
- for (int i = 0; i < Columns.Length; i++)
- if (Columns[i].Table != collection.Table)
- throw new ArgumentException("These columns don't point to this table.");
- //run Ctor rules again
- _validateColumns(_dataColumns);
-
- //make sure a unique constraint doesn't already exists for these columns
- UniqueConstraint uc = UniqueConstraint.GetUniqueConstraintForColumnSet(collection, this.Columns);
- if (null != uc) throw new ArgumentException("Unique constraint already exists for these" +
- " columns. Existing ConstraintName is " + uc.ConstraintName);
- //Allow only one primary key
- if (this.IsPrimaryKey) {
- uc = GetPrimaryKeyConstraint(collection);
- if (null != uc) uc._isPrimaryKey = false;
- }
- // if constraint is based on one column only
- // this column becomes unique
- if (_dataColumns.Length == 1) {
- _dataColumns[0].SetUnique();
- }
-
- //FIXME: ConstraintCollection calls AssertContraint() again rigth after calling
- //this method, so that it is executed twice. Need to investigate which
- // call to remove as that migth affect other parts of the classes.
- //AssertConstraint();
- if (IsConstraintViolated())
- throw new ArgumentException("These columns don't currently have unique values.");
- _belongsToCollection = true;
- }
-
-
- internal override void RemoveFromConstraintCollectionCleanup(
- ConstraintCollection collection)
- {
- _belongsToCollection = false;
- Index index = Index;
- Index = null;
- }
- protected override bool IsConstraintViolated()
- {
- if (Index == null) {
- Index = Table.GetIndex(Columns,null,DataViewRowState.None,null,false);
- }
- if (Index.HasDuplicates) {
- int[] dups = Index.Duplicates;
- for (int i = 0; i < dups.Length; i++){
- DataRow row = Table.RecordCache[dups[i]];
- ArrayList columns = new ArrayList();
- ArrayList values = new ArrayList();
- foreach (DataColumn col in Columns){
- columns.Add(col.ColumnName);
- values.Add(row[col].ToString());
- }
- string columnNames = String.Join(",", (string[])columns.ToArray(typeof(string)));
- string columnValues = String.Join(",", (string[])values.ToArray(typeof(string)));
- row.RowError = String.Format("Column(s) '{0}' are constrained to be unique. Value(s) '{1}' are already present", columnNames, columnValues);
- }
- // FIXME : check the exception to be thrown here
- // throw new ConstraintException("These columns don't currently have unique values");
- //throw new ConstraintException ("Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints.");
- return true;
- }
- return false;
- }
- internal override void AssertConstraint(DataRow row)
- {
- if (IsPrimaryKey && row.HasVersion(DataRowVersion.Default)) {
- for (int i = 0; i < Columns.Length; i++) {
- if (row.IsNull(Columns[i])) {
- throw new NoNullAllowedException("Column '" + Columns[i].ColumnName + "' does not allow nulls.");
- }
- }
- }
-
- if (Index == null) {
- Index = Table.GetIndex(Columns,null,DataViewRowState.None,null,false);
- }
- if (Index.HasDuplicates) {
- throw new ConstraintException(GetErrorMessage(row));
- }
- }
- internal override bool IsColumnContained(DataColumn column)
- {
- for (int i = 0; i < _dataColumns.Length; i++)
- if (column == _dataColumns[i])
- return true;
- return false;
- }
- internal override bool CanRemoveFromCollection(ConstraintCollection col, bool shouldThrow){
- if (Equals(col.Table.PrimaryKey)){
- if (shouldThrow)
- throw new ArgumentException("Cannot remove unique constraint since it's the primary key of a table.");
- return false;
- }
- if (Table.DataSet != null){
- foreach (DataTable table in Table.DataSet.Tables){
- foreach (Constraint constraint in table.Constraints){
- if (constraint is ForeignKeyConstraint)
- if (((ForeignKeyConstraint)constraint).RelatedTable == Table){
- if (shouldThrow)
- throw new ArgumentException(
- String.Format("Cannot remove unique constraint '{0}'. Remove foreign key constraint '{1}' first.",
- ConstraintName, constraint.ConstraintName)
- );
- return false;
- }
- }
- }
- }
- return true;
- }
- private string GetErrorMessage(DataRow row)
- {
- int i;
-
- System.Text.StringBuilder sb = new System.Text.StringBuilder(row[_dataColumns[0]].ToString());
- for (i = 1; i < _dataColumns.Length; i++) {
- sb = sb.Append(", ").Append(row[_dataColumns[i].ColumnName]);
- }
- string valStr = sb.ToString();
- sb = new System.Text.StringBuilder(_dataColumns[0].ColumnName);
- for (i = 1; i < _dataColumns.Length; i++) {
- sb = sb.Append(", ").Append(_dataColumns[i].ColumnName);
- }
- string colStr = sb.ToString();
- return "Column '" + colStr + "' is constrained to be unique. Value '" + valStr + "' is already present.";
- }
-
-
- #endregion // Methods
- }
-
- }
|