//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // // [....] // [....] // [....] //------------------------------------------------------------------------------ namespace System.Data { using System; using System.Diagnostics; using System.Collections; using System.ComponentModel; /// /// Represents a collection of constraints for a /// . /// [ DefaultEvent("CollectionChanged"), Editor("Microsoft.VSDesigner.Data.Design.ConstraintsCollectionEditor, " + AssemblyRef.MicrosoftVSDesigner, "System.Drawing.Design.UITypeEditor, " + AssemblyRef.SystemDrawing), ] public sealed class ConstraintCollection : InternalDataCollectionBase { // WebData 111752 private readonly DataTable table; // private Constraint[] constraints = new Constraint[2]; private readonly ArrayList list = new ArrayList(); private int defaultNameIndex = 1; private CollectionChangeEventHandler onCollectionChanged; private Constraint[] delayLoadingConstraints; private bool fLoadForeignKeyConstraintsOnly = false; /// /// ConstraintCollection constructor. Used only by DataTable. /// internal ConstraintCollection(DataTable table) { this.table = table; } /// /// Gets the list of objects contained by the collection. /// protected override ArrayList List { get { return list; } } /// /// Gets the /// from the collection at the specified index. /// public Constraint this[int index] { get { if (index >= 0 && index < List.Count) { return(Constraint) List[index]; } throw ExceptionBuilder.ConstraintOutOfRange(index); } } /// /// The DataTable with which this ConstraintCollection is associated /// internal DataTable Table { get { return table; } } /// /// Gets the from the collection with the specified name. /// public Constraint this[string name] { get { int index = InternalIndexOf(name); if (index == -2) { throw ExceptionBuilder.CaseInsensitiveNameConflict(name); } return (index < 0) ? null : (Constraint)List[index]; } } /// /// /// Adds the constraint to the collection. /// public void Add(Constraint constraint) { Add(constraint, true); } // To add foreign key constraint without adding any unique constraint for internal use. Main purpose : Binary Remoting internal void Add(Constraint constraint, bool addUniqueWhenAddingForeign) { if (constraint == null) throw ExceptionBuilder.ArgumentNull("constraint"); // It is an error if we find an equivalent constraint already in collection if (FindConstraint(constraint) != null) { throw ExceptionBuilder.DuplicateConstraint(FindConstraint(constraint).ConstraintName); } if (1 < table.NestedParentRelations.Length) { if (!AutoGenerated(constraint)) { throw ExceptionBuilder.CantAddConstraintToMultipleNestedTable(table.TableName); } } if (constraint is UniqueConstraint) { if (((UniqueConstraint)constraint).bPrimaryKey) { if (Table.primaryKey != null) { throw ExceptionBuilder.AddPrimaryKeyConstraint(); } } AddUniqueConstraint((UniqueConstraint)constraint); } else if (constraint is ForeignKeyConstraint) { ForeignKeyConstraint fk = (ForeignKeyConstraint)constraint; if (addUniqueWhenAddingForeign) { UniqueConstraint key = fk.RelatedTable.Constraints.FindKeyConstraint(fk.RelatedColumnsReference); if (key == null) { if (constraint.ConstraintName.Length == 0) constraint.ConstraintName = AssignName(); else RegisterName(constraint.ConstraintName); key = new UniqueConstraint(fk.RelatedColumnsReference); fk.RelatedTable.Constraints.Add(key); } } AddForeignKeyConstraint((ForeignKeyConstraint)constraint); } BaseAdd(constraint); ArrayAdd(constraint); OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Add, constraint)); if (constraint is UniqueConstraint) { if (((UniqueConstraint)constraint).bPrimaryKey) { Table.PrimaryKey = ((UniqueConstraint)constraint).ColumnsReference; } } } /// /// Constructs a new using the /// specified array of /// objects and adds it to the collection. /// public Constraint Add(string name, DataColumn[] columns, bool primaryKey) { UniqueConstraint constraint = new UniqueConstraint(name, columns); Add(constraint); if (primaryKey) Table.PrimaryKey = columns; return constraint; } /// /// Constructs a new using the /// specified and adds it to the collection. /// public Constraint Add(string name, DataColumn column, bool primaryKey) { UniqueConstraint constraint = new UniqueConstraint(name, column); Add(constraint); if (primaryKey) Table.PrimaryKey = constraint.ColumnsReference; return constraint; } /// /// /// Constructs a new /// with the /// specified parent and child /// columns and adds the constraint to the collection. /// public Constraint Add(string name, DataColumn primaryKeyColumn, DataColumn foreignKeyColumn) { ForeignKeyConstraint constraint = new ForeignKeyConstraint(name, primaryKeyColumn, foreignKeyColumn); Add(constraint); return constraint; } /// /// Constructs a new with the specified parent columns and /// child columns and adds the constraint to the collection. /// public Constraint Add(string name, DataColumn[] primaryKeyColumns, DataColumn[] foreignKeyColumns) { ForeignKeyConstraint constraint = new ForeignKeyConstraint(name, primaryKeyColumns, foreignKeyColumns); Add(constraint); return constraint; } public void AddRange(Constraint[] constraints ) { if (table.fInitInProgress) { delayLoadingConstraints = constraints; fLoadForeignKeyConstraintsOnly = false; return; } if (constraints != null) { foreach(Constraint constr in constraints) { if (constr != null) { Add(constr); } } } } private void AddUniqueConstraint(UniqueConstraint constraint) { DataColumn[] columns = constraint.ColumnsReference; for (int i = 0; i < columns.Length; i++) { if (columns[i].Table != this.table) { throw ExceptionBuilder.ConstraintForeignTable(); } } constraint.ConstraintIndexInitialize(); if (!constraint.CanEnableConstraint()) { constraint.ConstraintIndexClear(); throw ExceptionBuilder.UniqueConstraintViolation(); } } private void AddForeignKeyConstraint(ForeignKeyConstraint constraint) { if (!constraint.CanEnableConstraint()) { throw ExceptionBuilder.ConstraintParentValues(); } constraint.CheckCanAddToCollection(this); } private bool AutoGenerated(Constraint constraint) { ForeignKeyConstraint fk = (constraint as ForeignKeyConstraint); if (null != fk) { return XmlTreeGen.AutoGenerated(fk, false); } else { UniqueConstraint unique = (UniqueConstraint) constraint; return XmlTreeGen.AutoGenerated(unique); } } /// /// Occurs when the is changed through additions or /// removals. /// public event CollectionChangeEventHandler CollectionChanged { add { onCollectionChanged += value; } remove { onCollectionChanged -= value; } } /// /// Adds the constraint to the constraints array. /// private void ArrayAdd(Constraint constraint) { Debug.Assert(constraint != null, "Attempt to add null constraint to constraint array"); List.Add(constraint); } private void ArrayRemove(Constraint constraint) { List.Remove(constraint); } /// /// Creates a new default name. /// internal string AssignName() { string newName = MakeName(defaultNameIndex); defaultNameIndex++; return newName; } /// /// Does verification on the constraint and it's name. /// An ArgumentNullException is thrown if this constraint is null. An ArgumentException is thrown if this constraint /// already belongs to this collection, belongs to another collection. /// A DuplicateNameException is thrown if this collection already has a constraint with the same /// name (case insensitive). /// private void BaseAdd(Constraint constraint) { if (constraint == null) throw ExceptionBuilder.ArgumentNull("constraint"); if (constraint.ConstraintName.Length == 0) constraint.ConstraintName = AssignName(); else RegisterName(constraint.ConstraintName); constraint.InCollection = true; } /// /// BaseGroupSwitch will intelligently remove and add tables from the collection. /// private void BaseGroupSwitch(Constraint[] oldArray, int oldLength, Constraint[] newArray, int newLength) { // We're doing a smart diff of oldArray and newArray to find out what // should be removed. We'll pass through oldArray and see if it exists // in newArray, and if not, do remove work. newBase is an opt. in case // the arrays have similar prefixes. int newBase = 0; for (int oldCur = 0; oldCur < oldLength; oldCur++) { bool found = false; for (int newCur = newBase; newCur < newLength; newCur++) { if (oldArray[oldCur] == newArray[newCur]) { if (newBase == newCur) { newBase++; } found = true; break; } } if (!found) { // This means it's in oldArray and not newArray. Remove it. BaseRemove(oldArray[oldCur]); List.Remove(oldArray[oldCur]); } } // Now, let's pass through news and those that don't belong, add them. for (int newCur = 0; newCur < newLength; newCur++) { if (!newArray[newCur].InCollection) BaseAdd(newArray[newCur]); List.Add(newArray[newCur]); } } /// /// Does verification on the constraint and it's name. /// An ArgumentNullException is thrown if this constraint is null. An ArgumentException is thrown /// if this constraint doesn't belong to this collection or if this constraint is part of a relationship. /// private void BaseRemove(Constraint constraint) { if (constraint == null) { throw ExceptionBuilder.ArgumentNull("constraint"); } if (constraint.Table != table) { throw ExceptionBuilder.ConstraintRemoveFailed(); } UnregisterName(constraint.ConstraintName); constraint.InCollection = false; if (constraint is UniqueConstraint) { for (int i = 0; i < Table.ChildRelations.Count; i++) { DataRelation rel = Table.ChildRelations[i]; if (rel.ParentKeyConstraint == constraint) rel.SetParentKeyConstraint(null); } ((UniqueConstraint)constraint).ConstraintIndexClear(); } else if (constraint is ForeignKeyConstraint) { for (int i = 0; i < Table.ParentRelations.Count; i++) { DataRelation rel = Table.ParentRelations[i]; if (rel.ChildKeyConstraint == constraint) rel.SetChildKeyConstraint(null); } } } /// /// Indicates if a can be removed. /// // PUBLIC because called by design-time... need to consider this. public bool CanRemove(Constraint constraint) { return CanRemove(constraint, /*fThrowException:*/false); } internal bool CanRemove(Constraint constraint, bool fThrowException) { return constraint.CanBeRemovedFromCollection(this, fThrowException); } /// /// Clears the collection of any /// objects. /// public void Clear() { if (table != null) { table.PrimaryKey = null; for (int i = 0; i < table.ParentRelations.Count; i++) { table.ParentRelations[i].SetChildKeyConstraint(null); } for (int i = 0; i < table.ChildRelations.Count; i++) { table.ChildRelations[i].SetParentKeyConstraint(null); } } if (table.fInitInProgress && delayLoadingConstraints != null) { delayLoadingConstraints = null; fLoadForeignKeyConstraintsOnly = false; } int oldLength = List.Count; Constraint[] constraints = new Constraint[List.Count]; List.CopyTo(constraints, 0); try { // this will smartly add and remove the appropriate tables. BaseGroupSwitch(constraints, oldLength, null, 0); } catch (Exception e) { // if (Common.ADP.IsCatchableOrSecurityExceptionType(e)) { // something messed up. restore to original state. BaseGroupSwitch(null, 0, constraints, oldLength); List.Clear(); for (int i = 0; i < oldLength; i++) List.Add(constraints[i]); } throw; } List.Clear(); OnCollectionChanged(RefreshEventArgs); } /// /// Indicates whether the , specified by name, exists in the collection. /// public bool Contains(string name) { return (InternalIndexOf(name) >= 0); } internal bool Contains(string name, bool caseSensitive) { if (!caseSensitive) return Contains(name); int index = InternalIndexOf(name); if (index<0) return false; return (name == ((Constraint) List[index]).ConstraintName); } public void CopyTo(Constraint[] array, int index) { if (array==null) throw ExceptionBuilder.ArgumentNull("array"); if (index < 0) throw ExceptionBuilder.ArgumentOutOfRange("index"); if (array.Length - index < list.Count) throw ExceptionBuilder.InvalidOffsetLength(); for(int i = 0; i < list.Count; ++i) { array[index + i] = (Constraint)list[i]; } } /// /// Returns a matching constriant object. /// internal Constraint FindConstraint(Constraint constraint) { int constraintCount = List.Count; for (int i = 0; i < constraintCount; i++) { if (((Constraint)List[i]).Equals(constraint)) return(Constraint)List[i]; } return null; } /// /// Returns a matching constriant object. /// internal UniqueConstraint FindKeyConstraint(DataColumn[] columns) { int constraintCount = List.Count; for (int i = 0; i < constraintCount; i++) { UniqueConstraint constraint = (List[i] as UniqueConstraint); if ((null != constraint) && CompareArrays(constraint.Key.ColumnsReference, columns)) { return constraint; } } return null; } /// /// Returns a matching constriant object. /// internal UniqueConstraint FindKeyConstraint(DataColumn column) { int constraintCount = List.Count; for (int i = 0; i < constraintCount; i++) { UniqueConstraint constraint = (List[i] as UniqueConstraint); if ((null != constraint) && (constraint.Key.ColumnsReference.Length == 1) && (constraint.Key.ColumnsReference[0] == column)) return constraint; } return null; } /// /// Returns a matching constriant object. /// internal ForeignKeyConstraint FindForeignKeyConstraint(DataColumn[] parentColumns, DataColumn[] childColumns) { int constraintCount = List.Count; for (int i = 0; i < constraintCount; i++) { ForeignKeyConstraint constraint = (List[i] as ForeignKeyConstraint); if ((null != constraint) && CompareArrays(constraint.ParentKey.ColumnsReference, parentColumns) && CompareArrays(constraint.ChildKey.ColumnsReference, childColumns)) return constraint; } return null; } private static bool CompareArrays(DataColumn[] a1, DataColumn[] a2) { Debug.Assert(a1 != null && a2 != null, "Invalid Arguments"); if (a1.Length != a2.Length) return false; int i, j; for (i=0; i /// Returns the index of the specified . /// public int IndexOf(Constraint constraint) { if (null != constraint) { int count = Count; for (int i = 0; i < count; ++i) { if (constraint == (Constraint) List[i]) return i; } // didnt find the constraint } return -1; } /// /// Returns the index of the , specified by name. /// public int IndexOf(string constraintName) { int index = InternalIndexOf(constraintName); return (index < 0) ? -1 : index; } // Return value: // >= 0: find the match // -1: No match // -2: At least two matches with different cases internal int InternalIndexOf(string constraintName) { int cachedI = -1; if ((null != constraintName) && (0 < constraintName.Length)) { int constraintCount = List.Count; int result = 0; for (int i = 0; i < constraintCount; i++) { Constraint constraint = (Constraint) List[i]; result = NamesEqual(constraint.ConstraintName, constraintName, false, table.Locale); if (result == 1) return i; if (result == -1) cachedI = (cachedI == -1) ? i : -2; } } return cachedI; } /// /// Makes a default name with the given index. e.g. Constraint1, Constraint2, ... Constrainti /// private string MakeName(int index) { if (1 == index) { return "Constraint1"; } return "Constraint" + index.ToString(System.Globalization.CultureInfo.InvariantCulture); } /// /// Raises the event. /// private void OnCollectionChanged(CollectionChangeEventArgs ccevent) { if (onCollectionChanged != null) { onCollectionChanged(this, ccevent); } } /// /// Registers this name as being used in the collection. Will throw an ArgumentException /// if the name is already being used. Called by Add, All property, and Constraint.ConstraintName property. /// if the name is equivalent to the next default name to hand out, we increment our defaultNameIndex. /// internal void RegisterName(string name) { Debug.Assert (name != null); int constraintCount = List.Count; for (int i = 0; i < constraintCount; i++) { if (NamesEqual(name, ((Constraint)List[i]).ConstraintName, true, table.Locale) != 0) { throw ExceptionBuilder.DuplicateConstraintName(((Constraint)List[i]).ConstraintName); } } if (NamesEqual(name, MakeName(defaultNameIndex), true, table.Locale) != 0) { defaultNameIndex++; } } /// /// /// Removes the specified /// from the collection. /// public void Remove(Constraint constraint) { if (constraint == null) throw ExceptionBuilder.ArgumentNull("constraint"); // this will throw an exception if it can't be removed, otherwise indicates // whether we need to remove it from the collection. if (CanRemove(constraint, true)) { // constraint can be removed BaseRemove(constraint); ArrayRemove(constraint); if (constraint is UniqueConstraint && ((UniqueConstraint)constraint).IsPrimaryKey) { Table.PrimaryKey = null; } OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Remove, constraint)); } } /// /// Removes the constraint at the specified index from the /// collection. /// public void RemoveAt(int index) { Constraint c = this[index]; if (c == null) throw ExceptionBuilder.ConstraintOutOfRange(index); Remove(c); } /// /// Removes the constraint, specified by name, from the collection. /// public void Remove(string name) { Constraint c = this[name]; if (c == null) throw ExceptionBuilder.ConstraintNotInTheTable(name); Remove(c); } /// /// Unregisters this name as no longer being used in the collection. Called by Remove, All property, and /// Constraint.ConstraintName property. If the name is equivalent to the last proposed default name, we walk backwards /// to find the next proper default name to use. /// internal void UnregisterName(string name) { if (NamesEqual(name, MakeName(defaultNameIndex - 1), true, table.Locale) != 0) { do { defaultNameIndex--; } while (defaultNameIndex > 1 && !Contains(MakeName(defaultNameIndex - 1))); } } internal void FinishInitConstraints() { if (delayLoadingConstraints == null) return; int colCount; DataColumn[] parents, childs; for (int i = 0; i < delayLoadingConstraints.Length; i++) { if (delayLoadingConstraints[i] is UniqueConstraint) { if (fLoadForeignKeyConstraintsOnly) continue; UniqueConstraint constr = (UniqueConstraint) delayLoadingConstraints[i]; if (constr.columnNames == null) { this.Add(constr); continue; } colCount = constr.columnNames.Length; parents = new DataColumn[colCount]; for (int j = 0; j < colCount; j++) parents[j] = table.Columns[constr.columnNames[j]]; if (constr.bPrimaryKey) { if (table.primaryKey != null) { throw ExceptionBuilder.AddPrimaryKeyConstraint(); } else { Add(constr.ConstraintName,parents,true); } continue; } UniqueConstraint newConstraint = new UniqueConstraint(constr.constraintName, parents); if (FindConstraint(newConstraint) == null) this.Add(newConstraint); } else { ForeignKeyConstraint constr = (ForeignKeyConstraint) delayLoadingConstraints[i]; if (constr.parentColumnNames == null ||constr.childColumnNames == null) { this.Add(constr); continue; } if (table.DataSet == null) { fLoadForeignKeyConstraintsOnly = true; continue; } colCount = constr.parentColumnNames.Length; parents = new DataColumn[colCount]; childs = new DataColumn[colCount]; for (int j = 0; j < colCount; j++) { if (constr.parentTableNamespace == null) parents[j] = table.DataSet.Tables[constr.parentTableName].Columns[constr.parentColumnNames[j]]; else parents[j] = table.DataSet.Tables[constr.parentTableName, constr.parentTableNamespace].Columns[constr.parentColumnNames[j]]; childs[j] = table.Columns[constr.childColumnNames[j]]; } ForeignKeyConstraint newConstraint = new ForeignKeyConstraint(constr.constraintName, parents, childs); newConstraint.AcceptRejectRule = constr.acceptRejectRule; newConstraint.DeleteRule = constr.deleteRule; newConstraint.UpdateRule = constr.updateRule; this.Add(newConstraint); } } if (!fLoadForeignKeyConstraintsOnly) delayLoadingConstraints = null; } } }