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;
30 private string _parentTableName;
31 private string _childTableName;
32 private string [] _parentColumnNames;
33 private string [] _childColumnNames;
34 private bool _dataColsNotValidated = false;
38 public ForeignKeyConstraint(DataColumn parentColumn, DataColumn childColumn)
40 if (null == parentColumn || null == childColumn) {
41 throw new ArgumentNullException("Neither parentColumn or" +
42 " childColumn can be null.");
44 _foreignKeyConstraint(null, new DataColumn[] {parentColumn},
45 new DataColumn[] {childColumn});
48 public ForeignKeyConstraint(DataColumn[] parentColumns, DataColumn[] childColumns)
50 _foreignKeyConstraint(null, parentColumns, childColumns);
53 public ForeignKeyConstraint(string constraintName, DataColumn parentColumn, DataColumn childColumn)
55 if (null == parentColumn || null == childColumn) {
56 throw new ArgumentNullException("Neither parentColumn or" +
57 " childColumn can be null.");
60 _foreignKeyConstraint(constraintName, new DataColumn[] {parentColumn},
61 new DataColumn[] {childColumn});
64 public ForeignKeyConstraint(string constraintName, DataColumn[] parentColumns, DataColumn[] childColumns)
66 _foreignKeyConstraint(constraintName, parentColumns, childColumns);
71 public ForeignKeyConstraint(string constraintName, string parentTableName, string[] parentColumnNames, string[] childColumnNames, AcceptRejectRule acceptRejectRule, Rule deleteRule, Rule updateRule)
73 _dataColsNotValidated = true;
74 base.ConstraintName = constraintName;
76 // "parentTableName" is searched in the "DataSet" to which the "DataTable"
77 // from which AddRange() is called
78 // childTable is the "DataTable" which calls AddRange()
80 // Keep reference to parentTableName to resolve later
81 _parentTableName = parentTableName;
83 // Keep reference to parentColumnNames to resolve later
84 _parentColumnNames = parentColumnNames;
86 // Keep reference to childColumnNames to resolve later
87 _childColumnNames = childColumnNames;
89 _acceptRejectRule = acceptRejectRule;
90 _deleteRule = deleteRule;
91 _updateRule = updateRule;
95 internal void postAddRange (DataTable childTable)
97 // LAMESPEC - Does not say that this is mandatory
98 // Check whether childTable belongs to a DataSet
99 if (childTable.DataSet == null)
100 throw new InvalidConstraintException ("ChildTable : " + childTable.TableName + " does not belong to any DataSet");
101 DataSet dataSet = childTable.DataSet;
102 _childTableName = childTable.TableName;
103 // Search for the parentTable in the childTable's DataSet
104 if (!dataSet.Tables.Contains (_parentTableName))
105 throw new InvalidConstraintException ("Table : " + _parentTableName + "does not exist in DataSet : " + dataSet);
107 // Keep reference to parentTable
108 DataTable parentTable = dataSet.Tables [_parentTableName];
112 // LAMESPEC - Does not say which Exception is thrown
113 if (_parentColumnNames.Length < 0 || _childColumnNames.Length < 0)
114 throw new InvalidConstraintException ("Neither parent nor child columns can be zero length");
115 // LAMESPEC - Does not say which Exception is thrown
116 if (_parentColumnNames.Length != _childColumnNames.Length)
117 throw new InvalidConstraintException ("Both parent and child columns must be of same length");
118 DataColumn []parentColumns = new DataColumn [_parentColumnNames.Length];
119 DataColumn []childColumns = new DataColumn [_childColumnNames.Length];
121 // Search for the parentColumns in parentTable
122 foreach (string parentCol in _parentColumnNames){
123 if (!parentTable.Columns.Contains (parentCol))
124 throw new InvalidConstraintException ("Table : " + _parentTableName + "does not contain the column :" + parentCol);
125 parentColumns [i++] = parentTable. Columns [parentCol];
127 // Search for the childColumns in childTable
128 foreach (string childCol in _childColumnNames){
129 if (!childTable.Columns.Contains (childCol))
130 throw new InvalidConstraintException ("Table : " + _childTableName + "does not contain the column : " + childCol);
131 childColumns [j++] = childTable.Columns [childCol];
133 _validateColumns (parentColumns, childColumns);
134 _parentColumns = parentColumns;
135 _childColumns = childColumns;
140 public ForeignKeyConstraint (string constraintName, string parentTableName, string parentTableNamespace, string[] parentColumnNames, string[] childColumnNames, AcceptRejectRule acceptRejectRule, Rule deleteRule, Rule updateRule)
142 throw new NotImplementedException ();
146 private void _foreignKeyConstraint(string constraintName, DataColumn[] parentColumns,
147 DataColumn[] childColumns)
151 _validateColumns(parentColumns, childColumns);
153 //Set Constraint Name
154 base.ConstraintName = constraintName;
156 //Keep reference to columns
157 _parentColumns = parentColumns;
158 _childColumns = childColumns;
161 #endregion // Constructors
165 private void _validateColumns(DataColumn[] parentColumns, DataColumn[] childColumns)
168 if (null == parentColumns || null == childColumns)
169 throw new ArgumentNullException();
171 //at least one element in each array
172 if (parentColumns.Length < 1 || childColumns.Length < 1)
173 throw new ArgumentException("Neither ParentColumns or ChildColumns can't be" +
177 if (parentColumns.Length != childColumns.Length)
178 throw new ArgumentException("Parent columns and child columns must be the same length.");
181 DataTable ptable = parentColumns[0].Table;
182 DataTable ctable = childColumns[0].Table;
184 for (int i = 0; i < parentColumns.Length; i++)
186 DataColumn pc = parentColumns[i];
187 DataColumn cc = childColumns[i];
189 if (null == pc.Table)
190 throw new ArgumentException("All columns must belong to a table." +
191 " ColumnName: " + pc.ColumnName + " does not belong to a table.");
193 //All columns must belong to the same table
194 if (ptable != pc.Table)
195 throw new InvalidConstraintException("Parent columns must all belong to the same table.");
198 if (null == cc.Table)
199 throw new ArgumentException("All columns must belong to a table." +
200 " ColumnName: " + pc.ColumnName + " does not belong to a table.");
202 //All columns must belong to the same table.
203 if (ctable != cc.Table)
204 throw new InvalidConstraintException("Child columns must all belong to the same table.");
206 //Can't be the same column
208 throw new InvalidOperationException("Parent and child columns can't be the same column.");
210 if (! pc.DataType.Equals(cc.DataType))
212 //LAMESPEC: spec says throw InvalidConstraintException
213 // implementation throws InvalidOperationException
214 throw new ArgumentException("Parent column is not type compatible with it's child"
220 //Same dataset. If both are null it's ok
221 if (ptable.DataSet != ctable.DataSet)
223 //LAMESPEC: spec says InvalidConstraintExceptoin
224 // impl does InvalidOperationException
225 throw new InvalidOperationException("Parent column and child column must belong to" +
226 " tables that belong to the same DataSet.");
233 private void _validateRemoveParentConstraint(ConstraintCollection sender,
234 Constraint constraint, ref bool cancel, ref string failReason)
236 //if we hold a reference to the parent then cancel it
237 if (constraint == _parentUniqueConstraint)
240 failReason = "Cannot remove UniqueConstraint because the"
241 + " ForeignKeyConstraint " + this.ConstraintName + " exists.";
245 //Checks to see if a related unique constraint exists
246 //if it doesn't then a unique constraint is created.
247 //if a unique constraint can't be created an exception will be thrown
248 private void _ensureUniqueConstraintExists(ConstraintCollection collection,
249 DataColumn [] parentColumns)
252 if (null == parentColumns) throw new ArgumentNullException(
253 "ParentColumns can't be null");
255 UniqueConstraint uc = null;
257 //see if unique constraint already exists
258 //if not create unique constraint
259 if(parentColumns[0] != null)
260 uc = UniqueConstraint.GetUniqueConstraintForColumnSet(parentColumns[0].Table.Constraints, parentColumns);
263 uc = new UniqueConstraint(parentColumns, false); //could throw
264 parentColumns [0].Table.Constraints.Add (uc);
268 _parentUniqueConstraint = uc;
269 //parentColumns [0].Table.Constraints.Add (uc);
270 //if this unique constraint is attempted to be removed before us
271 //we can fail the validation
272 //collection.ValidateRemoveConstraint += new DelegateValidateRemoveConstraint(
273 // _validateRemoveParentConstraint);
275 parentColumns [0].Table.Constraints.ValidateRemoveConstraint += new DelegateValidateRemoveConstraint(
276 _validateRemoveParentConstraint);
285 [DataCategory ("Data")]
286 [DataSysDescription ("For accept and reject changes, indicates what kind of cascading should take place across this relation.")]
287 [DefaultValue (AcceptRejectRule.None)]
288 public virtual AcceptRejectRule AcceptRejectRule {
289 get { return _acceptRejectRule; }
290 set { _acceptRejectRule = value; }
293 [DataCategory ("Data")]
294 [DataSysDescription ("Indicates the child columns of this constraint.")]
296 public virtual DataColumn[] Columns {
297 get { return _childColumns; }
300 [DataCategory ("Data")]
301 [DataSysDescription ("For deletions, indicates what kind of cascading should take place across this relation.")]
302 [DefaultValue (Rule.Cascade)]
303 public virtual Rule DeleteRule {
304 get { return _deleteRule; }
305 set { _deleteRule = value; }
308 [DataCategory ("Data")]
309 [DataSysDescription ("For updates, indicates what kind of cascading should take place across this relation.")]
310 [DefaultValue (Rule.Cascade)]
311 public virtual Rule UpdateRule {
312 get { return _updateRule; }
313 set { _updateRule = value; }
316 [DataCategory ("Data")]
317 [DataSysDescription ("Indicates the parent columns of this constraint.")]
319 public virtual DataColumn[] RelatedColumns {
320 get { return _parentColumns; }
323 [DataCategory ("Data")]
324 [DataSysDescription ("Indicates the child table of this constraint.")]
326 public virtual DataTable RelatedTable {
328 if (_parentColumns != null)
329 if (_parentColumns.Length > 0)
330 return _parentColumns[0].Table;
336 [DataCategory ("Data")]
337 [DataSysDescription ("Indicates the table of this constraint.")]
339 public override DataTable Table {
341 if (_childColumns != null)
342 if (_childColumns.Length > 0)
343 return _childColumns[0].Table;
349 internal bool DataColsNotValidated{
350 get{ return (_dataColsNotValidated); }
354 #endregion // Properties
358 public override bool Equals(object key)
360 ForeignKeyConstraint fkc = key as ForeignKeyConstraint;
361 if (null == fkc) return false;
363 //if the fk constrains the same columns then they are equal
364 if (! DataColumn.AreColumnSetsTheSame( this.RelatedColumns, fkc.RelatedColumns))
366 if (! DataColumn.AreColumnSetsTheSame( this.Columns, fkc.Columns) )
372 public override int GetHashCode()
374 //initialize hash1 and hash2 with default hashes
375 //any two DIFFERENT numbers will do here
376 int hash1 = 32, hash2 = 88;
379 //derive the hash code from the columns that way
380 //Equals and GetHashCode return Equal objects to be the
383 //Get the first parent column hash
384 if (this.Columns.Length > 0)
385 hash1 ^= this.Columns[0].GetHashCode();
387 //get the rest of the parent column hashes if there any
388 for (i = 1; i < this.Columns.Length; i++)
390 hash1 ^= this.Columns[1].GetHashCode();
394 //Get the child column hash
395 if (this.RelatedColumns.Length > 0)
396 hash2 ^= this.Columns[0].GetHashCode();
398 for (i = 1; i < this.RelatedColumns.Length; i++)
400 hash2 ^= this.RelatedColumns[1].GetHashCode();
403 //combine the two hashes
404 return hash1 ^ hash2;
407 internal override void AddToConstraintCollectionSetup(
408 ConstraintCollection collection)
411 if (collection.Table != Table)
412 throw new InvalidConstraintException("This constraint cannot be added since ForeignKey doesn't belong to table " + RelatedTable.TableName + ".");
414 //run Ctor rules again
415 _validateColumns(_parentColumns, _childColumns);
417 //we must have a unique constraint on the parent
418 _ensureUniqueConstraintExists(collection, _parentColumns);
420 //Make sure we can create this thing
422 //TODO:if this fails and we created a unique constraint
423 //we should probably roll it back
424 // and remove index form Table
429 internal override void RemoveFromConstraintCollectionCleanup(
430 ConstraintCollection collection)
432 // this is not referncing the index anymore
433 Index index = this.Index;
435 // drop the extended index on child table
436 this.Table.DropIndex(index);
440 internal override void AssertConstraint()
442 //Constraint only works if both tables are part of the same dataset
444 if (Table == null || RelatedTable == null) return; //TODO: Do we want this
446 if (Table.DataSet == null || RelatedTable.DataSet == null) return; //
449 foreach (DataRow row in Table.Rows) {
450 // first we check if all values in _childColumns place are nulls.
452 if (row.IsNullColumns(_childColumns))
455 // check whenever there is (at least one) parent row in RelatedTable
456 if(!RelatedTable.RowsExist(_parentColumns,_childColumns,row)) {
457 // if no parent row exists - constraint is violated
459 for (int i = 0; i < _childColumns.Length; i++) {
460 values += row[_childColumns[0]].ToString();
461 if (i != _childColumns.Length - 1)
464 throw new InvalidConstraintException("ForeignKeyConstraint " + ConstraintName + " requires the child key values (" + values + ") to exist in the parent table.");
468 catch (InvalidConstraintException){
469 throw new ArgumentException("This constraint cannot be enabled as not all values have corresponding parent values.");
472 // Create or rebuild index on Table
473 // We create index for FK only if PK on the Table exists
474 // and extended index is created based on appending PK columns to the
476 if((this.Table.PrimaryKey != null) && (this.Table.PrimaryKey.Length > 0)) {
477 // rebuild extended columns
478 RebuildExtendedColumns();
480 if(this.Index == null) {
481 this.Index = this.Table.CreateIndex(this.ConstraintName + "_index",_childColumnsExtended,false);
484 if(UniqueConstraint.GetUniqueConstraintForColumnSet(this.Table.Constraints,this.Index.Columns) == null) {
485 this.Table.InitializeIndex(this.Index);
491 internal override void AssertConstraint(DataRow row)
493 // first we check if all values in _childColumns place are nulls.
495 if (row.IsNullColumns(_childColumns))
498 // check whenever there is (at least one) parent row in RelatedTable
499 if(!RelatedTable.RowsExist(_parentColumns,_childColumns,row)) {
500 // if no parent row exists - constraint is violated
502 for (int i = 0; i < _childColumns.Length; i++) {
503 values += row[_childColumns[0]].ToString();
504 if (i != _childColumns.Length - 1)
507 throw new InvalidConstraintException("ForeignKeyConstraint " + ConstraintName + " requires the child key values (" + values + ") to exist in the parent table.");
510 // if row can be inserted - add it to constraint index
511 // if there is no UniqueConstraint on the same columns
512 if(this.Index != null && UniqueConstraint.GetUniqueConstraintForColumnSet(this.Table.Constraints,this.Index.Columns) == null) {
517 internal override void RollbackAssert (DataRow row)
519 // first we check if all values in _childColumns place are DBNull.
521 if (row.IsNullColumns(_childColumns))
524 // if there is no UniqueConstraint on the same columns
525 // we should rollback row from index
526 if(this.Index != null && UniqueConstraint.GetUniqueConstraintForColumnSet(this.Table.Constraints,this.Index.Columns) == null) {
531 internal void RebuildExtendedColumns()
533 DataColumn[] pkColumns = this.Table.PrimaryKey;
534 if((pkColumns != null) && (pkColumns.Length > 0)) {
535 _childColumnsExtended = new DataColumn[_childColumns.Length + pkColumns.Length];
536 Array.Copy(_childColumns,0,_childColumnsExtended,0,_childColumns.Length);
537 Array.Copy(pkColumns,0,_childColumnsExtended,_childColumns.Length,pkColumns.Length);
540 throw new InvalidOperationException("Can not extend columns for foreign key");
544 #endregion // Methods