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 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 using System
.Collections
;
37 using System
.ComponentModel
;
38 using System
.Runtime
.InteropServices
;
40 namespace System
.Data
{
42 [DefaultProperty ("ConstraintName")]
44 public class ForeignKeyConstraint
: Constraint
46 private UniqueConstraint _parentUniqueConstraint
;
47 private DataColumn
[] _parentColumns
;
48 private DataColumn
[] _childColumns
;
49 private DataColumn
[] _childColumnsExtended
;
50 private Rule _deleteRule
= Rule
.Cascade
;
51 private Rule _updateRule
= Rule
.Cascade
;
52 private AcceptRejectRule _acceptRejectRule
= AcceptRejectRule
.None
;
53 private string _parentTableName
;
54 private string _childTableName
;
55 private string [] _parentColumnNames
;
56 private string [] _childColumnNames
;
57 private bool _dataColsNotValidated
= false;
61 public ForeignKeyConstraint(DataColumn parentColumn
, DataColumn childColumn
)
63 if (null == parentColumn
|| null == childColumn
) {
64 throw new NullReferenceException("Neither parentColumn or" +
65 " childColumn can be null.");
67 _foreignKeyConstraint(null, new DataColumn
[] {parentColumn}
,
68 new DataColumn
[] {childColumn}
);
71 public ForeignKeyConstraint(DataColumn
[] parentColumns
, DataColumn
[] childColumns
)
73 _foreignKeyConstraint(null, parentColumns
, childColumns
);
76 public ForeignKeyConstraint(string constraintName
, DataColumn parentColumn
, DataColumn childColumn
)
78 if (null == parentColumn
|| null == childColumn
) {
79 throw new NullReferenceException("Neither parentColumn or" +
80 " childColumn can be null.");
83 _foreignKeyConstraint(constraintName
, new DataColumn
[] {parentColumn}
,
84 new DataColumn
[] {childColumn}
);
87 public ForeignKeyConstraint(string constraintName
, DataColumn
[] parentColumns
, DataColumn
[] childColumns
)
89 _foreignKeyConstraint(constraintName
, parentColumns
, childColumns
);
94 public ForeignKeyConstraint(string constraintName
, string parentTableName
, string[] parentColumnNames
, string[] childColumnNames
, AcceptRejectRule acceptRejectRule
, Rule deleteRule
, Rule updateRule
)
96 _dataColsNotValidated
= true;
97 base.ConstraintName
= constraintName
;
99 // "parentTableName" is searched in the "DataSet" to which the "DataTable"
100 // from which AddRange() is called
101 // childTable is the "DataTable" which calls AddRange()
103 // Keep reference to parentTableName to resolve later
104 _parentTableName
= parentTableName
;
106 // Keep reference to parentColumnNames to resolve later
107 _parentColumnNames
= parentColumnNames
;
109 // Keep reference to childColumnNames to resolve later
110 _childColumnNames
= childColumnNames
;
112 _acceptRejectRule
= acceptRejectRule
;
113 _deleteRule
= deleteRule
;
114 _updateRule
= updateRule
;
118 internal void postAddRange (DataTable childTable
)
120 // LAMESPEC - Does not say that this is mandatory
121 // Check whether childTable belongs to a DataSet
122 if (childTable
.DataSet
== null)
123 throw new InvalidConstraintException ("ChildTable : " + childTable
.TableName
+ " does not belong to any DataSet");
124 DataSet dataSet
= childTable
.DataSet
;
125 _childTableName
= childTable
.TableName
;
126 // Search for the parentTable in the childTable's DataSet
127 if (!dataSet
.Tables
.Contains (_parentTableName
))
128 throw new InvalidConstraintException ("Table : " + _parentTableName
+ "does not exist in DataSet : " + dataSet
);
130 // Keep reference to parentTable
131 DataTable parentTable
= dataSet
.Tables
[_parentTableName
];
135 // LAMESPEC - Does not say which Exception is thrown
136 if (_parentColumnNames
.Length
< 0 || _childColumnNames
.Length
< 0)
137 throw new InvalidConstraintException ("Neither parent nor child columns can be zero length");
138 // LAMESPEC - Does not say which Exception is thrown
139 if (_parentColumnNames
.Length
!= _childColumnNames
.Length
)
140 throw new InvalidConstraintException ("Both parent and child columns must be of same length");
141 DataColumn
[]parentColumns
= new DataColumn
[_parentColumnNames
.Length
];
142 DataColumn
[]childColumns
= new DataColumn
[_childColumnNames
.Length
];
144 // Search for the parentColumns in parentTable
145 foreach (string parentCol
in _parentColumnNames
){
146 if (!parentTable
.Columns
.Contains (parentCol
))
147 throw new InvalidConstraintException ("Table : " + _parentTableName
+ "does not contain the column :" + parentCol
);
148 parentColumns
[i
++] = parentTable
. Columns
[parentCol
];
150 // Search for the childColumns in childTable
151 foreach (string childCol
in _childColumnNames
){
152 if (!childTable
.Columns
.Contains (childCol
))
153 throw new InvalidConstraintException ("Table : " + _childTableName
+ "does not contain the column : " + childCol
);
154 childColumns
[j
++] = childTable
.Columns
[childCol
];
156 _validateColumns (parentColumns
, childColumns
);
157 _parentColumns
= parentColumns
;
158 _childColumns
= childColumns
;
163 public ForeignKeyConstraint (string constraintName
, string parentTableName
, string parentTableNamespace
, string[] parentColumnNames
, string[] childColumnNames
, AcceptRejectRule acceptRejectRule
, Rule deleteRule
, Rule updateRule
)
165 throw new NotImplementedException ();
169 private void _foreignKeyConstraint(string constraintName
, DataColumn
[] parentColumns
,
170 DataColumn
[] childColumns
)
174 _validateColumns(parentColumns
, childColumns
);
176 //Set Constraint Name
177 base.ConstraintName
= constraintName
;
179 //Keep reference to columns
180 _parentColumns
= parentColumns
;
181 _childColumns
= childColumns
;
184 #endregion // Constructors
188 private void _validateColumns(DataColumn
[] parentColumns
, DataColumn
[] childColumns
)
191 if (null == parentColumns
|| null == childColumns
)
192 throw new ArgumentNullException();
194 //at least one element in each array
195 if (parentColumns
.Length
< 1 || childColumns
.Length
< 1)
196 throw new ArgumentException("Neither ParentColumns or ChildColumns can't be" +
200 if (parentColumns
.Length
!= childColumns
.Length
)
201 throw new ArgumentException("Parent columns and child columns must be the same length.");
204 DataTable ptable
= parentColumns
[0].Table
;
205 DataTable ctable
= childColumns
[0].Table
;
207 for (int i
= 0; i
< parentColumns
.Length
; i
++) {
208 DataColumn pc
= parentColumns
[i
];
209 DataColumn cc
= childColumns
[i
];
212 if (null == pc
.Table
)
213 throw new ArgumentException("All columns must belong to a table." +
214 " ColumnName: " + pc
.ColumnName
+ " does not belong to a table.");
216 //All columns must belong to the same table
217 if (ptable
!= pc
.Table
)
218 throw new InvalidConstraintException("Parent columns must all belong to the same table.");
221 if (null == cc
.Table
)
222 throw new ArgumentException("All columns must belong to a table." +
223 " ColumnName: " + pc
.ColumnName
+ " does not belong to a table.");
225 //All columns must belong to the same table.
226 if (ctable
!= cc
.Table
)
227 throw new InvalidConstraintException("Child columns must all belong to the same table.");
231 for (int i
= 0; i
< parentColumns
.Length
; i
++)
233 DataColumn pc
= parentColumns
[i
];
234 DataColumn cc
= childColumns
[i
];
236 //Can't be the same column
238 throw new InvalidOperationException("Parent and child columns can't be the same column.");
240 if (! pc
.DataType
.Equals(cc
.DataType
))
242 //LAMESPEC: spec says throw InvalidConstraintException
243 // implementation throws InvalidOperationException
244 throw new InvalidConstraintException("Parent column is not type compatible with it's child"
250 //Same dataset. If both are null it's ok
251 if (ptable
.DataSet
!= ctable
.DataSet
)
253 //LAMESPEC: spec says InvalidConstraintExceptoin
254 // impl does InvalidOperationException
255 throw new InvalidOperationException("Parent column and child column must belong to" +
256 " tables that belong to the same DataSet.");
263 private void _validateRemoveParentConstraint(ConstraintCollection sender
,
264 Constraint constraint
, ref bool cancel
, ref string failReason
)
267 //if we hold a reference to the parent then cancel it
268 if (constraint
== _parentUniqueConstraint
)
271 failReason
= "Cannot remove UniqueConstraint because the"
272 + " ForeignKeyConstraint " + this.ConstraintName
+ " exists.";
277 //Checks to see if a related unique constraint exists
278 //if it doesn't then a unique constraint is created.
279 //if a unique constraint can't be created an exception will be thrown
280 private void _ensureUniqueConstraintExists(ConstraintCollection collection
,
281 DataColumn
[] parentColumns
)
284 if (null == parentColumns
) throw new ArgumentNullException(
285 "ParentColumns can't be null");
287 UniqueConstraint uc
= null;
289 //see if unique constraint already exists
290 //if not create unique constraint
291 if(parentColumns
[0] != null)
292 uc
= UniqueConstraint
.GetUniqueConstraintForColumnSet(parentColumns
[0].Table
.Constraints
, parentColumns
);
295 uc
= new UniqueConstraint(parentColumns
, false); //could throw
296 parentColumns
[0].Table
.Constraints
.Add (uc
);
300 _parentUniqueConstraint
= uc
;
301 //parentColumns [0].Table.Constraints.Add (uc);
302 //if this unique constraint is attempted to be removed before us
303 //we can fail the validation
304 //collection.ValidateRemoveConstraint += new DelegateValidateRemoveConstraint(
305 // _validateRemoveParentConstraint);
307 parentColumns
[0].Table
.Constraints
.ValidateRemoveConstraint
+= new DelegateValidateRemoveConstraint(
308 _validateRemoveParentConstraint
);
317 [DataCategory ("Data")]
318 [DataSysDescription ("For accept and reject changes, indicates what kind of cascading should take place across this relation.")]
319 [DefaultValue (AcceptRejectRule
.None
)]
320 public virtual AcceptRejectRule AcceptRejectRule
{
321 get { return _acceptRejectRule; }
322 set { _acceptRejectRule = value; }
325 [DataCategory ("Data")]
326 [DataSysDescription ("Indicates the child columns of this constraint.")]
328 public virtual DataColumn
[] Columns
{
329 get { return _childColumns; }
332 [DataCategory ("Data")]
333 [DataSysDescription ("For deletions, indicates what kind of cascading should take place across this relation.")]
334 [DefaultValue (Rule
.Cascade
)]
335 public virtual Rule DeleteRule
{
336 get { return _deleteRule; }
337 set { _deleteRule = value; }
340 [DataCategory ("Data")]
341 [DataSysDescription ("For updates, indicates what kind of cascading should take place across this relation.")]
342 [DefaultValue (Rule
.Cascade
)]
343 public virtual Rule UpdateRule
{
344 get { return _updateRule; }
345 set { _updateRule = value; }
348 [DataCategory ("Data")]
349 [DataSysDescription ("Indicates the parent columns of this constraint.")]
351 public virtual DataColumn
[] RelatedColumns
{
352 get { return _parentColumns; }
355 [DataCategory ("Data")]
356 [DataSysDescription ("Indicates the child table of this constraint.")]
358 public virtual DataTable RelatedTable
{
360 if (_parentColumns
!= null)
361 if (_parentColumns
.Length
> 0)
362 return _parentColumns
[0].Table
;
364 throw new InvalidOperationException ("Property not accessible because 'Object reference not set to an instance of an object'");
368 [DataCategory ("Data")]
369 [DataSysDescription ("Indicates the table of this constraint.")]
371 public override DataTable Table
{
373 if (_childColumns
!= null)
374 if (_childColumns
.Length
> 0)
375 return _childColumns
[0].Table
;
377 throw new InvalidOperationException ("Property not accessible because 'Object reference not set to an instance of an object'");
381 internal bool DataColsNotValidated
{
382 get{ return (_dataColsNotValidated); }
386 #endregion // Properties
390 public override bool Equals(object key
)
392 ForeignKeyConstraint fkc
= key
as ForeignKeyConstraint
;
393 if (null == fkc
) return false;
395 //if the fk constrains the same columns then they are equal
396 if (! DataColumn
.AreColumnSetsTheSame( this.RelatedColumns
, fkc
.RelatedColumns
))
398 if (! DataColumn
.AreColumnSetsTheSame( this.Columns
, fkc
.Columns
) )
404 public override int GetHashCode()
406 //initialize hash1 and hash2 with default hashes
407 //any two DIFFERENT numbers will do here
408 int hash1
= 32, hash2
= 88;
411 //derive the hash code from the columns that way
412 //Equals and GetHashCode return Equal objects to be the
415 //Get the first parent column hash
416 if (this.Columns
.Length
> 0)
417 hash1 ^
= this.Columns
[0].GetHashCode();
419 //get the rest of the parent column hashes if there any
420 for (i
= 1; i
< this.Columns
.Length
; i
++)
422 hash1 ^
= this.Columns
[1].GetHashCode();
426 //Get the child column hash
427 if (this.RelatedColumns
.Length
> 0)
428 hash2 ^
= this.Columns
[0].GetHashCode();
430 for (i
= 1; i
< this.RelatedColumns
.Length
; i
++)
432 hash2 ^
= this.RelatedColumns
[1].GetHashCode();
435 //combine the two hashes
436 return hash1 ^ hash2
;
439 internal override void AddToConstraintCollectionSetup(
440 ConstraintCollection collection
)
443 if (collection
.Table
!= Table
)
444 throw new InvalidConstraintException("This constraint cannot be added since ForeignKey doesn't belong to table " + RelatedTable
.TableName
+ ".");
446 //run Ctor rules again
447 _validateColumns(_parentColumns
, _childColumns
);
449 //we must have a unique constraint on the parent
450 _ensureUniqueConstraintExists(collection
, _parentColumns
);
452 //Make sure we can create this thing
454 //TODO:if this fails and we created a unique constraint
455 //we should probably roll it back
456 // and remove index form Table
461 internal override void RemoveFromConstraintCollectionCleanup(
462 ConstraintCollection collection
)
464 // this is not referncing the index anymore
465 Index index
= this.Index
;
467 // drop the extended index on child table
468 this.Table
.DropIndex(index
);
472 internal override void AssertConstraint()
474 //Constraint only works if both tables are part of the same dataset
476 if (Table
== null || RelatedTable
== null) return; //TODO: Do we want this
478 if (Table
.DataSet
== null || RelatedTable
.DataSet
== null) return; //
481 foreach (DataRow row
in Table
.Rows
) {
482 // first we check if all values in _childColumns place are nulls.
484 if (row
.IsNullColumns(_childColumns
))
487 // check whenever there is (at least one) parent row in RelatedTable
488 if(!RelatedTable
.RowsExist(_parentColumns
,_childColumns
,row
)) {
489 // if no parent row exists - constraint is violated
491 for (int i
= 0; i
< _childColumns
.Length
; i
++) {
492 values
+= row
[_childColumns
[0]].ToString();
493 if (i
!= _childColumns
.Length
- 1)
496 throw new InvalidConstraintException("ForeignKeyConstraint " + ConstraintName
+ " requires the child key values (" + values
+ ") to exist in the parent table.");
500 catch (InvalidConstraintException
){
501 throw new ArgumentException("This constraint cannot be enabled as not all values have corresponding parent values.");
504 // Create or rebuild index on Table
505 // We create index for FK only if PK on the Table exists
506 // and extended index is created based on appending PK columns to the
508 if((this.Table
.PrimaryKey
!= null) && (this.Table
.PrimaryKey
.Length
> 0)) {
509 // rebuild extended columns
510 RebuildExtendedColumns();
512 if(this.Index
== null) {
513 this.Index
= this.Table
.CreateIndex(this.ConstraintName
+ "_index",_childColumnsExtended
,false);
516 if(UniqueConstraint
.GetUniqueConstraintForColumnSet(this.Table
.Constraints
,this.Index
.Columns
) == null) {
517 this.Table
.InitializeIndex(this.Index
);
523 internal override void AssertConstraint(DataRow row
)
525 // first we check if all values in _childColumns place are nulls.
527 if (row
.IsNullColumns(_childColumns
))
530 // check whenever there is (at least one) parent row in RelatedTable
531 if(!RelatedTable
.RowsExist(_parentColumns
,_childColumns
,row
)) {
532 // if no parent row exists - constraint is violated
534 for (int i
= 0; i
< _childColumns
.Length
; i
++) {
535 values
+= row
[_childColumns
[0]].ToString();
536 if (i
!= _childColumns
.Length
- 1)
539 throw new InvalidConstraintException("ForeignKeyConstraint " + ConstraintName
+ " requires the child key values (" + values
+ ") to exist in the parent table.");
542 // if row can be inserted - add it to constraint index
543 // if there is no UniqueConstraint on the same columns
544 if(this.Index
!= null && UniqueConstraint
.GetUniqueConstraintForColumnSet(this.Table
.Constraints
,this.Index
.Columns
) == null) {
549 internal override void RollbackAssert (DataRow row
)
551 // first we check if all values in _childColumns place are DBNull.
553 if (row
.IsNullColumns(_childColumns
))
556 // if there is no UniqueConstraint on the same columns
557 // we should rollback row from index
558 if(this.Index
!= null && UniqueConstraint
.GetUniqueConstraintForColumnSet(this.Table
.Constraints
,this.Index
.Columns
) == null) {
563 internal void RebuildExtendedColumns()
565 DataColumn
[] pkColumns
= this.Table
.PrimaryKey
;
566 if((pkColumns
!= null) && (pkColumns
.Length
> 0)) {
567 _childColumnsExtended
= new DataColumn
[_childColumns
.Length
+ pkColumns
.Length
];
568 Array
.Copy(_childColumns
,0,_childColumnsExtended
,0,_childColumns
.Length
);
569 Array
.Copy(pkColumns
,0,_childColumnsExtended
,_childColumns
.Length
,pkColumns
.Length
);
572 throw new InvalidOperationException("Can not extend columns for foreign key");
576 #endregion // Methods