1 //------------------------------------------------------------------------------
2 // <copyright file="ForeignKeyConstraint.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="true" primary="false">Microsoft</owner>
7 // <owner current="false" primary="false">Microsoft</owner>
8 //------------------------------------------------------------------------------
10 namespace System
.Data
{
12 using System
.ComponentModel
;
13 using System
.Diagnostics
;
14 using System
.Data
.Common
;
17 /// <para>Represents an action
18 /// restriction enforced on a set of columns in a primary key/foreign key relationship when
19 /// a value or row is either deleted or updated.</para>
22 DefaultProperty("ConstraintName"),
23 Editor("Microsoft.VSDesigner.Data.Design.ForeignKeyConstraintEditor, " + AssemblyRef
.MicrosoftVSDesigner
, "System.Drawing.Design.UITypeEditor, " + AssemblyRef
.SystemDrawing
),
25 public class ForeignKeyConstraint
: Constraint
{
27 internal const Rule Rule_Default
= Rule
.Cascade
;
28 internal const AcceptRejectRule AcceptRejectRule_Default
= AcceptRejectRule
.None
;
31 internal Rule deleteRule
= Rule_Default
;
32 internal Rule updateRule
= Rule_Default
;
33 internal AcceptRejectRule acceptRejectRule
= AcceptRejectRule_Default
;
34 private DataKey childKey
;
35 private DataKey parentKey
;
37 // Design time serialization
38 internal string constraintName
= null;
39 internal string[] parentColumnNames
= null;
40 internal string[] childColumnNames
= null;
41 internal string parentTableName
= null;
42 internal string parentTableNamespace
= null;
46 /// Initializes a new instance of the <see cref='System.Data.ForeignKeyConstraint'/> class with the specified parent and
47 /// child <see cref='System.Data.DataColumn'/> objects.
50 public ForeignKeyConstraint(DataColumn parentColumn
, DataColumn childColumn
)
51 : this(null, parentColumn
, childColumn
) {
56 /// Initializes a new instance of the <see cref='System.Data.ForeignKeyConstraint'/> class with the specified name,
57 /// parent and child <see cref='System.Data.DataColumn'/> objects.
60 public ForeignKeyConstraint(string constraintName
, DataColumn parentColumn
, DataColumn childColumn
) {
61 DataColumn
[] parentColumns
= new DataColumn
[] {parentColumn}
;
62 DataColumn
[] childColumns
= new DataColumn
[] {childColumn}
;
63 Create(constraintName
, parentColumns
, childColumns
);
68 /// Initializes a new instance of the <see cref='System.Data.ForeignKeyConstraint'/> class with the specified arrays
69 /// of parent and child <see cref='System.Data.DataColumn'/> objects.
72 public ForeignKeyConstraint(DataColumn
[] parentColumns
, DataColumn
[] childColumns
)
73 : this(null, parentColumns
, childColumns
) {
78 /// Initializes a new instance of the <see cref='System.Data.ForeignKeyConstraint'/> class with the specified name,
79 /// and arrays of parent and child <see cref='System.Data.DataColumn'/> objects.
82 public ForeignKeyConstraint(string constraintName
, DataColumn
[] parentColumns
, DataColumn
[] childColumns
) {
83 Create(constraintName
, parentColumns
, childColumns
);
86 // construct design time object
88 /// <para>[To be supplied.]</para>
91 public ForeignKeyConstraint(string constraintName
, string parentTableName
, string[] parentColumnNames
, string[] childColumnNames
,
92 AcceptRejectRule acceptRejectRule
, Rule deleteRule
, Rule updateRule
) {
93 this.constraintName
= constraintName
;
94 this.parentColumnNames
= parentColumnNames
;
95 this.childColumnNames
= childColumnNames
;
96 this.parentTableName
= parentTableName
;
97 this.acceptRejectRule
= acceptRejectRule
;
98 this.deleteRule
= deleteRule
;
99 this.updateRule
= updateRule
;
100 // ForeignKeyConstraint(constraintName, parentTableName, null, parentColumnNames, childColumnNames,acceptRejectRule, deleteRule, updateRule)
104 // construct design time object
106 public ForeignKeyConstraint(string constraintName
, string parentTableName
, string parentTableNamespace
, string[] parentColumnNames
,
107 string[] childColumnNames
, AcceptRejectRule acceptRejectRule
, Rule deleteRule
, Rule updateRule
) {
108 this.constraintName
= constraintName
;
109 this.parentColumnNames
= parentColumnNames
;
110 this.childColumnNames
= childColumnNames
;
111 this.parentTableName
= parentTableName
;
112 this.parentTableNamespace
= parentTableNamespace
;
113 this.acceptRejectRule
= acceptRejectRule
;
114 this.deleteRule
= deleteRule
;
115 this.updateRule
= updateRule
;
119 /// The internal constraint object for the child table.
121 internal DataKey ChildKey
{
123 CheckStateForProperty();
130 /// Gets the child columns of this constraint.
134 ResCategoryAttribute(Res
.DataCategory_Data
),
135 ResDescriptionAttribute(Res
.ForeignKeyConstraintChildColumnsDescr
),
138 public virtual DataColumn
[] Columns
{
140 CheckStateForProperty();
141 return childKey
.ToArray();
147 /// Gets the child table of this constraint.
151 ResCategoryAttribute(Res
.DataCategory_Data
),
152 ResDescriptionAttribute(Res
.ConstraintTableDescr
),
155 public override DataTable Table
{
157 CheckStateForProperty();
158 return childKey
.Table
;
162 internal string[] ParentColumnNames
{
164 return parentKey
.GetColumnNames();
168 internal string[] ChildColumnNames
{
170 return childKey
.GetColumnNames();
174 internal override void CheckCanAddToCollection(ConstraintCollection constraints
) {
175 if (Table
!= constraints
.Table
)
176 throw ExceptionBuilder
.ConstraintAddFailed(constraints
.Table
);
177 if (Table
.Locale
.LCID
!= RelatedTable
.Locale
.LCID
|| Table
.CaseSensitive
!= RelatedTable
.CaseSensitive
)
178 throw ExceptionBuilder
.CaseLocaleMismatch();
181 internal override bool CanBeRemovedFromCollection(ConstraintCollection constraints
, bool fThrowException
) {
185 internal bool IsKeyNull( object[] values
) {
186 for (int i
= 0; i
< values
.Length
; i
++) {
187 if (! DataStorage
.IsObjectNull(values
[i
]))
194 internal override bool IsConstraintViolated() {
195 Index childIndex
= childKey
.GetSortIndex();
196 object[] uniqueChildKeys
= childIndex
.GetUniqueKeyValues();
199 Index parentIndex
= parentKey
.GetSortIndex();
200 for (int i
= 0; i
< uniqueChildKeys
.Length
; i
++) {
201 object[] childValues
= (object[]) uniqueChildKeys
[i
];
203 if (!IsKeyNull(childValues
)) {
204 if (!parentIndex
.IsKeyInIndex(childValues
)) {
205 DataRow
[] rows
= childIndex
.GetRows(childIndex
.FindRecords(childValues
));
206 string error
= Res
.GetString(Res
.DataConstraint_ForeignKeyViolation
, ConstraintName
, ExceptionBuilder
.KeysToString(childValues
));
207 for (int j
= 0; j
< rows
.Length
; j
++) {
208 rows
[j
].RowError
= error
;
217 internal override bool CanEnableConstraint() {
218 if (Table
.DataSet
== null || !Table
.DataSet
.EnforceConstraints
)
221 Index childIndex
= childKey
.GetSortIndex();
222 object[] uniqueChildKeys
= childIndex
.GetUniqueKeyValues();
224 Index parentIndex
= parentKey
.GetSortIndex();
225 for (int i
= 0; i
< uniqueChildKeys
.Length
; i
++) {
226 object[] childValues
= (object[]) uniqueChildKeys
[i
];
228 if (!IsKeyNull(childValues
) && !parentIndex
.IsKeyInIndex(childValues
)) {
235 internal void CascadeCommit(DataRow row
) {
236 if (row
.RowState
== DataRowState
.Detached
)
238 if (acceptRejectRule
== AcceptRejectRule
.Cascade
) {
239 Index childIndex
= childKey
.GetSortIndex( row
.RowState
== DataRowState
.Deleted
? DataViewRowState
.Deleted
: DataViewRowState
.CurrentRows
);
240 object[] key
= row
.GetKeyValues(parentKey
, row
.RowState
== DataRowState
.Deleted
? DataRowVersion
.Original
: DataRowVersion
.Default
);
241 if (IsKeyNull(key
)) {
245 Range range
= childIndex
.FindRecords(key
);
247 // SQLBU 499726 - DataTable internal index is corrupted: '13'
248 // Self-referencing table has suspendIndexEvents, in the multi-table scenario the child table hasn't
249 // this allows the self-ref table to maintain the index while in the child-table doesn't
250 DataRow
[] rows
= childIndex
.GetRows(range
);
251 foreach(DataRow childRow
in rows
) {
252 if (DataRowState
.Detached
!= childRow
.RowState
) {
253 if (childRow
.inCascade
)
255 childRow
.AcceptChanges();
262 internal void CascadeDelete(DataRow row
) {
263 if (-1 == row
.newRecord
)
266 object[] currentKey
= row
.GetKeyValues(parentKey
, DataRowVersion
.Current
);
267 if (IsKeyNull(currentKey
)) {
271 Index childIndex
= childKey
.GetSortIndex();
272 switch (DeleteRule
) {
274 if (row
.Table
.DataSet
.EnforceConstraints
) {
275 // if we're not cascading deletes, we should throw if we're going to strand a child row under enforceConstraints.
276 Range range
= childIndex
.FindRecords(currentKey
);
278 if (range
.Count
== 1 && childIndex
.GetRow(range
.Min
) == row
)
281 throw ExceptionBuilder
.FailedCascadeDelete(ConstraintName
);
288 object[] key
= row
.GetKeyValues(parentKey
, DataRowVersion
.Default
);
289 Range range
= childIndex
.FindRecords(key
);
291 DataRow
[] rows
= childIndex
.GetRows(range
);
293 for (int j
= 0; j
< rows
.Length
; j
++) {
297 r
.Table
.DeleteRow(r
);
304 object[] proposedKey
= new object[childKey
.ColumnsReference
.Length
];
305 for (int i
= 0; i
< childKey
.ColumnsReference
.Length
; i
++)
306 proposedKey
[i
] = DBNull
.Value
;
307 Range range
= childIndex
.FindRecords(currentKey
);
309 DataRow
[] rows
= childIndex
.GetRows(range
);
310 for (int j
= 0; j
< rows
.Length
; j
++) {
311 // if (rows[j].inCascade)
314 rows
[j
].SetKeyValues(childKey
, proposedKey
);
319 case Rule
.SetDefault
: {
320 object[] proposedKey
= new object[childKey
.ColumnsReference
.Length
];
321 for (int i
= 0; i
< childKey
.ColumnsReference
.Length
; i
++)
322 proposedKey
[i
] = childKey
.ColumnsReference
[i
].DefaultValue
;
323 Range range
= childIndex
.FindRecords(currentKey
);
325 DataRow
[] rows
= childIndex
.GetRows(range
);
326 for (int j
= 0; j
< rows
.Length
; j
++) {
327 // if (rows[j].inCascade)
330 rows
[j
].SetKeyValues(childKey
, proposedKey
);
336 Debug
.Assert(false, "Unknown Rule value");
342 internal void CascadeRollback(DataRow row
) {
343 Index childIndex
= childKey
.GetSortIndex( row
.RowState
== DataRowState
.Deleted
? DataViewRowState
.OriginalRows
: DataViewRowState
.CurrentRows
);
344 object[] key
= row
.GetKeyValues(parentKey
, row
.RowState
== DataRowState
.Modified
? DataRowVersion
.Current
: DataRowVersion
.Default
);
346 // Bug : This is definitely not a proper fix. (Ref. MDAC Bug 73592)
347 if (IsKeyNull(key
)) {
351 Range range
= childIndex
.FindRecords(key
);
352 if (acceptRejectRule
== AcceptRejectRule
.Cascade
) {
354 DataRow
[] rows
= childIndex
.GetRows(range
);
355 for (int j
= 0; j
< rows
.Length
; j
++) {
356 if (rows
[j
].inCascade
)
358 rows
[j
].RejectChanges();
363 // AcceptRejectRule.None
364 if (row
.RowState
!= DataRowState
.Deleted
&& row
.Table
.DataSet
.EnforceConstraints
) {
366 if (range
.Count
== 1 && childIndex
.GetRow(range
.Min
) == row
)
369 if (row
.HasKeyChanged(parentKey
)) {// if key is not changed, this will not cause child to be stranded
370 throw ExceptionBuilder
.FailedCascadeUpdate(ConstraintName
);
377 internal void CascadeUpdate(DataRow row
) {
378 if (-1 == row
.newRecord
)
381 object[] currentKey
= row
.GetKeyValues(parentKey
, DataRowVersion
.Current
);
382 if (!Table
.DataSet
.fInReadXml
&& IsKeyNull(currentKey
)) {
386 Index childIndex
= childKey
.GetSortIndex();
387 switch (UpdateRule
) {
389 if (row
.Table
.DataSet
.EnforceConstraints
)
391 // if we're not cascading deletes, we should throw if we're going to strand a child row under enforceConstraints.
392 Range range
= childIndex
.FindRecords(currentKey
);
394 throw ExceptionBuilder
.FailedCascadeUpdate(ConstraintName
);
401 Range range
= childIndex
.FindRecords(currentKey
);
403 object[] proposedKey
= row
.GetKeyValues(parentKey
, DataRowVersion
.Proposed
);
404 DataRow
[] rows
= childIndex
.GetRows(range
);
405 for (int j
= 0; j
< rows
.Length
; j
++) {
406 // if (rows[j].inCascade)
408 rows
[j
].SetKeyValues(childKey
, proposedKey
);
415 object[] proposedKey
= new object[childKey
.ColumnsReference
.Length
];
416 for (int i
= 0; i
< childKey
.ColumnsReference
.Length
; i
++)
417 proposedKey
[i
] = DBNull
.Value
;
418 Range range
= childIndex
.FindRecords(currentKey
);
420 DataRow
[] rows
= childIndex
.GetRows(range
);
421 for (int j
= 0; j
< rows
.Length
; j
++) {
422 // if (rows[j].inCascade)
424 rows
[j
].SetKeyValues(childKey
, proposedKey
);
429 case Rule
.SetDefault
: {
430 object[] proposedKey
= new object[childKey
.ColumnsReference
.Length
];
431 for (int i
= 0; i
< childKey
.ColumnsReference
.Length
; i
++)
432 proposedKey
[i
] = childKey
.ColumnsReference
[i
].DefaultValue
;
433 Range range
= childIndex
.FindRecords(currentKey
);
435 DataRow
[] rows
= childIndex
.GetRows(range
);
436 for (int j
= 0; j
< rows
.Length
; j
++) {
437 // if (rows[j].inCascade)
439 rows
[j
].SetKeyValues(childKey
, proposedKey
);
445 Debug
.Assert(false, "Unknown Rule value");
451 internal void CheckCanClearParentTable(DataTable table
) {
452 if (Table
.DataSet
.EnforceConstraints
&& Table
.Rows
.Count
> 0) {
453 throw ExceptionBuilder
.FailedClearParentTable(table
.TableName
, ConstraintName
, Table
.TableName
);
457 internal void CheckCanRemoveParentRow(DataRow row
) {
458 Debug
.Assert(Table
.DataSet
!= null, "Relation " + ConstraintName
+ " isn't part of a DataSet, so this check shouldn't be happening.");
459 if (!Table
.DataSet
.EnforceConstraints
)
461 if (DataRelation
.GetChildRows(this.ParentKey
, this.ChildKey
, row
, DataRowVersion
.Default
).Length
> 0) {
462 throw ExceptionBuilder
.RemoveParentRow(this);
466 internal void CheckCascade(DataRow row
, DataRowAction action
) {
467 Debug
.Assert(Table
.DataSet
!= null, "ForeignKeyConstraint " + ConstraintName
+ " isn't part of a DataSet, so this check shouldn't be happening.");
472 row
.inCascade
= true;
474 if (action
== DataRowAction
.Change
) {
475 if (row
.HasKeyChanged(parentKey
)) {
479 else if (action
== DataRowAction
.Delete
) {
482 else if (action
== DataRowAction
.Commit
) {
485 else if (action
== DataRowAction
.Rollback
) {
486 CascadeRollback(row
);
488 else if (action
== DataRowAction
.Add
) {
491 Debug
.Assert(false, "attempt to cascade unknown action: " + ((Enum
) action
).ToString());
495 row
.inCascade
= false;
499 internal override void CheckConstraint(DataRow childRow
, DataRowAction action
) {
500 if ((action
== DataRowAction
.Change
||
501 action
== DataRowAction
.Add
||
502 action
== DataRowAction
.Rollback
) &&
503 Table
.DataSet
!= null && Table
.DataSet
.EnforceConstraints
&&
504 childRow
.HasKeyChanged(childKey
)) {
506 // This branch is for cascading case verification.
507 DataRowVersion version
= (action
== DataRowAction
.Rollback
) ? DataRowVersion
.Original
: DataRowVersion
.Current
;
508 object[] childKeyValues
= childRow
.GetKeyValues(childKey
);
509 // check to see if this is just a change to my parent's proposed value.
510 if (childRow
.HasVersion(version
)) {
511 // this is the new proposed value for the parent.
512 DataRow parentRow
= DataRelation
.GetParentRow(this.ParentKey
, this.ChildKey
, childRow
, version
);
513 if(parentRow
!= null && parentRow
.inCascade
) {
514 object[] parentKeyValues
= parentRow
.GetKeyValues(parentKey
, action
== DataRowAction
.Rollback
? version
: DataRowVersion
.Default
);
516 int parentKeyValuesRecord
= childRow
.Table
.NewRecord();
517 childRow
.Table
.SetKeyValues(childKey
, parentKeyValues
, parentKeyValuesRecord
);
518 if (childKey
.RecordsEqual(childRow
.tempRecord
, parentKeyValuesRecord
)) {
524 // now check to see if someone exists... it will have to be in a parent row's current, not a proposed.
525 object[] childValues
= childRow
.GetKeyValues(childKey
);
526 if (!IsKeyNull(childValues
)) {
527 Index parentIndex
= parentKey
.GetSortIndex();
528 if (!parentIndex
.IsKeyInIndex(childValues
)) {
529 // could be self-join constraint
530 if (childKey
.Table
== parentKey
.Table
&& childRow
.tempRecord
!= -1) {
532 for (lo
= 0; lo
< childValues
.Length
; lo
++) {
533 DataColumn column
= parentKey
.ColumnsReference
[lo
];
534 object value = column
.ConvertValue(childValues
[lo
]);
535 if (0 != column
.CompareValueTo(childRow
.tempRecord
, value)) {
539 if (lo
== childValues
.Length
) {
543 throw ExceptionBuilder
.ForeignKeyViolation(ConstraintName
, childKeyValues
);
549 private void NonVirtualCheckState () {
550 if (_DataSet
== null) {
551 // Make sure columns arrays are valid
552 parentKey
.CheckState();
553 childKey
.CheckState();
555 if (parentKey
.Table
.DataSet
!= childKey
.Table
.DataSet
) {
556 throw ExceptionBuilder
.TablesInDifferentSets();
559 for (int i
= 0; i
< parentKey
.ColumnsReference
.Length
; i
++) {
560 if (parentKey
.ColumnsReference
[i
].DataType
!= childKey
.ColumnsReference
[i
].DataType
||
561 ((parentKey
.ColumnsReference
[i
].DataType
== typeof(DateTime
)) && (parentKey
.ColumnsReference
[i
].DateTimeMode
!= childKey
.ColumnsReference
[i
].DateTimeMode
) && ((parentKey
.ColumnsReference
[i
].DateTimeMode
& childKey
.ColumnsReference
[i
].DateTimeMode
) != DataSetDateTime
.Unspecified
)))
562 throw ExceptionBuilder
.ColumnsTypeMismatch();
565 if (childKey
.ColumnsEqual(parentKey
)) {
566 throw ExceptionBuilder
.KeyColumnsIdentical();
571 // If we're not in a DataSet relations collection, we need to verify on every property get that we're
572 // still a good relation object.
573 internal override void CheckState() {
574 NonVirtualCheckState ();
579 /// Indicates what kind of action should take place across
580 /// this constraint when <see cref='System.Data.DataTable.AcceptChanges'/>
585 ResCategoryAttribute(Res
.DataCategory_Data
),
586 DefaultValue(AcceptRejectRule_Default
),
587 ResDescriptionAttribute(Res
.ForeignKeyConstraintAcceptRejectRuleDescr
)
589 public virtual AcceptRejectRule AcceptRejectRule
{
591 CheckStateForProperty();
592 return acceptRejectRule
;
595 switch(value) { // @perfnote: Enum.IsDefined
596 case AcceptRejectRule
.None
:
597 case AcceptRejectRule
.Cascade
:
598 this.acceptRejectRule
= value;
601 throw Common
.ADP
.InvalidAcceptRejectRule(value);
606 internal override bool ContainsColumn(DataColumn column
) {
607 return parentKey
.ContainsColumn(column
) || childKey
.ContainsColumn(column
);
610 internal override Constraint
Clone(DataSet destination
) {
611 return Clone(destination
, false);
614 internal override Constraint
Clone(DataSet destination
, bool ignorNSforTableLookup
) {
616 if (ignorNSforTableLookup
) {
617 iDest
= destination
.Tables
.IndexOf(Table
.TableName
);
620 iDest
= destination
.Tables
.IndexOf(Table
.TableName
, Table
.Namespace
, false); // pass false for last param
621 // to be backward compatable, otherwise meay cause new exception
626 DataTable table
= destination
.Tables
[iDest
];
627 if (ignorNSforTableLookup
) {
628 iDest
= destination
.Tables
.IndexOf(RelatedTable
.TableName
);
631 iDest
= destination
.Tables
.IndexOf(RelatedTable
.TableName
, RelatedTable
.Namespace
, false);// pass false for last param
635 DataTable relatedTable
= destination
.Tables
[iDest
];
637 int keys
= Columns
.Length
;
638 DataColumn
[] columns
= new DataColumn
[keys
];
639 DataColumn
[] relatedColumns
= new DataColumn
[keys
];
641 for (int i
= 0; i
< keys
; i
++) {
642 DataColumn src
= Columns
[i
];
643 iDest
= table
.Columns
.IndexOf(src
.ColumnName
);
646 columns
[i
] = table
.Columns
[iDest
];
648 src
= RelatedColumnsReference
[i
];
649 iDest
= relatedTable
.Columns
.IndexOf(src
.ColumnName
);
652 relatedColumns
[i
] = relatedTable
.Columns
[iDest
];
654 ForeignKeyConstraint clone
= new ForeignKeyConstraint(ConstraintName
,relatedColumns
, columns
);
655 clone
.UpdateRule
= this.UpdateRule
;
656 clone
.DeleteRule
= this.DeleteRule
;
657 clone
.AcceptRejectRule
= this.AcceptRejectRule
;
659 // ...Extended Properties
660 foreach(Object key
in this.ExtendedProperties
.Keys
) {
661 clone
.ExtendedProperties
[key
]=this.ExtendedProperties
[key
];
668 internal ForeignKeyConstraint
Clone(DataTable destination
) {
669 Debug
.Assert(this.Table
== this.RelatedTable
, "We call this clone just if we have the same datatable as parent and child ");
670 int keys
= Columns
.Length
;
671 DataColumn
[] columns
= new DataColumn
[keys
];
672 DataColumn
[] relatedColumns
= new DataColumn
[keys
];
676 for (int i
= 0; i
< keys
; i
++) {
677 DataColumn src
= Columns
[i
];
678 iDest
= destination
.Columns
.IndexOf(src
.ColumnName
);
681 columns
[i
] = destination
.Columns
[iDest
];
683 src
= RelatedColumnsReference
[i
];
684 iDest
= destination
.Columns
.IndexOf(src
.ColumnName
);
687 relatedColumns
[i
] = destination
.Columns
[iDest
];
689 ForeignKeyConstraint clone
= new ForeignKeyConstraint(ConstraintName
, relatedColumns
, columns
);
690 clone
.UpdateRule
= this.UpdateRule
;
691 clone
.DeleteRule
= this.DeleteRule
;
692 clone
.AcceptRejectRule
= this.AcceptRejectRule
;
694 // ...Extended Properties
695 foreach(Object key
in this.ExtendedProperties
.Keys
) {
696 clone
.ExtendedProperties
[key
]=this.ExtendedProperties
[key
];
704 private void Create(string relationName
, DataColumn
[] parentColumns
, DataColumn
[] childColumns
) {
705 if (parentColumns
.Length
== 0 || childColumns
.Length
== 0)
706 throw ExceptionBuilder
.KeyLengthZero();
708 if (parentColumns
.Length
!= childColumns
.Length
)
709 throw ExceptionBuilder
.KeyLengthMismatch();
711 for (int i
= 0; i
< parentColumns
.Length
; i
++) {
712 if (parentColumns
[i
].Computed
) {
713 throw ExceptionBuilder
.ExpressionInConstraint(parentColumns
[i
]);
715 if (childColumns
[i
].Computed
) {
716 throw ExceptionBuilder
.ExpressionInConstraint(childColumns
[i
]);
720 this.parentKey
= new DataKey(parentColumns
, true);
721 this.childKey
= new DataKey(childColumns
, true);
723 ConstraintName
= relationName
;
725 NonVirtualCheckState();
730 /// or sets the action that occurs across this constraint when a row is deleted.</para>
733 ResCategoryAttribute(Res
.DataCategory_Data
),
734 DefaultValue(Rule_Default
),
735 ResDescriptionAttribute(Res
.ForeignKeyConstraintDeleteRuleDescr
)
737 public virtual Rule DeleteRule
{
739 CheckStateForProperty();
743 switch(value) { // @perfnote: Enum.IsDefined
747 case Rule
.SetDefault
:
748 this.deleteRule
= value;
751 throw Common
.ADP
.InvalidRule(value);
757 /// <para>Gets a value indicating whether the current <see cref='System.Data.ForeignKeyConstraint'/> is identical to the specified object.</para>
759 public override bool Equals(object key
) {
760 if (!(key
is ForeignKeyConstraint
))
762 ForeignKeyConstraint key2
= (ForeignKeyConstraint
) key
;
764 // The ParentKey and ChildKey completely identify the ForeignKeyConstraint
765 return this.ParentKey
.ColumnsEqual(key2
.ParentKey
) && this.ChildKey
.ColumnsEqual(key2
.ChildKey
);
769 /// <para>[To be supplied.]</para>
771 public override Int32
GetHashCode() {
772 return base.GetHashCode();
777 /// The parent columns of this constraint.
781 ResCategoryAttribute(Res
.DataCategory_Data
),
782 ResDescriptionAttribute(Res
.ForeignKeyConstraintParentColumnsDescr
),
785 public virtual DataColumn
[] RelatedColumns
{
787 CheckStateForProperty();
788 return parentKey
.ToArray();
792 internal DataColumn
[] RelatedColumnsReference
{
794 CheckStateForProperty();
795 return parentKey
.ColumnsReference
;
800 /// The internal key object for the parent table.
802 internal DataKey ParentKey
{
804 CheckStateForProperty();
809 internal DataRelation
FindParentRelation () {
810 DataRelationCollection rels
= Table
.ParentRelations
;
812 for (int i
= 0; i
< rels
.Count
; i
++) {
813 if (rels
[i
].ChildKeyConstraint
== this)
821 /// Gets the parent table of this constraint.
825 ResCategoryAttribute(Res
.DataCategory_Data
),
826 ResDescriptionAttribute(Res
.ForeignKeyRelatedTableDescr
),
829 public virtual DataTable RelatedTable
{
831 CheckStateForProperty();
832 return parentKey
.Table
;
838 /// sets the action that occurs across this constraint on when a row is
842 ResCategoryAttribute(Res
.DataCategory_Data
),
843 DefaultValue(Rule_Default
),
844 ResDescriptionAttribute(Res
.ForeignKeyConstraintUpdateRuleDescr
)
846 public virtual Rule UpdateRule
{
848 CheckStateForProperty();
852 switch(value) { // @perfnote: Enum.IsDefined
856 case Rule
.SetDefault
:
857 this.updateRule
= value;
860 throw Common
.ADP
.InvalidRule(value);