2 // System.Data.ForeignKeyConstraint.cs
5 // Franklin Wise <gracenote@earthlink.net>
6 // Daniel Morgan <danmorg@sc.rr.com>
8 // (C) 2002 Franklin Wise
9 // (C) 2002 Daniel Morgan
13 using System.Collections;
14 using System.ComponentModel;
15 using System.Runtime.InteropServices;
17 namespace System.Data {
19 [DefaultProperty ("ConstraintName")]
21 public class ForeignKeyConstraint : Constraint
23 private UniqueConstraint _parentUniqueConstraint;
24 private DataColumn [] _parentColumns;
25 private DataColumn [] _childColumns;
26 private Rule _deleteRule;
27 private Rule _updateRule;
28 private AcceptRejectRule _acceptRejectRule;
32 public ForeignKeyConstraint(DataColumn parentColumn, DataColumn childColumn)
34 if (null == parentColumn || null == childColumn) {
35 throw new ArgumentNullException("Neither parentColumn or" +
36 " childColumn can be null.");
39 _foreignKeyConstraint(null, new DataColumn[] {parentColumn},
40 new DataColumn[] {childColumn});
43 public ForeignKeyConstraint(DataColumn[] parentColumns, DataColumn[] childColumns)
45 _foreignKeyConstraint(null, parentColumns, childColumns);
48 public ForeignKeyConstraint(string constraintName, DataColumn parentColumn, DataColumn childColumn)
50 if (null == parentColumn || null == childColumn) {
51 throw new ArgumentNullException("Neither parentColumn or" +
52 " childColumn can be null.");
55 _foreignKeyConstraint(constraintName, new DataColumn[] {parentColumn},
56 new DataColumn[] {childColumn});
59 public ForeignKeyConstraint(string constraintName, DataColumn[] parentColumns, DataColumn[] childColumns)
61 _foreignKeyConstraint(constraintName, parentColumns, childColumns);
67 public ForeignKeyConstraint(string constraintName, string parentTableName, string[] parentColumnNames, string[] childColumnNames, AcceptRejectRule acceptRejectRule, Rule deleteRule, Rule updateRule)
71 private void _foreignKeyConstraint(string constraintName, DataColumn[] parentColumns,
72 DataColumn[] childColumns)
76 _validateColumns(parentColumns, childColumns);
79 base.ConstraintName = constraintName;
81 //Keep reference to columns
82 _parentColumns = parentColumns;
83 _childColumns = childColumns;
86 #endregion // Constructors
90 private void _validateColumns(DataColumn[] parentColumns, DataColumn[] childColumns)
93 if (null == parentColumns || null == childColumns)
94 throw new ArgumentNullException();
96 //at least one element in each array
97 if (parentColumns.Length < 1 || childColumns.Length < 1)
98 throw new ArgumentException("Neither ParentColumns or ChildColumns can't be" +
102 if (parentColumns.Length != childColumns.Length)
103 throw new ArgumentException("Parent columns and child columns must be the same length.");
106 DataTable ptable = parentColumns[0].Table;
107 DataTable ctable = childColumns[0].Table;
110 foreach (DataColumn pc in parentColumns)
113 if (null == pc.Table)
115 throw new ArgumentException("All columns must belong to a table." +
116 " ColumnName: " + pc.ColumnName + " does not belong to a table.");
119 //All columns must belong to the same table
120 if (ptable != pc.Table)
121 throw new InvalidConstraintException("Parent columns must all belong to the same table.");
123 foreach (DataColumn cc in childColumns)
126 if (null == pc.Table)
128 throw new ArgumentException("All columns must belong to a table." +
129 " ColumnName: " + pc.ColumnName + " does not belong to a table.");
132 //All columns must belong to the same table.
133 if (ctable != cc.Table)
134 throw new InvalidConstraintException("Child columns must all belong to the same table.");
137 //Can't be the same column
139 throw new InvalidOperationException("Parent and child columns can't be the same column.");
141 foreach (DataColumn c2 in childColumns) {
142 if (!Object.ReferenceEquals (c2.Table, cc.Table))
143 throw new InvalidConstraintException ("Cannot create a Key from Columns thath belong to different tables.");
146 if (! pc.DataType.Equals(cc.DataType))
148 //LAMESPEC: spec says throw InvalidConstraintException
149 // implementation throws InvalidOperationException
150 throw new InvalidOperationException("Parent column is not type compatible with it's child"
157 //Same dataset. If both are null it's ok
158 if (ptable.DataSet != ctable.DataSet)
160 //LAMESPEC: spec says InvalidConstraintExceptoin
161 // impl does InvalidOperationException
162 throw new InvalidOperationException("Parent column and child column must belong to" +
163 " tables that belong to the same DataSet.");
172 private void _validateRemoveParentConstraint(ConstraintCollection sender,
173 Constraint constraint, ref bool cancel, ref string failReason)
175 //if we hold a reference to the parent then cancel it
176 if (constraint == _parentUniqueConstraint)
179 failReason = "Cannot remove UniqueConstraint because the"
180 + " ForeignKeyConstraint " + this.ConstraintName + " exists.";
184 //Checks to see if a related unique constraint exists
185 //if it doesn't then a unique constraint is created.
186 //if a unique constraint can't be created an exception will be thrown
187 private void _ensureUniqueConstraintExists(ConstraintCollection collection,
188 DataColumn [] parentColumns)
191 if (null == parentColumns) throw new ArgumentNullException(
192 "ParentColumns can't be null");
194 UniqueConstraint uc = null;
196 //see if unique constraint already exists
197 //if not create unique constraint
198 if(parentColumns[0] != null)
199 uc = UniqueConstraint.GetUniqueConstraintForColumnSet(parentColumns[0].Table.Constraints, parentColumns);
202 uc = new UniqueConstraint(parentColumns, false); //could throw
203 parentColumns [0].Table.Constraints.Add (uc);
207 _parentUniqueConstraint = uc;
208 //parentColumns [0].Table.Constraints.Add (uc);
209 //if this unique constraint is attempted to be removed before us
210 //we can fail the validation
211 //collection.ValidateRemoveConstraint += new DelegateValidateRemoveConstraint(
212 // _validateRemoveParentConstraint);
214 parentColumns [0].Table.Constraints.ValidateRemoveConstraint += new DelegateValidateRemoveConstraint(
215 _validateRemoveParentConstraint);
224 [DataCategory ("Data")]
225 [DataSysDescription ("For accept and reject changes, indicates what kind of cascading should take place across this relation.")]
226 [DefaultValue (AcceptRejectRule.None)]
227 public virtual AcceptRejectRule AcceptRejectRule {
228 get { return _acceptRejectRule; }
229 set { _acceptRejectRule = value; }
232 [DataCategory ("Data")]
233 [DataSysDescription ("Indicates the child columns of this constraint.")]
235 public virtual DataColumn[] Columns {
236 get { return _childColumns; }
239 [DataCategory ("Data")]
240 [DataSysDescription ("For deletions, indicates what kind of cascading should take place across this relation.")]
241 [DefaultValue (Rule.Cascade)]
242 public virtual Rule DeleteRule {
243 get { return _deleteRule; }
244 set { _deleteRule = value; }
247 [DataCategory ("Data")]
248 [DataSysDescription ("For updates, indicates what kind of cascading should take place across this relation.")]
249 [DefaultValue (Rule.Cascade)]
250 public virtual Rule UpdateRule {
251 get { return _updateRule; }
252 set { _updateRule = value; }
255 [DataCategory ("Data")]
256 [DataSysDescription ("Indicates the parent columns of this constraint.")]
258 public virtual DataColumn[] RelatedColumns {
259 get { return _parentColumns; }
262 [DataCategory ("Data")]
263 [DataSysDescription ("Indicates the child table of this constraint.")]
265 public virtual DataTable RelatedTable {
267 if (_parentColumns != null)
268 if (_parentColumns.Length > 0)
269 return _parentColumns[0].Table;
275 [DataCategory ("Data")]
276 [DataSysDescription ("Indicates the table of this constraint.")]
278 public override DataTable Table {
280 if (_childColumns != null)
281 if (_childColumns.Length > 0)
282 return _childColumns[0].Table;
288 #endregion // Properties
292 public override bool Equals(object key)
294 ForeignKeyConstraint fkc = key as ForeignKeyConstraint;
295 if (null == fkc) return false;
297 //if the fk constrains the same columns then they are equal
298 if (! DataColumn.AreColumnSetsTheSame( this.RelatedColumns, fkc.RelatedColumns))
300 if (! DataColumn.AreColumnSetsTheSame( this.Columns, fkc.Columns) )
306 public override int GetHashCode()
308 //initialize hash1 and hash2 with default hashes
309 //any two DIFFERENT numbers will do here
310 int hash1 = 32, hash2 = 88;
313 //derive the hash code from the columns that way
314 //Equals and GetHashCode return Equal objects to be the
317 //Get the first parent column hash
318 if (this.Columns.Length > 0)
319 hash1 ^= this.Columns[0].GetHashCode();
321 //get the rest of the parent column hashes if there any
322 for (i = 1; i < this.Columns.Length; i++)
324 hash1 ^= this.Columns[1].GetHashCode();
328 //Get the child column hash
329 if (this.RelatedColumns.Length > 0)
330 hash2 ^= this.Columns[0].GetHashCode();
332 for (i = 1; i < this.RelatedColumns.Length; i++)
334 hash2 ^= this.RelatedColumns[1].GetHashCode();
337 //combine the two hashes
338 return hash1 ^ hash2;
341 internal override void AddToConstraintCollectionSetup(
342 ConstraintCollection collection)
345 //run Ctor rules again
346 _validateColumns(_parentColumns, _childColumns);
348 //we must have a unique constraint on the parent
349 _ensureUniqueConstraintExists(collection, _parentColumns);
351 //Make sure we can create this thing
352 AssertConstraint(); //TODO:if this fails and we created a unique constraint
353 //we should probably roll it back
354 if (collection.Table != Table)
355 throw new InvalidConstraintException("This constraint cannot be added since ForeignKey doesn't belong to table " + RelatedTable.TableName + ".");
360 internal override void RemoveFromConstraintCollectionCleanup(
361 ConstraintCollection collection)
363 return; //no rules yet
367 internal override void AssertConstraint()
370 //Constraint only works if both tables are part of the same dataset
373 if (Table == null || RelatedTable == null) return; //TODO: Do we want this
375 if (Table.DataSet == null || RelatedTable.DataSet == null) return; //
378 //check for orphaned children
384 internal override void AssertConstraint(DataRow row)
386 //Implement: this should be used to validate ForeignKeys constraints
387 //when modifiying the DataRow values of a DataTable.
390 #endregion // Methods