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 DataColumn [] _childColumnsExtended;
27 private Rule _deleteRule = Rule.Cascade;
28 private Rule _updateRule = Rule.Cascade;
29 private AcceptRejectRule _acceptRejectRule = AcceptRejectRule.None;
33 public ForeignKeyConstraint(DataColumn parentColumn, DataColumn childColumn)
35 if (null == parentColumn || null == childColumn) {
36 throw new ArgumentNullException("Neither parentColumn or" +
37 " childColumn can be null.");
40 _foreignKeyConstraint(null, new DataColumn[] {parentColumn},
41 new DataColumn[] {childColumn});
44 public ForeignKeyConstraint(DataColumn[] parentColumns, DataColumn[] childColumns)
46 _foreignKeyConstraint(null, parentColumns, childColumns);
49 public ForeignKeyConstraint(string constraintName, DataColumn parentColumn, DataColumn childColumn)
51 if (null == parentColumn || null == childColumn) {
52 throw new ArgumentNullException("Neither parentColumn or" +
53 " childColumn can be null.");
56 _foreignKeyConstraint(constraintName, new DataColumn[] {parentColumn},
57 new DataColumn[] {childColumn});
60 public ForeignKeyConstraint(string constraintName, DataColumn[] parentColumns, DataColumn[] childColumns)
62 _foreignKeyConstraint(constraintName, parentColumns, childColumns);
68 public ForeignKeyConstraint(string constraintName, string parentTableName, string[] parentColumnNames, string[] childColumnNames, AcceptRejectRule acceptRejectRule, Rule deleteRule, Rule updateRule)
74 public ForeignKeyConstraint (string constraintName, string parentTableName, string parentTableNamespace, string[] parentColumnNames, string[] childColumnNames, AcceptRejectRule acceptRejectRule, Rule deleteRule, Rule updateRule)
76 throw new NotImplementedException ();
80 private void _foreignKeyConstraint(string constraintName, DataColumn[] parentColumns,
81 DataColumn[] childColumns)
85 _validateColumns(parentColumns, childColumns);
88 base.ConstraintName = constraintName;
90 //Keep reference to columns
91 _parentColumns = parentColumns;
92 _childColumns = childColumns;
95 #endregion // Constructors
99 private void _validateColumns(DataColumn[] parentColumns, DataColumn[] childColumns)
102 if (null == parentColumns || null == childColumns)
103 throw new ArgumentNullException();
105 //at least one element in each array
106 if (parentColumns.Length < 1 || childColumns.Length < 1)
107 throw new ArgumentException("Neither ParentColumns or ChildColumns can't be" +
111 if (parentColumns.Length != childColumns.Length)
112 throw new ArgumentException("Parent columns and child columns must be the same length.");
115 DataTable ptable = parentColumns[0].Table;
116 DataTable ctable = childColumns[0].Table;
118 for (int i = 0; i < parentColumns.Length; i++)
120 DataColumn pc = parentColumns[i];
121 DataColumn cc = childColumns[i];
123 if (null == pc.Table)
124 throw new ArgumentException("All columns must belong to a table." +
125 " ColumnName: " + pc.ColumnName + " does not belong to a table.");
127 //All columns must belong to the same table
128 if (ptable != pc.Table)
129 throw new InvalidConstraintException("Parent columns must all belong to the same table.");
132 if (null == cc.Table)
133 throw new ArgumentException("All columns must belong to a table." +
134 " ColumnName: " + pc.ColumnName + " does not belong to a table.");
136 //All columns must belong to the same table.
137 if (ctable != cc.Table)
138 throw new InvalidConstraintException("Child columns must all belong to the same table.");
140 //Can't be the same column
142 throw new InvalidOperationException("Parent and child columns can't be the same column.");
144 if (! pc.DataType.Equals(cc.DataType))
146 //LAMESPEC: spec says throw InvalidConstraintException
147 // implementation throws InvalidOperationException
148 throw new ArgumentException("Parent column is not type compatible with it's child"
154 //Same dataset. If both are null it's ok
155 if (ptable.DataSet != ctable.DataSet)
157 //LAMESPEC: spec says InvalidConstraintExceptoin
158 // impl does InvalidOperationException
159 throw new InvalidOperationException("Parent column and child column must belong to" +
160 " tables that belong to the same DataSet.");
167 private void _validateRemoveParentConstraint(ConstraintCollection sender,
168 Constraint constraint, ref bool cancel, ref string failReason)
170 //if we hold a reference to the parent then cancel it
171 if (constraint == _parentUniqueConstraint)
174 failReason = "Cannot remove UniqueConstraint because the"
175 + " ForeignKeyConstraint " + this.ConstraintName + " exists.";
179 //Checks to see if a related unique constraint exists
180 //if it doesn't then a unique constraint is created.
181 //if a unique constraint can't be created an exception will be thrown
182 private void _ensureUniqueConstraintExists(ConstraintCollection collection,
183 DataColumn [] parentColumns)
186 if (null == parentColumns) throw new ArgumentNullException(
187 "ParentColumns can't be null");
189 UniqueConstraint uc = null;
191 //see if unique constraint already exists
192 //if not create unique constraint
193 if(parentColumns[0] != null)
194 uc = UniqueConstraint.GetUniqueConstraintForColumnSet(parentColumns[0].Table.Constraints, parentColumns);
197 uc = new UniqueConstraint(parentColumns, false); //could throw
198 parentColumns [0].Table.Constraints.Add (uc);
202 _parentUniqueConstraint = uc;
203 //parentColumns [0].Table.Constraints.Add (uc);
204 //if this unique constraint is attempted to be removed before us
205 //we can fail the validation
206 //collection.ValidateRemoveConstraint += new DelegateValidateRemoveConstraint(
207 // _validateRemoveParentConstraint);
209 parentColumns [0].Table.Constraints.ValidateRemoveConstraint += new DelegateValidateRemoveConstraint(
210 _validateRemoveParentConstraint);
219 [DataCategory ("Data")]
220 [DataSysDescription ("For accept and reject changes, indicates what kind of cascading should take place across this relation.")]
221 [DefaultValue (AcceptRejectRule.None)]
222 public virtual AcceptRejectRule AcceptRejectRule {
223 get { return _acceptRejectRule; }
224 set { _acceptRejectRule = value; }
227 [DataCategory ("Data")]
228 [DataSysDescription ("Indicates the child columns of this constraint.")]
230 public virtual DataColumn[] Columns {
231 get { return _childColumns; }
234 [DataCategory ("Data")]
235 [DataSysDescription ("For deletions, indicates what kind of cascading should take place across this relation.")]
236 [DefaultValue (Rule.Cascade)]
237 public virtual Rule DeleteRule {
238 get { return _deleteRule; }
239 set { _deleteRule = value; }
242 [DataCategory ("Data")]
243 [DataSysDescription ("For updates, indicates what kind of cascading should take place across this relation.")]
244 [DefaultValue (Rule.Cascade)]
245 public virtual Rule UpdateRule {
246 get { return _updateRule; }
247 set { _updateRule = value; }
250 [DataCategory ("Data")]
251 [DataSysDescription ("Indicates the parent columns of this constraint.")]
253 public virtual DataColumn[] RelatedColumns {
254 get { return _parentColumns; }
257 [DataCategory ("Data")]
258 [DataSysDescription ("Indicates the child table of this constraint.")]
260 public virtual DataTable RelatedTable {
262 if (_parentColumns != null)
263 if (_parentColumns.Length > 0)
264 return _parentColumns[0].Table;
270 [DataCategory ("Data")]
271 [DataSysDescription ("Indicates the table of this constraint.")]
273 public override DataTable Table {
275 if (_childColumns != null)
276 if (_childColumns.Length > 0)
277 return _childColumns[0].Table;
283 #endregion // Properties
287 public override bool Equals(object key)
289 ForeignKeyConstraint fkc = key as ForeignKeyConstraint;
290 if (null == fkc) return false;
292 //if the fk constrains the same columns then they are equal
293 if (! DataColumn.AreColumnSetsTheSame( this.RelatedColumns, fkc.RelatedColumns))
295 if (! DataColumn.AreColumnSetsTheSame( this.Columns, fkc.Columns) )
301 public override int GetHashCode()
303 //initialize hash1 and hash2 with default hashes
304 //any two DIFFERENT numbers will do here
305 int hash1 = 32, hash2 = 88;
308 //derive the hash code from the columns that way
309 //Equals and GetHashCode return Equal objects to be the
312 //Get the first parent column hash
313 if (this.Columns.Length > 0)
314 hash1 ^= this.Columns[0].GetHashCode();
316 //get the rest of the parent column hashes if there any
317 for (i = 1; i < this.Columns.Length; i++)
319 hash1 ^= this.Columns[1].GetHashCode();
323 //Get the child column hash
324 if (this.RelatedColumns.Length > 0)
325 hash2 ^= this.Columns[0].GetHashCode();
327 for (i = 1; i < this.RelatedColumns.Length; i++)
329 hash2 ^= this.RelatedColumns[1].GetHashCode();
332 //combine the two hashes
333 return hash1 ^ hash2;
336 internal override void AddToConstraintCollectionSetup(
337 ConstraintCollection collection)
340 if (collection.Table != Table)
341 throw new InvalidConstraintException("This constraint cannot be added since ForeignKey doesn't belong to table " + RelatedTable.TableName + ".");
343 //run Ctor rules again
344 _validateColumns(_parentColumns, _childColumns);
346 //we must have a unique constraint on the parent
347 _ensureUniqueConstraintExists(collection, _parentColumns);
349 //Make sure we can create this thing
351 //TODO:if this fails and we created a unique constraint
352 //we should probably roll it back
353 // and remove index form Table
358 internal override void RemoveFromConstraintCollectionCleanup(
359 ConstraintCollection collection)
361 // this is not referncing the index anymore
362 Index index = this.Index;
364 // drop the extended index on child table
365 this.Table.DropIndex(index);
369 internal override void AssertConstraint()
371 //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 foreach (DataRow row in Table.Rows) {
379 // first we check if all values in _childColumns place are nulls.
381 if (row.IsNullColumns(_childColumns))
384 // check whenever there is (at least one) parent row in RelatedTable
385 if(!RelatedTable.RowsExist(_parentColumns,_childColumns,row)) {
386 // if no parent row exists - constraint is violated
388 for (int i = 0; i < _childColumns.Length; i++) {
389 values += row[_childColumns[0]].ToString();
390 if (i != _childColumns.Length - 1)
393 throw new InvalidConstraintException("ForeignKeyConstraint " + ConstraintName + " requires the child key values (" + values + ") to exist in the parent table.");
397 catch (InvalidConstraintException){
398 throw new ArgumentException("This constraint cannot be enabled as not all values have corresponding parent values.");
401 // Create or rebuild index on Table
402 // We create index for FK only if PK on the Table exists
403 // and extended index is created based on appending PK columns to the
405 if((this.Table.PrimaryKey != null) && (this.Table.PrimaryKey.Length > 0)) {
406 // rebuild extended columns
407 RebuildExtendedColumns();
409 if(this.Index == null) {
410 this.Index = this.Table.CreateIndex(this.ConstraintName + "_index",_childColumnsExtended,false);
413 if(UniqueConstraint.GetUniqueConstraintForColumnSet(this.Table.Constraints,this.Index.Columns) == null) {
414 this.Table.InitializeIndex(this.Index);
420 internal override void AssertConstraint(DataRow row)
422 // first we check if all values in _childColumns place are nulls.
424 if (row.IsNullColumns(_childColumns))
427 // check whenever there is (at least one) parent row in RelatedTable
428 if(!RelatedTable.RowsExist(_parentColumns,_childColumns,row)) {
429 // if no parent row exists - constraint is violated
431 for (int i = 0; i < _childColumns.Length; i++) {
432 values += row[_childColumns[0]].ToString();
433 if (i != _childColumns.Length - 1)
436 throw new InvalidConstraintException("ForeignKeyConstraint " + ConstraintName + " requires the child key values (" + values + ") to exist in the parent table.");
439 // if row can be inserted - add it to constraint index
440 // if there is no UniqueConstraint on the same columns
441 if(this.Index != null && UniqueConstraint.GetUniqueConstraintForColumnSet(this.Table.Constraints,this.Index.Columns) == null) {
446 internal override void RollbackAssert (DataRow row)
448 // first we check if all values in _childColumns place are DBNull.
450 if (row.IsNullColumns(_childColumns))
453 // if there is no UniqueConstraint on the same columns
454 // we should rollback row from index
455 if(this.Index != null && UniqueConstraint.GetUniqueConstraintForColumnSet(this.Table.Constraints,this.Index.Columns) == null) {
460 internal void RebuildExtendedColumns()
462 DataColumn[] pkColumns = this.Table.PrimaryKey;
463 if((pkColumns != null) && (pkColumns.Length > 0)) {
464 _childColumnsExtended = new DataColumn[_childColumns.Length + pkColumns.Length];
465 Array.Copy(_childColumns,0,_childColumnsExtended,0,_childColumns.Length);
466 Array.Copy(pkColumns,0,_childColumnsExtended,_childColumns.Length,pkColumns.Length);
469 throw new InvalidOperationException("Can not extend columns for foreign key");
473 #endregion // Methods