2 // System.Data.ConstraintCollection.cs
5 // Franklin Wise <gracenote@earthlink.net>
8 // (C) Ximian, Inc. 2002
9 // (C) 2002 Franklin Wise
10 // (C) 2002 Daniel Morgan
14 using System.Collections;
15 using System.ComponentModel;
17 namespace System.Data {
20 internal delegate void DelegateValidateRemoveConstraint(ConstraintCollection sender, Constraint constraintToRemove, ref bool fail,ref string failReason);
23 /// hold collection of constraints for data table
25 [DefaultEvent ("CollectionChanged")]
26 [EditorAttribute("Microsoft.VSDesigner.Data.Design.ConstraintsCollectionEditor, "+Consts.AssemblyMicrosoft_VSDesigner, "System.Drawing.Design.UITypeEditor, "+Consts.AssemblySystem_Drawing )]
28 public class ConstraintCollection : InternalDataCollectionBase
30 //private bool beginInit = false;
32 public event CollectionChangeEventHandler CollectionChanged;
33 internal event DelegateValidateRemoveConstraint ValidateRemoveConstraint;
34 private DataTable table;
36 // Call this to set the "table" property of the UniqueConstraint class
37 // intialized with UniqueConstraint( string, string[], bool );
38 // And also validate that the named columns exist in the "table"
39 private delegate void PostAddRange( DataTable table );
41 // Keep reference to most recent constraints passed to AddRange()
42 // so that they can be added when EndInit() is called.
43 private Constraint [] _mostRecentConstraints;
45 //Don't allow public instantiation
46 //Will be instantianted from DataTable
47 internal ConstraintCollection(DataTable table){
51 internal DataTable Table{
57 public virtual Constraint this[string name] {
59 //If the name is not found we just return null
60 int index = IndexOf(name); //case insensitive
61 if (-1 == index) return null;
66 public virtual Constraint this[int index] {
68 if (index < 0 || index >= List.Count)
69 throw new IndexOutOfRangeException();
70 return (Constraint)List[index];
74 private void _handleBeforeConstraintNameChange(object sender, string newName)
77 if (newName == null || newName == "")
78 throw new ArgumentException("ConstraintName cannot be set to null or empty " +
79 " after it has been added to a ConstraintCollection.");
81 if (_isDuplicateConstraintName(newName,(Constraint)sender))
82 throw new DuplicateNameException("Constraint name already exists.");
85 private bool _isDuplicateConstraintName(string constraintName, Constraint excludeFromComparison)
87 foreach (Constraint cst in List) {
88 if (String.Compare (constraintName, cst.ConstraintName, !Table.CaseSensitive) == 0 && cst != excludeFromComparison)
95 //finds an open name slot of ConstraintXX
96 //where XX is a number
97 private string _createNewConstraintName()
99 bool loopAgain = false;
105 foreach (Constraint cst in List)
108 if (String.Compare (cst.ConstraintName,
109 "Constraint" + index,
110 !Table.CaseSensitive,
121 return "Constraint" + index.ToString();
126 // Overloaded Add method (5 of them)
127 // to add Constraint object to the collection
129 public void Add(Constraint constraint)
132 if (null == constraint) throw new ArgumentNullException("Can not add null.");
134 //check constraint membership
135 //can't already exist in this collection or any other
136 if (this == constraint.ConstraintCollection)
137 throw new ArgumentException("Constraint already belongs to this collection.");
138 if (null != constraint.ConstraintCollection)
139 throw new ArgumentException("Constraint already belongs to another collection.");
141 //check for duplicate name
143 if (_isDuplicateConstraintName(constraint.ConstraintName,null) )
144 throw new DuplicateNameException("Constraint name already exists.");
147 // Check whether Constraint is UniqueConstraint and initailized with the special
148 // constructor - UniqueConstraint( string, string[], bool );
149 // If yes, It must be added via AddRange() only
150 // Environment.StackTrace can help us
151 // FIXME: Is a different mechanism to do this?
152 if (constraint is UniqueConstraint){
153 if ((constraint as UniqueConstraint).DataColsNotValidated == true){
154 if ( Environment.StackTrace.IndexOf( "AddRange" ) == -1 ){
155 throw new ArgumentException(" Some DataColumns are invalid - They may not belong to the table associated with this Constraint Collection" );
160 if (constraint is ForeignKeyConstraint){
161 if ((constraint as ForeignKeyConstraint).DataColsNotValidated == true){
162 if ( Environment.StackTrace.IndexOf( "AddRange" ) == -1 ){
163 throw new ArgumentException(" Some DataColumns are invalid - They may not belong to the table associated with this Constraint Collection" );
168 //Allow constraint to run validation rules and setup
169 constraint.AddToConstraintCollectionSetup(this); //may throw if it can't setup
171 //Run Constraint to check existing data in table
172 // this is redundant, since AddToConstraintCollectionSetup
173 // calls AssertConstraint right before this call
174 //constraint.AssertConstraint();
176 //if name is null or empty give it a name
177 if (constraint.ConstraintName == null ||
178 constraint.ConstraintName == "" )
180 constraint.ConstraintName = _createNewConstraintName();
183 //Add event handler for ConstraintName change
184 constraint.BeforeConstraintNameChange += new DelegateConstraintNameChange(
185 _handleBeforeConstraintNameChange);
187 constraint.ConstraintCollection = this;
188 List.Add(constraint);
190 if (constraint is UniqueConstraint)
191 ((UniqueConstraint)constraint).UpdatePrimaryKey();
193 OnCollectionChanged( new CollectionChangeEventArgs( CollectionChangeAction.Add, this) );
198 public virtual Constraint Add(string name, DataColumn column, bool primaryKey)
201 UniqueConstraint uc = new UniqueConstraint(name, column, primaryKey);
207 public virtual Constraint Add(string name, DataColumn primaryKeyColumn,
208 DataColumn foreignKeyColumn)
210 ForeignKeyConstraint fc = new ForeignKeyConstraint(name, primaryKeyColumn,
217 public virtual Constraint Add(string name, DataColumn[] columns, bool primaryKey)
219 UniqueConstraint uc = new UniqueConstraint(name, columns, primaryKey);
225 public virtual Constraint Add(string name, DataColumn[] primaryKeyColumns,
226 DataColumn[] foreignKeyColumns)
228 ForeignKeyConstraint fc = new ForeignKeyConstraint(name, primaryKeyColumns,
235 public void AddRange(Constraint[] constraints) {
237 //When AddRange() occurs after BeginInit,
238 //it does not add any elements to the collection until EndInit is called.
239 if (this.table.InitStatus == DataTable.initStatus.BeginInit){
240 // Keep reference so that they can be added when EndInit() is called.
241 _mostRecentConstraints = constraints;
245 // Check whether the constraint is UniqueConstraint
246 // And whether it was initialized with the special ctor
247 // i.e UniqueConstraint( string, string[], bool );
248 for (int i = 0; i < constraints.Length; i++){
249 if (constraints[i] is UniqueConstraint){
250 if (( constraints[i] as UniqueConstraint).DataColsNotValidated == true){
251 PostAddRange _postAddRange= new PostAddRange ((constraints[i] as UniqueConstraint).PostAddRange);
252 // UniqueConstraint.PostAddRange() validates whether all named
253 // columns exist in the table associated with this instance of
254 // ConstraintCollection.
255 _postAddRange (this.table);
259 else if (constraints [i] is ForeignKeyConstraint){
260 if (( constraints [i] as ForeignKeyConstraint).DataColsNotValidated == true){
261 (constraints [i] as ForeignKeyConstraint).postAddRange (this.table);
267 if ( (constraints == null) || (constraints.Length == 0))
268 throw new ArgumentNullException ("Cannot add null");
271 foreach (Constraint constraint in constraints)
279 // Helper AddRange() - Call this function when EndInit is called
280 internal void PostEndInit()
282 AddRange (_mostRecentConstraints);
286 public bool CanRemove(Constraint constraint)
289 //Rule A UniqueConstraint can't be removed if there is
290 //a foreign key relationship to that column
293 //LAMESPEC: MSFT implementation throws and exception here
294 //spec says nothing about this
295 if (null == constraint) throw new ArgumentNullException("Constraint can't be null.");
297 //LAMESPEC: spec says return false (which makes sense) and throw exception for False case (?).
298 //TODO: I may want to change how this is done
299 //maybe put a CanRemove on the Constraint class
300 //and have the Constraint fire this event
302 //discover if there is a related ForeignKey
303 string failReason ="";
304 return _canRemoveConstraint(constraint, ref failReason);
311 //CanRemove? See Lamespec below.
313 //the Constraints have a reference to us
314 //and we listen to name change events
315 //we should remove these before clearing
316 foreach (Constraint con in List)
318 con.ConstraintCollection = null;
319 con.BeforeConstraintNameChange -= new DelegateConstraintNameChange(
320 _handleBeforeConstraintNameChange);
323 //LAMESPEC: MSFT implementation allows this
324 //even when a ForeignKeyConstraint exist for a UniqueConstraint
325 //thus violating the CanRemove logic
326 //CanRemove will throws Exception incase of the above
327 List.Clear(); //Will violate CanRemove rule
328 OnCollectionChanged( new CollectionChangeEventArgs(CollectionChangeAction.Refresh, this) );
331 public bool Contains(string name)
333 return (-1 != IndexOf(name));
336 public int IndexOf(Constraint constraint)
338 return List.IndexOf(constraint);
341 public virtual int IndexOf(string constraintName)
343 //LAMESPEC: Spec doesn't say case insensitive
344 //it should to be consistant with the other
345 //case insensitive comparisons in this class
348 foreach (Constraint con in List)
350 if (String.Compare (constraintName, con.ConstraintName, !Table.CaseSensitive, Table.Locale) == 0)
357 return -1; //not found
360 public void Remove(Constraint constraint) {
361 //LAMESPEC: spec doesn't document the ArgumentException the
362 //will be thrown if the CanRemove rule is violated
364 //LAMESPEC: spec says an exception will be thrown
365 //if the element is not in the collection. The implementation
366 //doesn't throw an exception. ArrayList.Remove doesn't throw if the
367 //element doesn't exist
368 //ALSO the overloaded remove in the spec doesn't say it throws any exceptions
371 if (null == constraint) throw new ArgumentNullException();
373 string failReason = "";
374 if (! _canRemoveConstraint(constraint, ref failReason) )
376 if (failReason != null || failReason != "")
377 throw new ArgumentException(failReason);
379 throw new ArgumentException("Can't remove constraint.");
382 constraint.RemoveFromConstraintCollectionCleanup(this);
383 List.Remove(constraint);
384 OnCollectionChanged( new CollectionChangeEventArgs(CollectionChangeAction.Remove,this));
387 public void Remove(string name)
389 //if doesn't exist fail quietly
390 int index = IndexOf(name);
391 if (-1 == index) return;
396 public void RemoveAt(int index)
401 protected override ArrayList List {
407 protected virtual void OnCollectionChanged( CollectionChangeEventArgs ccevent)
409 if (null != CollectionChanged)
411 CollectionChanged(this, ccevent);
415 private bool _canRemoveConstraint(Constraint constraint, ref string failReason )
419 if (null != ValidateRemoveConstraint)
421 ValidateRemoveConstraint(this, constraint, ref cancel, ref tmp);
427 internal ICollection UniqueConstraints
431 return GetConstraintsCollection(typeof(UniqueConstraint));
435 internal ICollection ForeignKeyConstraints
439 return GetConstraintsCollection(typeof(ForeignKeyConstraint));
443 private ICollection GetConstraintsCollection (Type constraintType)
445 ArrayList cCollection = new ArrayList();
446 foreach (Constraint c in List)
448 if (c.GetType() == constraintType)