1 //------------------------------------------------------------------------------
2 // <copyright file="ConstraintCollection.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="true" primary="false">Microsoft</owner>
7 // <owner current="false" primary="false">Microsoft</owner>
8 //------------------------------------------------------------------------------
10 namespace System.Data {
12 using System.Diagnostics;
13 using System.Collections;
14 using System.ComponentModel;
17 /// <para>Represents a collection of constraints for a <see cref='System.Data.DataTable'/>
21 DefaultEvent("CollectionChanged"),
22 Editor("Microsoft.VSDesigner.Data.Design.ConstraintsCollectionEditor, " + AssemblyRef.MicrosoftVSDesigner, "System.Drawing.Design.UITypeEditor, " + AssemblyRef.SystemDrawing),
24 public sealed class ConstraintCollection : InternalDataCollectionBase { // WebData 111752
26 private readonly DataTable table;
27 // private Constraint[] constraints = new Constraint[2];
28 private readonly ArrayList list = new ArrayList();
29 private int defaultNameIndex = 1;
31 private CollectionChangeEventHandler onCollectionChanged;
32 private Constraint[] delayLoadingConstraints;
33 private bool fLoadForeignKeyConstraintsOnly = false;
36 /// ConstraintCollection constructor. Used only by DataTable.
38 internal ConstraintCollection(DataTable table) {
43 /// <para>Gets the list of objects contained by the collection.</para>
45 protected override ArrayList List {
52 /// <para>Gets the <see cref='System.Data.Constraint'/>
53 /// from the collection at the specified index.</para>
55 public Constraint this[int index] {
57 if (index >= 0 && index < List.Count) {
58 return(Constraint) List[index];
60 throw ExceptionBuilder.ConstraintOutOfRange(index);
65 /// The DataTable with which this ConstraintCollection is associated
67 internal DataTable Table {
74 /// <para>Gets the <see cref='System.Data.Constraint'/> from the collection with the specified name.</para>
76 public Constraint this[string name] {
78 int index = InternalIndexOf(name);
80 throw ExceptionBuilder.CaseInsensitiveNameConflict(name);
82 return (index < 0) ? null : (Constraint)List[index];
88 /// Adds the constraint to the collection.</para>
90 public void Add(Constraint constraint) {
91 Add(constraint, true);
94 // To add foreign key constraint without adding any unique constraint for internal use. Main purpose : Binary Remoting
95 internal void Add(Constraint constraint, bool addUniqueWhenAddingForeign) {
97 if (constraint == null)
98 throw ExceptionBuilder.ArgumentNull("constraint");
100 // It is an error if we find an equivalent constraint already in collection
101 if (FindConstraint(constraint) != null) {
102 throw ExceptionBuilder.DuplicateConstraint(FindConstraint(constraint).ConstraintName);
105 if (1 < table.NestedParentRelations.Length) {
106 if (!AutoGenerated(constraint)) {
107 throw ExceptionBuilder.CantAddConstraintToMultipleNestedTable(table.TableName);
111 if (constraint is UniqueConstraint) {
112 if (((UniqueConstraint)constraint).bPrimaryKey) {
113 if (Table.primaryKey != null) {
114 throw ExceptionBuilder.AddPrimaryKeyConstraint();
117 AddUniqueConstraint((UniqueConstraint)constraint);
119 else if (constraint is ForeignKeyConstraint) {
120 ForeignKeyConstraint fk = (ForeignKeyConstraint)constraint;
121 if (addUniqueWhenAddingForeign) {
122 UniqueConstraint key = fk.RelatedTable.Constraints.FindKeyConstraint(fk.RelatedColumnsReference);
124 if (constraint.ConstraintName.Length == 0)
125 constraint.ConstraintName = AssignName();
127 RegisterName(constraint.ConstraintName);
129 key = new UniqueConstraint(fk.RelatedColumnsReference);
130 fk.RelatedTable.Constraints.Add(key);
133 AddForeignKeyConstraint((ForeignKeyConstraint)constraint);
136 ArrayAdd(constraint);
137 OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Add, constraint));
139 if (constraint is UniqueConstraint) {
140 if (((UniqueConstraint)constraint).bPrimaryKey) {
141 Table.PrimaryKey = ((UniqueConstraint)constraint).ColumnsReference;
147 /// <para>Constructs a new <see cref='System.Data.UniqueConstraint'/> using the
148 /// specified array of <see cref='System.Data.DataColumn'/>
149 /// objects and adds it to the collection.</para>
151 public Constraint Add(string name, DataColumn[] columns, bool primaryKey) {
152 UniqueConstraint constraint = new UniqueConstraint(name, columns);
155 Table.PrimaryKey = columns;
160 /// <para>Constructs a new <see cref='System.Data.UniqueConstraint'/> using the
161 /// specified <see cref='System.Data.DataColumn'/> and adds it to the collection.</para>
163 public Constraint Add(string name, DataColumn column, bool primaryKey) {
164 UniqueConstraint constraint = new UniqueConstraint(name, column);
167 Table.PrimaryKey = constraint.ColumnsReference;
173 /// Constructs a new <see cref='System.Data.ForeignKeyConstraint'/>
175 /// specified parent and child
176 /// columns and adds the constraint to the collection.</para>
178 public Constraint Add(string name, DataColumn primaryKeyColumn, DataColumn foreignKeyColumn) {
179 ForeignKeyConstraint constraint = new ForeignKeyConstraint(name, primaryKeyColumn, foreignKeyColumn);
185 /// <para>Constructs a new <see cref='System.Data.ForeignKeyConstraint'/> with the specified parent columns and
186 /// child columns and adds the constraint to the collection.</para>
188 public Constraint Add(string name, DataColumn[] primaryKeyColumns, DataColumn[] foreignKeyColumns) {
189 ForeignKeyConstraint constraint = new ForeignKeyConstraint(name, primaryKeyColumns, foreignKeyColumns);
194 public void AddRange(Constraint[] constraints ) {
195 if (table.fInitInProgress) {
196 delayLoadingConstraints = constraints;
197 fLoadForeignKeyConstraintsOnly = false;
201 if (constraints != null) {
202 foreach(Constraint constr in constraints) {
203 if (constr != null) {
211 private void AddUniqueConstraint(UniqueConstraint constraint) {
212 DataColumn[] columns = constraint.ColumnsReference;
214 for (int i = 0; i < columns.Length; i++) {
215 if (columns[i].Table != this.table) {
216 throw ExceptionBuilder.ConstraintForeignTable();
219 constraint.ConstraintIndexInitialize();
221 if (!constraint.CanEnableConstraint()) {
222 constraint.ConstraintIndexClear();
223 throw ExceptionBuilder.UniqueConstraintViolation();
227 private void AddForeignKeyConstraint(ForeignKeyConstraint constraint) {
228 if (!constraint.CanEnableConstraint()) {
229 throw ExceptionBuilder.ConstraintParentValues();
231 constraint.CheckCanAddToCollection(this);
234 private bool AutoGenerated(Constraint constraint) {
235 ForeignKeyConstraint fk = (constraint as ForeignKeyConstraint);
237 return XmlTreeGen.AutoGenerated(fk, false);
240 UniqueConstraint unique = (UniqueConstraint) constraint;
241 return XmlTreeGen.AutoGenerated(unique);
246 /// <para>Occurs when the <see cref='System.Data.ConstraintCollection'/> is changed through additions or
249 public event CollectionChangeEventHandler CollectionChanged {
251 onCollectionChanged += value;
254 onCollectionChanged -= value;
259 /// Adds the constraint to the constraints array.
261 private void ArrayAdd(Constraint constraint) {
262 Debug.Assert(constraint != null, "Attempt to add null constraint to constraint array");
263 List.Add(constraint);
266 private void ArrayRemove(Constraint constraint) {
267 List.Remove(constraint);
271 /// Creates a new default name.
273 internal string AssignName() {
274 string newName = MakeName(defaultNameIndex);
280 /// Does verification on the constraint and it's name.
281 /// An ArgumentNullException is thrown if this constraint is null. An ArgumentException is thrown if this constraint
282 /// already belongs to this collection, belongs to another collection.
283 /// A DuplicateNameException is thrown if this collection already has a constraint with the same
284 /// name (case insensitive).
286 private void BaseAdd(Constraint constraint) {
287 if (constraint == null)
288 throw ExceptionBuilder.ArgumentNull("constraint");
290 if (constraint.ConstraintName.Length == 0)
291 constraint.ConstraintName = AssignName();
293 RegisterName(constraint.ConstraintName);
295 constraint.InCollection = true;
299 /// BaseGroupSwitch will intelligently remove and add tables from the collection.
301 private void BaseGroupSwitch(Constraint[] oldArray, int oldLength, Constraint[] newArray, int newLength) {
302 // We're doing a smart diff of oldArray and newArray to find out what
303 // should be removed. We'll pass through oldArray and see if it exists
304 // in newArray, and if not, do remove work. newBase is an opt. in case
305 // the arrays have similar prefixes.
307 for (int oldCur = 0; oldCur < oldLength; oldCur++) {
309 for (int newCur = newBase; newCur < newLength; newCur++) {
310 if (oldArray[oldCur] == newArray[newCur]) {
311 if (newBase == newCur) {
319 // This means it's in oldArray and not newArray. Remove it.
320 BaseRemove(oldArray[oldCur]);
321 List.Remove(oldArray[oldCur]);
326 // Now, let's pass through news and those that don't belong, add them.
327 for (int newCur = 0; newCur < newLength; newCur++) {
328 if (!newArray[newCur].InCollection)
329 BaseAdd(newArray[newCur]);
330 List.Add(newArray[newCur]);
335 /// Does verification on the constraint and it's name.
336 /// An ArgumentNullException is thrown if this constraint is null. An ArgumentException is thrown
337 /// if this constraint doesn't belong to this collection or if this constraint is part of a relationship.
339 private void BaseRemove(Constraint constraint) {
340 if (constraint == null) {
341 throw ExceptionBuilder.ArgumentNull("constraint");
343 if (constraint.Table != table) {
344 throw ExceptionBuilder.ConstraintRemoveFailed();
347 UnregisterName(constraint.ConstraintName);
348 constraint.InCollection = false;
350 if (constraint is UniqueConstraint) {
351 for (int i = 0; i < Table.ChildRelations.Count; i++) {
352 DataRelation rel = Table.ChildRelations[i];
353 if (rel.ParentKeyConstraint == constraint)
354 rel.SetParentKeyConstraint(null);
356 ((UniqueConstraint)constraint).ConstraintIndexClear();
358 else if (constraint is ForeignKeyConstraint) {
359 for (int i = 0; i < Table.ParentRelations.Count; i++) {
360 DataRelation rel = Table.ParentRelations[i];
361 if (rel.ChildKeyConstraint == constraint)
362 rel.SetChildKeyConstraint(null);
368 /// <para>Indicates if a <see cref='System.Data.Constraint'/> can be removed.</para>
370 // PUBLIC because called by design-time... need to consider this.
371 public bool CanRemove(Constraint constraint) {
372 return CanRemove(constraint, /*fThrowException:*/false);
375 internal bool CanRemove(Constraint constraint, bool fThrowException) {
376 return constraint.CanBeRemovedFromCollection(this, fThrowException);
380 /// <para>Clears the collection of any <see cref='System.Data.Constraint'/>
383 public void Clear() {
385 table.PrimaryKey = null;
387 for (int i = 0; i < table.ParentRelations.Count; i++) {
388 table.ParentRelations[i].SetChildKeyConstraint(null);
390 for (int i = 0; i < table.ChildRelations.Count; i++) {
391 table.ChildRelations[i].SetParentKeyConstraint(null);
395 if (table.fInitInProgress && delayLoadingConstraints != null) {
396 delayLoadingConstraints = null;
397 fLoadForeignKeyConstraintsOnly = false;
400 int oldLength = List.Count;
402 Constraint[] constraints = new Constraint[List.Count];
403 List.CopyTo(constraints, 0);
405 // this will smartly add and remove the appropriate tables.
406 BaseGroupSwitch(constraints, oldLength, null, 0);
408 catch (Exception e) {
410 if (Common.ADP.IsCatchableOrSecurityExceptionType(e)) {
411 // something messed up. restore to original state.
412 BaseGroupSwitch(null, 0, constraints, oldLength);
414 for (int i = 0; i < oldLength; i++)
415 List.Add(constraints[i]);
421 OnCollectionChanged(RefreshEventArgs);
425 /// <para>Indicates whether the <see cref='System.Data.Constraint'/>, specified by name, exists in the collection.</para>
427 public bool Contains(string name) {
428 return (InternalIndexOf(name) >= 0);
431 internal bool Contains(string name, bool caseSensitive) {
433 return Contains(name);
434 int index = InternalIndexOf(name);
437 return (name == ((Constraint) List[index]).ConstraintName);
440 public void CopyTo(Constraint[] array, int index) {
442 throw ExceptionBuilder.ArgumentNull("array");
444 throw ExceptionBuilder.ArgumentOutOfRange("index");
445 if (array.Length - index < list.Count)
446 throw ExceptionBuilder.InvalidOffsetLength();
447 for(int i = 0; i < list.Count; ++i) {
448 array[index + i] = (Constraint)list[i];
453 /// Returns a matching constriant object.
455 internal Constraint FindConstraint(Constraint constraint) {
456 int constraintCount = List.Count;
457 for (int i = 0; i < constraintCount; i++) {
458 if (((Constraint)List[i]).Equals(constraint))
459 return(Constraint)List[i];
465 /// Returns a matching constriant object.
467 internal UniqueConstraint FindKeyConstraint(DataColumn[] columns) {
468 int constraintCount = List.Count;
469 for (int i = 0; i < constraintCount; i++) {
470 UniqueConstraint constraint = (List[i] as UniqueConstraint);
471 if ((null != constraint) && CompareArrays(constraint.Key.ColumnsReference, columns)) {
479 /// Returns a matching constriant object.
481 internal UniqueConstraint FindKeyConstraint(DataColumn column) {
482 int constraintCount = List.Count;
483 for (int i = 0; i < constraintCount; i++) {
484 UniqueConstraint constraint = (List[i] as UniqueConstraint);
485 if ((null != constraint) && (constraint.Key.ColumnsReference.Length == 1) && (constraint.Key.ColumnsReference[0] == column))
492 /// Returns a matching constriant object.
494 internal ForeignKeyConstraint FindForeignKeyConstraint(DataColumn[] parentColumns, DataColumn[] childColumns) {
495 int constraintCount = List.Count;
496 for (int i = 0; i < constraintCount; i++) {
497 ForeignKeyConstraint constraint = (List[i] as ForeignKeyConstraint);
498 if ((null != constraint) &&
499 CompareArrays(constraint.ParentKey.ColumnsReference, parentColumns) &&
500 CompareArrays(constraint.ChildKey.ColumnsReference, childColumns))
506 private static bool CompareArrays(DataColumn[] a1, DataColumn[] a2) {
507 Debug.Assert(a1 != null && a2 != null, "Invalid Arguments");
508 if (a1.Length != a2.Length)
512 for (i=0; i<a1.Length; i++) {
514 for (j=0; j<a2.Length; j++) {
529 /// <para>Returns the index of the specified <see cref='System.Data.Constraint'/> .</para>
531 public int IndexOf(Constraint constraint) {
532 if (null != constraint) {
534 for (int i = 0; i < count; ++i) {
535 if (constraint == (Constraint) List[i])
538 // didnt find the constraint
544 /// <para>Returns the index of the <see cref='System.Data.Constraint'/>, specified by name.</para>
546 public int IndexOf(string constraintName) {
547 int index = InternalIndexOf(constraintName);
548 return (index < 0) ? -1 : index;
552 // >= 0: find the match
554 // -2: At least two matches with different cases
555 internal int InternalIndexOf(string constraintName) {
557 if ((null != constraintName) && (0 < constraintName.Length)) {
558 int constraintCount = List.Count;
560 for (int i = 0; i < constraintCount; i++) {
561 Constraint constraint = (Constraint) List[i];
562 result = NamesEqual(constraint.ConstraintName, constraintName, false, table.Locale);
567 cachedI = (cachedI == -1) ? i : -2;
574 /// Makes a default name with the given index. e.g. Constraint1, Constraint2, ... Constrainti
576 private string MakeName(int index) {
578 return "Constraint1";
580 return "Constraint" + index.ToString(System.Globalization.CultureInfo.InvariantCulture);
584 /// <para>Raises the <see cref='System.Data.ConstraintCollection.CollectionChanged'/> event.</para>
586 private void OnCollectionChanged(CollectionChangeEventArgs ccevent) {
587 if (onCollectionChanged != null) {
588 onCollectionChanged(this, ccevent);
593 /// Registers this name as being used in the collection. Will throw an ArgumentException
594 /// if the name is already being used. Called by Add, All property, and Constraint.ConstraintName property.
595 /// if the name is equivalent to the next default name to hand out, we increment our defaultNameIndex.
597 internal void RegisterName(string name) {
598 Debug.Assert (name != null);
600 int constraintCount = List.Count;
601 for (int i = 0; i < constraintCount; i++) {
602 if (NamesEqual(name, ((Constraint)List[i]).ConstraintName, true, table.Locale) != 0) {
603 throw ExceptionBuilder.DuplicateConstraintName(((Constraint)List[i]).ConstraintName);
606 if (NamesEqual(name, MakeName(defaultNameIndex), true, table.Locale) != 0) {
613 /// Removes the specified <see cref='System.Data.Constraint'/>
614 /// from the collection.</para>
616 public void Remove(Constraint constraint) {
617 if (constraint == null)
618 throw ExceptionBuilder.ArgumentNull("constraint");
620 // this will throw an exception if it can't be removed, otherwise indicates
621 // whether we need to remove it from the collection.
622 if (CanRemove(constraint, true)) {
623 // constraint can be removed
624 BaseRemove(constraint);
625 ArrayRemove(constraint);
626 if (constraint is UniqueConstraint && ((UniqueConstraint)constraint).IsPrimaryKey) {
627 Table.PrimaryKey = null;
630 OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Remove, constraint));
635 /// <para>Removes the constraint at the specified index from the
636 /// collection.</para>
638 public void RemoveAt(int index) {
639 Constraint c = this[index];
641 throw ExceptionBuilder.ConstraintOutOfRange(index);
646 /// <para>Removes the constraint, specified by name, from the collection.</para>
648 public void Remove(string name) {
649 Constraint c = this[name];
651 throw ExceptionBuilder.ConstraintNotInTheTable(name);
656 /// Unregisters this name as no longer being used in the collection. Called by Remove, All property, and
657 /// Constraint.ConstraintName property. If the name is equivalent to the last proposed default name, we walk backwards
658 /// to find the next proper default name to use.
660 internal void UnregisterName(string name) {
661 if (NamesEqual(name, MakeName(defaultNameIndex - 1), true, table.Locale) != 0) {
664 } while (defaultNameIndex > 1 &&
665 !Contains(MakeName(defaultNameIndex - 1)));
669 internal void FinishInitConstraints() {
670 if (delayLoadingConstraints == null)
674 DataColumn[] parents, childs;
675 for (int i = 0; i < delayLoadingConstraints.Length; i++) {
676 if (delayLoadingConstraints[i] is UniqueConstraint) {
677 if (fLoadForeignKeyConstraintsOnly)
680 UniqueConstraint constr = (UniqueConstraint) delayLoadingConstraints[i];
681 if (constr.columnNames == null) {
685 colCount = constr.columnNames.Length;
686 parents = new DataColumn[colCount];
687 for (int j = 0; j < colCount; j++)
688 parents[j] = table.Columns[constr.columnNames[j]];
689 if (constr.bPrimaryKey) {
690 if (table.primaryKey != null) {
691 throw ExceptionBuilder.AddPrimaryKeyConstraint();
694 Add(constr.ConstraintName,parents,true);
698 UniqueConstraint newConstraint = new UniqueConstraint(constr.constraintName, parents);
699 if (FindConstraint(newConstraint) == null)
700 this.Add(newConstraint);
703 ForeignKeyConstraint constr = (ForeignKeyConstraint) delayLoadingConstraints[i];
704 if (constr.parentColumnNames == null ||constr.childColumnNames == null) {
709 if (table.DataSet == null) {
710 fLoadForeignKeyConstraintsOnly = true;
714 colCount = constr.parentColumnNames.Length;
715 parents = new DataColumn[colCount];
716 childs = new DataColumn[colCount];
717 for (int j = 0; j < colCount; j++) {
718 if (constr.parentTableNamespace == null)
719 parents[j] = table.DataSet.Tables[constr.parentTableName].Columns[constr.parentColumnNames[j]];
721 parents[j] = table.DataSet.Tables[constr.parentTableName, constr.parentTableNamespace].Columns[constr.parentColumnNames[j]];
722 childs[j] = table.Columns[constr.childColumnNames[j]];
724 ForeignKeyConstraint newConstraint = new ForeignKeyConstraint(constr.constraintName, parents, childs);
725 newConstraint.AcceptRejectRule = constr.acceptRejectRule;
726 newConstraint.DeleteRule = constr.deleteRule;
727 newConstraint.UpdateRule = constr.updateRule;
728 this.Add(newConstraint);
732 if (!fLoadForeignKeyConstraintsOnly)
733 delayLoadingConstraints = null;