[bcl] Updates referencesource to 4.7.1
[mono-project.git] / mcs / class / referencesource / System.Data / System / Data / ForeignKeyConstraint.cs
blob26350a8bff77a0f7136d03d621ffe4ea8a26e53e
1 //------------------------------------------------------------------------------
2 // <copyright file="ForeignKeyConstraint.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
4 // </copyright>
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 {
11 using System;
12 using System.ComponentModel;
13 using System.Diagnostics;
14 using System.Data.Common;
16 /// <devdoc>
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>
20 /// </devdoc>
22 DefaultProperty("ConstraintName"),
23 Editor("Microsoft.VSDesigner.Data.Design.ForeignKeyConstraintEditor, " + AssemblyRef.MicrosoftVSDesigner, "System.Drawing.Design.UITypeEditor, " + AssemblyRef.SystemDrawing),
25 public class ForeignKeyConstraint : Constraint {
26 // constants
27 internal const Rule Rule_Default = Rule.Cascade;
28 internal const AcceptRejectRule AcceptRejectRule_Default = AcceptRejectRule.None;
30 // properties
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;
44 /// <devdoc>
45 /// <para>
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.
48 /// </para>
49 /// </devdoc>
50 public ForeignKeyConstraint(DataColumn parentColumn, DataColumn childColumn)
51 : this(null, parentColumn, childColumn) {
54 /// <devdoc>
55 /// <para>
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.
58 /// </para>
59 /// </devdoc>
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);
66 /// <devdoc>
67 /// <para>
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.
70 /// </para>
71 /// </devdoc>
72 public ForeignKeyConstraint(DataColumn[] parentColumns, DataColumn[] childColumns)
73 : this(null, parentColumns, childColumns) {
76 /// <devdoc>
77 /// <para>
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.
80 /// </para>
81 /// </devdoc>
82 public ForeignKeyConstraint(string constraintName, DataColumn[] parentColumns, DataColumn[] childColumns) {
83 Create(constraintName, parentColumns, childColumns);
86 // construct design time object
87 /// <devdoc>
88 /// <para>[To be supplied.]</para>
89 /// </devdoc>
90 [Browsable(false)]
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
105 [Browsable(false)]
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;
118 /// <devdoc>
119 /// The internal constraint object for the child table.
120 /// </devdoc>
121 internal DataKey ChildKey {
122 get {
123 CheckStateForProperty();
124 return childKey;
128 /// <devdoc>
129 /// <para>
130 /// Gets the child columns of this constraint.
131 /// </para>
132 /// </devdoc>
134 ResCategoryAttribute(Res.DataCategory_Data),
135 ResDescriptionAttribute(Res.ForeignKeyConstraintChildColumnsDescr),
136 ReadOnly(true)
138 public virtual DataColumn[] Columns {
139 get {
140 CheckStateForProperty();
141 return childKey.ToArray();
145 /// <devdoc>
146 /// <para>
147 /// Gets the child table of this constraint.
148 /// </para>
149 /// </devdoc>
151 ResCategoryAttribute(Res.DataCategory_Data),
152 ResDescriptionAttribute(Res.ConstraintTableDescr),
153 ReadOnly(true)
155 public override DataTable Table {
156 get {
157 CheckStateForProperty();
158 return childKey.Table;
162 internal string[] ParentColumnNames {
163 get {
164 return parentKey.GetColumnNames();
168 internal string[] ChildColumnNames {
169 get {
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) {
182 return true;
185 internal bool IsKeyNull( object[] values ) {
186 for (int i = 0; i < values.Length; i++) {
187 if (! DataStorage.IsObjectNull(values[i]))
188 return false;
191 return true;
194 internal override bool IsConstraintViolated() {
195 Index childIndex = childKey.GetSortIndex();
196 object[] uniqueChildKeys = childIndex.GetUniqueKeyValues();
197 bool errors = false;
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;
210 errors = true;
214 return errors;
217 internal override bool CanEnableConstraint() {
218 if (Table.DataSet == null || !Table.DataSet.EnforceConstraints)
219 return true;
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)) {
229 return false;
232 return true;
235 internal void CascadeCommit(DataRow row) {
236 if (row.RowState == DataRowState.Detached)
237 return;
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)) {
242 return;
245 Range range = childIndex.FindRecords(key);
246 if (!range.IsNull) {
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)
254 continue;
255 childRow.AcceptChanges();
262 internal void CascadeDelete(DataRow row) {
263 if (-1 == row.newRecord)
264 return;
266 object[] currentKey = row.GetKeyValues(parentKey, DataRowVersion.Current);
267 if (IsKeyNull(currentKey)) {
268 return;
271 Index childIndex = childKey.GetSortIndex();
272 switch (DeleteRule) {
273 case Rule.None: {
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);
277 if (!range.IsNull) {
278 if (range.Count == 1 && childIndex.GetRow(range.Min) == row)
279 return;
281 throw ExceptionBuilder.FailedCascadeDelete(ConstraintName);
284 break;
287 case Rule.Cascade: {
288 object[] key = row.GetKeyValues(parentKey, DataRowVersion.Default);
289 Range range = childIndex.FindRecords(key);
290 if (!range.IsNull) {
291 DataRow[] rows = childIndex.GetRows(range);
293 for (int j = 0; j < rows.Length; j++) {
294 DataRow r = rows[j];
295 if (r.inCascade)
296 continue;
297 r.Table.DeleteRow(r);
300 break;
303 case Rule.SetNull: {
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);
308 if (!range.IsNull) {
309 DataRow[] rows = childIndex.GetRows(range);
310 for (int j = 0; j < rows.Length; j++) {
311 // if (rows[j].inCascade)
312 // continue;
313 if (row != rows[j])
314 rows[j].SetKeyValues(childKey, proposedKey);
317 break;
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);
324 if (!range.IsNull) {
325 DataRow[] rows = childIndex.GetRows(range);
326 for (int j = 0; j < rows.Length; j++) {
327 // if (rows[j].inCascade)
328 // continue;
329 if (row != rows[j])
330 rows[j].SetKeyValues(childKey, proposedKey);
333 break;
335 default: {
336 Debug.Assert(false, "Unknown Rule value");
337 break;
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)) {
348 return;
351 Range range = childIndex.FindRecords(key);
352 if (acceptRejectRule == AcceptRejectRule.Cascade) {
353 if (!range.IsNull) {
354 DataRow[] rows = childIndex.GetRows(range);
355 for (int j = 0; j < rows.Length; j++) {
356 if (rows[j].inCascade)
357 continue;
358 rows[j].RejectChanges();
362 else {
363 // AcceptRejectRule.None
364 if (row.RowState != DataRowState.Deleted && row.Table.DataSet.EnforceConstraints) {
365 if (!range.IsNull) {
366 if (range.Count == 1 && childIndex.GetRow(range.Min) == row)
367 return;
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)
379 return;
381 object[] currentKey = row.GetKeyValues(parentKey, DataRowVersion.Current);
382 if (!Table.DataSet.fInReadXml && IsKeyNull(currentKey)) {
383 return;
386 Index childIndex = childKey.GetSortIndex();
387 switch (UpdateRule) {
388 case Rule.None: {
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);
393 if (!range.IsNull) {
394 throw ExceptionBuilder.FailedCascadeUpdate(ConstraintName);
397 break;
400 case Rule.Cascade: {
401 Range range = childIndex.FindRecords(currentKey);
402 if (!range.IsNull) {
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)
407 // continue;
408 rows[j].SetKeyValues(childKey, proposedKey);
411 break;
414 case Rule.SetNull: {
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);
419 if (!range.IsNull) {
420 DataRow[] rows = childIndex.GetRows(range);
421 for (int j = 0; j < rows.Length; j++) {
422 // if (rows[j].inCascade)
423 // continue;
424 rows[j].SetKeyValues(childKey, proposedKey);
427 break;
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);
434 if (!range.IsNull) {
435 DataRow[] rows = childIndex.GetRows(range);
436 for (int j = 0; j < rows.Length; j++) {
437 // if (rows[j].inCascade)
438 // continue;
439 rows[j].SetKeyValues(childKey, proposedKey);
442 break;
444 default: {
445 Debug.Assert(false, "Unknown Rule value");
446 break;
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)
460 return;
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.");
469 if (row.inCascade)
470 return;
472 row.inCascade = true;
473 try {
474 if (action == DataRowAction.Change) {
475 if (row.HasKeyChanged(parentKey)) {
476 CascadeUpdate(row);
479 else if (action == DataRowAction.Delete) {
480 CascadeDelete(row);
482 else if (action == DataRowAction.Commit) {
483 CascadeCommit(row);
485 else if (action == DataRowAction.Rollback) {
486 CascadeRollback(row);
488 else if (action == DataRowAction.Add) {
490 else {
491 Debug.Assert(false, "attempt to cascade unknown action: " + ((Enum) action).ToString());
494 finally {
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)) {
519 return;
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) {
531 int lo = 0;
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)) {
536 break;
539 if (lo == childValues.Length) {
540 return;
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 ();
577 /// <devdoc>
578 /// <para>
579 /// Indicates what kind of action should take place across
580 /// this constraint when <see cref='System.Data.DataTable.AcceptChanges'/>
581 /// is invoked.
582 /// </para>
583 /// </devdoc>
585 ResCategoryAttribute(Res.DataCategory_Data),
586 DefaultValue(AcceptRejectRule_Default),
587 ResDescriptionAttribute(Res.ForeignKeyConstraintAcceptRejectRuleDescr)
589 public virtual AcceptRejectRule AcceptRejectRule {
590 get {
591 CheckStateForProperty();
592 return acceptRejectRule;
594 set {
595 switch(value) { // @perfnote: Enum.IsDefined
596 case AcceptRejectRule.None:
597 case AcceptRejectRule.Cascade:
598 this.acceptRejectRule = value;
599 break;
600 default:
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) {
615 int iDest;
616 if (ignorNSforTableLookup) {
617 iDest = destination.Tables.IndexOf(Table.TableName);
619 else {
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
624 if (iDest < 0)
625 return null;
626 DataTable table = destination.Tables[iDest];
627 if (ignorNSforTableLookup) {
628 iDest = destination.Tables.IndexOf(RelatedTable.TableName);
630 else {
631 iDest = destination.Tables.IndexOf(RelatedTable.TableName, RelatedTable.Namespace, false);// pass false for last param
633 if (iDest < 0)
634 return null;
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);
644 if (iDest < 0)
645 return null;
646 columns[i] = table.Columns[iDest];
648 src = RelatedColumnsReference[i];
649 iDest = relatedTable.Columns.IndexOf(src.ColumnName);
650 if (iDest < 0)
651 return null;
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];
664 return clone;
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];
674 int iDest =0;
676 for (int i = 0; i < keys; i++) {
677 DataColumn src = Columns[i];
678 iDest = destination.Columns.IndexOf(src.ColumnName);
679 if (iDest < 0)
680 return null;
681 columns[i] = destination.Columns[iDest];
683 src = RelatedColumnsReference[i];
684 iDest = destination.Columns.IndexOf(src.ColumnName);
685 if (iDest < 0)
686 return null;
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];
699 return clone;
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();
728 /// <devdoc>
729 /// <para> Gets
730 /// or sets the action that occurs across this constraint when a row is deleted.</para>
731 /// </devdoc>
733 ResCategoryAttribute(Res.DataCategory_Data),
734 DefaultValue(Rule_Default),
735 ResDescriptionAttribute(Res.ForeignKeyConstraintDeleteRuleDescr)
737 public virtual Rule DeleteRule {
738 get {
739 CheckStateForProperty();
740 return deleteRule;
742 set {
743 switch(value) { // @perfnote: Enum.IsDefined
744 case Rule.None:
745 case Rule.Cascade:
746 case Rule.SetNull:
747 case Rule.SetDefault:
748 this.deleteRule = value;
749 break;
750 default:
751 throw Common.ADP.InvalidRule(value);
756 /// <devdoc>
757 /// <para>Gets a value indicating whether the current <see cref='System.Data.ForeignKeyConstraint'/> is identical to the specified object.</para>
758 /// </devdoc>
759 public override bool Equals(object key) {
760 if (!(key is ForeignKeyConstraint))
761 return false;
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);
768 /// <devdoc>
769 /// <para>[To be supplied.]</para>
770 /// </devdoc>
771 public override Int32 GetHashCode() {
772 return base.GetHashCode();
775 /// <devdoc>
776 /// <para>
777 /// The parent columns of this constraint.
778 /// </para>
779 /// </devdoc>
781 ResCategoryAttribute(Res.DataCategory_Data),
782 ResDescriptionAttribute(Res.ForeignKeyConstraintParentColumnsDescr),
783 ReadOnly(true)
785 public virtual DataColumn[] RelatedColumns {
786 get {
787 CheckStateForProperty();
788 return parentKey.ToArray();
792 internal DataColumn[] RelatedColumnsReference {
793 get {
794 CheckStateForProperty();
795 return parentKey.ColumnsReference;
799 /// <devdoc>
800 /// The internal key object for the parent table.
801 /// </devdoc>
802 internal DataKey ParentKey {
803 get {
804 CheckStateForProperty();
805 return parentKey;
809 internal DataRelation FindParentRelation () {
810 DataRelationCollection rels = Table.ParentRelations;
812 for (int i = 0; i < rels.Count; i++) {
813 if (rels[i].ChildKeyConstraint == this)
814 return rels[i];
816 return null;
819 /// <devdoc>
820 /// <para>
821 /// Gets the parent table of this constraint.
822 /// </para>
823 /// </devdoc>
825 ResCategoryAttribute(Res.DataCategory_Data),
826 ResDescriptionAttribute(Res.ForeignKeyRelatedTableDescr),
827 ReadOnly(true)
829 public virtual DataTable RelatedTable {
830 get {
831 CheckStateForProperty();
832 return parentKey.Table;
836 /// <devdoc>
837 /// <para>Gets or
838 /// sets the action that occurs across this constraint on when a row is
839 /// updated.</para>
840 /// </devdoc>
842 ResCategoryAttribute(Res.DataCategory_Data),
843 DefaultValue(Rule_Default),
844 ResDescriptionAttribute(Res.ForeignKeyConstraintUpdateRuleDescr)
846 public virtual Rule UpdateRule {
847 get {
848 CheckStateForProperty();
849 return updateRule;
851 set {
852 switch(value) { // @perfnote: Enum.IsDefined
853 case Rule.None:
854 case Rule.Cascade:
855 case Rule.SetNull:
856 case Rule.SetDefault:
857 this.updateRule = value;
858 break;
859 default:
860 throw Common.ADP.InvalidRule(value);