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 uc = UniqueConstraint.GetUniqueConstraintForColumnSet(collection, parentColumns);
200 if (null == uc) uc = new UniqueConstraint(parentColumns, false); //could throw
203 _parentUniqueConstraint = uc;
204 parentColumns [0].Table.Constraints.Add (uc);
205 //if this unique constraint is attempted to be removed before us
206 //we can fail the validation
207 collection.ValidateRemoveConstraint += new DelegateValidateRemoveConstraint(
208 _validateRemoveParentConstraint);
216 [DataCategory ("Data")]
217 [DataSysDescription ("For accept and reject changes, indicates what kind of cascading should take place across this relation.")]
218 [DefaultValue (AcceptRejectRule.None)]
219 public virtual AcceptRejectRule AcceptRejectRule {
220 get { return _acceptRejectRule; }
221 set { _acceptRejectRule = value; }
224 [DataCategory ("Data")]
225 [DataSysDescription ("Indicates the child columns of this constraint.")]
227 public virtual DataColumn[] Columns {
228 get { return _childColumns; }
231 [DataCategory ("Data")]
232 [DataSysDescription ("For deletions, indicates what kind of cascading should take place across this relation.")]
233 [DefaultValue (Rule.Cascade)]
234 public virtual Rule DeleteRule {
235 get { return _deleteRule; }
236 set { _deleteRule = value; }
239 [DataCategory ("Data")]
240 [DataSysDescription ("For updates, indicates what kind of cascading should take place across this relation.")]
241 [DefaultValue (Rule.Cascade)]
242 public virtual Rule UpdateRule {
243 get { return _updateRule; }
244 set { _updateRule = value; }
247 [DataCategory ("Data")]
248 [DataSysDescription ("Indicates the parent columns of this constraint.")]
250 public virtual DataColumn[] RelatedColumns {
251 get { return _parentColumns; }
254 [DataCategory ("Data")]
255 [DataSysDescription ("Indicates the child table of this constraint.")]
257 public virtual DataTable RelatedTable {
259 if (_parentColumns != null)
260 if (_parentColumns.Length > 0)
261 return _parentColumns[0].Table;
267 [DataCategory ("Data")]
268 [DataSysDescription ("Indicates the table of this constraint.")]
270 public override DataTable Table {
272 if (_childColumns != null)
273 if (_childColumns.Length > 0)
274 return _childColumns[0].Table;
280 #endregion // Properties
284 public override bool Equals(object key)
286 ForeignKeyConstraint fkc = key as ForeignKeyConstraint;
287 if (null == fkc) return false;
289 //if the fk constrains the same columns then they are equal
290 if (! DataColumn.AreColumnSetsTheSame( this.RelatedColumns, fkc.RelatedColumns))
292 if (! DataColumn.AreColumnSetsTheSame( this.Columns, fkc.Columns) )
298 public override int GetHashCode()
300 //initialize hash1 and hash2 with default hashes
301 //any two DIFFERENT numbers will do here
302 int hash1 = 32, hash2 = 88;
305 //derive the hash code from the columns that way
306 //Equals and GetHashCode return Equal objects to be the
309 //Get the first parent column hash
310 if (this.Columns.Length > 0)
311 hash1 ^= this.Columns[0].GetHashCode();
313 //get the rest of the parent column hashes if there any
314 for (i = 1; i < this.Columns.Length; i++)
316 hash1 ^= this.Columns[1].GetHashCode();
320 //Get the child column hash
321 if (this.RelatedColumns.Length > 0)
322 hash2 ^= this.Columns[0].GetHashCode();
324 for (i = 1; i < this.RelatedColumns.Length; i++)
326 hash2 ^= this.RelatedColumns[1].GetHashCode();
329 //combine the two hashes
330 return hash1 ^ hash2;
333 internal override void AddToConstraintCollectionSetup(
334 ConstraintCollection collection)
337 //run Ctor rules again
338 _validateColumns(_parentColumns, _childColumns);
340 //we must have a unique constraint on the parent
341 _ensureUniqueConstraintExists(collection, _parentColumns);
343 //Make sure we can create this thing
344 AssertConstraint(); //TODO:if this fails and we created a unique constraint
345 //we should probably roll it back
351 internal override void RemoveFromConstraintCollectionCleanup(
352 ConstraintCollection collection)
354 return; //no rules yet
358 internal override void AssertConstraint()
360 //Constraint only works if both tables are part of the same dataset
363 if (Table == null || RelatedTable == null) return; //TODO: Do we want this
365 if (Table.DataSet == null || RelatedTable.DataSet == null) return; //
368 //check for orphaned children
374 internal override void AssertConstraint(DataRow row)
376 //Implement: this should be used to validate ForeignKeys constraints
377 //when modifiying the DataRow values of a DataTable.
380 #endregion // Methods