1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System.Diagnostics; 6 using System.ComponentModel; 7 8 namespace System.Data 9 { 10 /// <summary> 11 /// Represents a restriction on a set of columns in which all values must be unique. 12 /// </summary> 13 [DefaultProperty("ConstraintName")] 14 public class UniqueConstraint : Constraint 15 { 16 private DataKey _key; 17 private Index _constraintIndex; 18 internal bool _bPrimaryKey = false; 19 20 // Design time serialization 21 internal string _constraintName = null; 22 internal string[] _columnNames = null; 23 24 /// <summary> 25 /// Initializes a new instance of the <see cref='System.Data.UniqueConstraint'/> with the specified name and 26 /// <see cref='System.Data.DataColumn'/>. 27 /// </summary> UniqueConstraint(string name, DataColumn column)28 public UniqueConstraint(string name, DataColumn column) 29 { 30 DataColumn[] columns = new DataColumn[1]; 31 columns[0] = column; 32 Create(name, columns); 33 } 34 35 /// <summary> 36 /// Initializes a new instance of the <see cref='System.Data.UniqueConstraint'/> with the specified <see cref='System.Data.DataColumn'/>. 37 /// </summary> UniqueConstraint(DataColumn column)38 public UniqueConstraint(DataColumn column) 39 { 40 DataColumn[] columns = new DataColumn[1]; 41 columns[0] = column; 42 Create(null, columns); 43 } 44 45 /// <summary> 46 /// Initializes a new instance of the <see cref='System.Data.UniqueConstraint'/> with the specified name and array 47 /// of <see cref='System.Data.DataColumn'/> objects. 48 /// </summary> UniqueConstraint(string name, DataColumn[] columns)49 public UniqueConstraint(string name, DataColumn[] columns) 50 { 51 Create(name, columns); 52 } 53 54 /// <summary> 55 /// Initializes a new instance of the <see cref='System.Data.UniqueConstraint'/> with the given array of <see cref='System.Data.DataColumn'/> 56 /// objects. 57 /// </summary> UniqueConstraint(DataColumn[] columns)58 public UniqueConstraint(DataColumn[] columns) 59 { 60 Create(null, columns); 61 } 62 63 // Construct design time object 64 [Browsable(false)] UniqueConstraint(string name, string[] columnNames, bool isPrimaryKey)65 public UniqueConstraint(string name, string[] columnNames, bool isPrimaryKey) 66 { 67 _constraintName = name; 68 _columnNames = columnNames; 69 _bPrimaryKey = isPrimaryKey; 70 } 71 72 /// <summary> 73 /// Initializes a new instance of the <see cref='System.Data.UniqueConstraint'/> with the specified name and 74 /// <see cref='System.Data.DataColumn'/>. 75 /// </summary> UniqueConstraint(string name, DataColumn column, bool isPrimaryKey)76 public UniqueConstraint(string name, DataColumn column, bool isPrimaryKey) 77 { 78 DataColumn[] columns = new DataColumn[1]; 79 columns[0] = column; 80 _bPrimaryKey = isPrimaryKey; 81 Create(name, columns); 82 } 83 84 /// <summary> 85 /// Initializes a new instance of the <see cref='System.Data.UniqueConstraint'/> with the specified <see cref='System.Data.DataColumn'/>. 86 /// </summary> UniqueConstraint(DataColumn column, bool isPrimaryKey)87 public UniqueConstraint(DataColumn column, bool isPrimaryKey) 88 { 89 DataColumn[] columns = new DataColumn[1]; 90 columns[0] = column; 91 _bPrimaryKey = isPrimaryKey; 92 Create(null, columns); 93 } 94 95 /// <summary> 96 /// Initializes a new instance of the <see cref='System.Data.UniqueConstraint'/> with the specified name and array 97 /// of <see cref='System.Data.DataColumn'/> objects. 98 /// </summary> UniqueConstraint(string name, DataColumn[] columns, bool isPrimaryKey)99 public UniqueConstraint(string name, DataColumn[] columns, bool isPrimaryKey) 100 { 101 _bPrimaryKey = isPrimaryKey; 102 Create(name, columns); 103 } 104 105 /// <summary> 106 /// Initializes a new instance of the <see cref='System.Data.UniqueConstraint'/> with the given array of <see cref='System.Data.DataColumn'/> 107 /// objects. 108 /// </summary> UniqueConstraint(DataColumn[] columns, bool isPrimaryKey)109 public UniqueConstraint(DataColumn[] columns, bool isPrimaryKey) 110 { 111 _bPrimaryKey = isPrimaryKey; 112 Create(null, columns); 113 } 114 115 // design time serialization only 116 internal string[] ColumnNames 117 { 118 get 119 { 120 return _key.GetColumnNames(); 121 } 122 } 123 124 // Use constraint index only for search operations (and use key.GetSortIndex() when enumeration is needed and/or order is important) 125 internal Index ConstraintIndex 126 { 127 get 128 { 129 AssertConstraintAndKeyIndexes(); 130 return _constraintIndex; 131 } 132 } 133 134 [Conditional("DEBUG")] AssertConstraintAndKeyIndexes()135 private void AssertConstraintAndKeyIndexes() 136 { 137 Debug.Assert(null != _constraintIndex, "null UniqueConstraint index"); 138 139 // ideally, we would like constraintIndex and key.GetSortIndex to share the same index underneath: Debug.Assert(_constraintIndex == key.GetSortIndex) 140 // but, there is a scenario where constraint and key indexes are built from the same list of columns but in a different order 141 DataColumn[] sortIndexColumns = new DataColumn[_constraintIndex._indexFields.Length]; 142 for (int i = 0; i < sortIndexColumns.Length; i++) 143 { 144 sortIndexColumns[i] = _constraintIndex._indexFields[i].Column; 145 } 146 Debug.Assert(DataKey.ColumnsEqual(_key.ColumnsReference, sortIndexColumns), "UniqueConstraint index columns do not match the key sort index"); 147 } 148 ConstraintIndexClear()149 internal void ConstraintIndexClear() 150 { 151 if (null != _constraintIndex) 152 { 153 _constraintIndex.RemoveRef(); 154 _constraintIndex = null; 155 } 156 } 157 ConstraintIndexInitialize()158 internal void ConstraintIndexInitialize() 159 { 160 if (null == _constraintIndex) 161 { 162 _constraintIndex = _key.GetSortIndex(); 163 _constraintIndex.AddRef(); 164 } 165 166 AssertConstraintAndKeyIndexes(); 167 } 168 CheckState()169 internal override void CheckState() 170 { 171 NonVirtualCheckState(); 172 } 173 NonVirtualCheckState()174 private void NonVirtualCheckState() 175 { 176 _key.CheckState(); 177 } 178 CheckCanAddToCollection(ConstraintCollection constraints)179 internal override void CheckCanAddToCollection(ConstraintCollection constraints) 180 { 181 } 182 CanBeRemovedFromCollection(ConstraintCollection constraints, bool fThrowException)183 internal override bool CanBeRemovedFromCollection(ConstraintCollection constraints, bool fThrowException) 184 { 185 if (Equals(constraints.Table._primaryKey)) 186 { 187 Debug.Assert(constraints.Table._primaryKey == this, "If the primary key and this are 'Equal', they should also be '=='"); 188 if (!fThrowException) 189 return false; 190 else 191 throw ExceptionBuilder.RemovePrimaryKey(constraints.Table); 192 } 193 for (ParentForeignKeyConstraintEnumerator cs = new ParentForeignKeyConstraintEnumerator(Table.DataSet, Table); cs.GetNext();) 194 { 195 ForeignKeyConstraint constraint = cs.GetForeignKeyConstraint(); 196 if (!_key.ColumnsEqual(constraint.ParentKey)) 197 continue; 198 199 if (!fThrowException) 200 return false; 201 else 202 throw ExceptionBuilder.NeededForForeignKeyConstraint(this, constraint); 203 } 204 205 return true; 206 } 207 CanEnableConstraint()208 internal override bool CanEnableConstraint() 209 { 210 if (Table.EnforceConstraints) 211 return ConstraintIndex.CheckUnique(); 212 213 return true; 214 } 215 IsConstraintViolated()216 internal override bool IsConstraintViolated() 217 { 218 bool result = false; 219 Index index = ConstraintIndex; 220 if (index.HasDuplicates) 221 { 222 object[] uniqueKeys = index.GetUniqueKeyValues(); 223 224 for (int i = 0; i < uniqueKeys.Length; i++) 225 { 226 Range r = index.FindRecords((object[])uniqueKeys[i]); 227 if (1 < r.Count) 228 { 229 DataRow[] rows = index.GetRows(r); 230 string error = ExceptionBuilder.UniqueConstraintViolationText(_key.ColumnsReference, (object[])uniqueKeys[i]); 231 for (int j = 0; j < rows.Length; j++) 232 { 233 rows[j].RowError = error; 234 foreach (DataColumn dataColumn in _key.ColumnsReference) 235 { 236 rows[j].SetColumnError(dataColumn, error); 237 } 238 } 239 result = true; 240 } 241 } 242 } 243 return result; 244 } 245 CheckConstraint(DataRow row, DataRowAction action)246 internal override void CheckConstraint(DataRow row, DataRowAction action) 247 { 248 if (Table.EnforceConstraints && 249 (action == DataRowAction.Add || 250 action == DataRowAction.Change || 251 (action == DataRowAction.Rollback && row._tempRecord != -1))) 252 { 253 if (row.HaveValuesChanged(ColumnsReference)) 254 { 255 if (ConstraintIndex.IsKeyRecordInIndex(row.GetDefaultRecord())) 256 { 257 object[] values = row.GetColumnValues(ColumnsReference); 258 throw ExceptionBuilder.ConstraintViolation(ColumnsReference, values); 259 } 260 } 261 } 262 } 263 ContainsColumn(DataColumn column)264 internal override bool ContainsColumn(DataColumn column) 265 { 266 return _key.ContainsColumn(column); 267 } 268 Clone(DataSet destination)269 internal override Constraint Clone(DataSet destination) 270 { 271 return Clone(destination, false); 272 } 273 Clone(DataSet destination, bool ignorNSforTableLookup)274 internal override Constraint Clone(DataSet destination, bool ignorNSforTableLookup) 275 { 276 int iDest; 277 if (ignorNSforTableLookup) 278 { 279 iDest = destination.Tables.IndexOf(Table.TableName); 280 } 281 else 282 { 283 iDest = destination.Tables.IndexOf(Table.TableName, Table.Namespace, false);// pass false for last param to be backward compatable 284 } 285 286 if (iDest < 0) 287 return null; 288 DataTable table = destination.Tables[iDest]; 289 290 int keys = ColumnsReference.Length; 291 DataColumn[] columns = new DataColumn[keys]; 292 293 for (int i = 0; i < keys; i++) 294 { 295 DataColumn src = ColumnsReference[i]; 296 iDest = table.Columns.IndexOf(src.ColumnName); 297 if (iDest < 0) 298 return null; 299 columns[i] = table.Columns[iDest]; 300 } 301 302 UniqueConstraint clone = new UniqueConstraint(ConstraintName, columns); 303 304 // ...Extended Properties 305 foreach (object key in ExtendedProperties.Keys) 306 { 307 clone.ExtendedProperties[key] = ExtendedProperties[key]; 308 } 309 310 return clone; 311 } 312 Clone(DataTable table)313 internal UniqueConstraint Clone(DataTable table) 314 { 315 int keys = ColumnsReference.Length; 316 DataColumn[] columns = new DataColumn[keys]; 317 318 for (int i = 0; i < keys; i++) 319 { 320 DataColumn src = ColumnsReference[i]; 321 int iDest = table.Columns.IndexOf(src.ColumnName); 322 if (iDest < 0) 323 return null; 324 columns[i] = table.Columns[iDest]; 325 } 326 327 UniqueConstraint clone = new UniqueConstraint(ConstraintName, columns); 328 329 // ...Extended Properties 330 foreach (object key in ExtendedProperties.Keys) 331 { 332 clone.ExtendedProperties[key] = ExtendedProperties[key]; 333 } 334 335 return clone; 336 } 337 338 /// <summary> 339 /// Gets the array of columns that this constraint affects. 340 /// </summary> 341 [ReadOnly(true)] 342 public virtual DataColumn[] Columns 343 { 344 get 345 { 346 return _key.ToArray(); 347 } 348 } 349 350 internal DataColumn[] ColumnsReference 351 { 352 get 353 { 354 return _key.ColumnsReference; 355 } 356 } 357 358 /// <summary> 359 /// Gets a value indicating whether or not the constraint is on a primary key. 360 /// </summary> 361 public bool IsPrimaryKey 362 { 363 get 364 { 365 if (Table == null) 366 { 367 return false; 368 } 369 return (this == Table._primaryKey); 370 } 371 } 372 Create(string constraintName, DataColumn[] columns)373 private void Create(string constraintName, DataColumn[] columns) 374 { 375 for (int i = 0; i < columns.Length; i++) 376 { 377 if (columns[i].Computed) 378 { 379 throw ExceptionBuilder.ExpressionInConstraint(columns[i]); 380 } 381 } 382 _key = new DataKey(columns, true); 383 ConstraintName = constraintName; 384 NonVirtualCheckState(); 385 } 386 387 /// <summary> 388 /// Compares this constraint to a second to determine if both are identical. 389 /// </summary> Equals(object key2)390 public override bool Equals(object key2) 391 { 392 if (!(key2 is UniqueConstraint)) 393 return false; 394 395 return Key.ColumnsEqual(((UniqueConstraint)key2).Key); 396 } 397 GetHashCode()398 public override int GetHashCode() 399 { 400 return base.GetHashCode(); 401 } 402 403 internal override bool InCollection 404 { 405 set 406 { 407 base.InCollection = value; 408 if (_key.ColumnsReference.Length == 1) 409 { 410 _key.ColumnsReference[0].InternalUnique(value); 411 } 412 } 413 } 414 415 internal DataKey Key 416 { 417 get 418 { 419 return _key; 420 } 421 } 422 423 /// <summary> 424 /// Gets the table to which this constraint belongs. 425 /// </summary> 426 [ReadOnly(true)] 427 public override DataTable Table 428 { 429 get 430 { 431 if (_key.HasValue) 432 { 433 return _key.Table; 434 } 435 return null; 436 } 437 } 438 439 // misc 440 } 441 } 442