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;
39 using System.Data.Common;
41 namespace System.Data {
42 [Editor ("Microsoft.VSDesigner.Data.Design.ForeignKeyConstraintEditor, " + Consts.AssemblyMicrosoft_VSDesigner,
43 "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
44 [DefaultProperty ("ConstraintName")]
45 public class ForeignKeyConstraint : Constraint
47 private UniqueConstraint _parentUniqueConstraint;
48 //FIXME: create a class which will wrap this collection
49 private DataColumn [] _parentColumns;
50 //FIXME: create a class which will wrap this collection
51 private DataColumn [] _childColumns;
52 private Rule _deleteRule = Rule.Cascade;
53 private Rule _updateRule = Rule.Cascade;
54 private AcceptRejectRule _acceptRejectRule = AcceptRejectRule.None;
55 private string _parentTableName;
56 private string _parentTableNamespace;
57 private string _childTableName;
59 //FIXME: remove those; and use only DataColumns[]
60 private string [] _parentColumnNames;
61 private string [] _childColumnNames;
65 public ForeignKeyConstraint(DataColumn parentColumn, DataColumn childColumn)
67 if (null == parentColumn || null == childColumn) {
68 throw new NullReferenceException("Neither parentColumn or" +
69 " childColumn can be null.");
71 _foreignKeyConstraint(null, new DataColumn[] {parentColumn},
72 new DataColumn[] {childColumn});
75 public ForeignKeyConstraint(DataColumn[] parentColumns, DataColumn[] childColumns)
77 _foreignKeyConstraint(null, parentColumns, childColumns);
80 public ForeignKeyConstraint(string constraintName, DataColumn parentColumn, DataColumn childColumn)
82 if (null == parentColumn || null == childColumn) {
83 throw new NullReferenceException("Neither parentColumn or" +
84 " childColumn can be null.");
87 _foreignKeyConstraint(constraintName, new DataColumn[] {parentColumn},
88 new DataColumn[] {childColumn});
91 public ForeignKeyConstraint(string constraintName, DataColumn[] parentColumns, DataColumn[] childColumns)
93 _foreignKeyConstraint(constraintName, parentColumns, childColumns);
98 public ForeignKeyConstraint(string constraintName, string parentTableName, string[] parentColumnNames, string[] childColumnNames, AcceptRejectRule acceptRejectRule, Rule deleteRule, Rule updateRule)
101 InitInProgress = true;
102 base.ConstraintName = constraintName;
104 // "parentTableName" is searched in the "DataSet" to which the "DataTable"
105 // from which AddRange() is called
106 // childTable is the "DataTable" which calls AddRange()
108 // Keep reference to parentTableName to resolve later
109 _parentTableName = parentTableName;
111 // Keep a copy of parentColumnNames to resolve later
112 _parentColumnNames = new string [parentColumnNames.Length];
113 for (i = 0; i < parentColumnNames.Length; i++)
114 _parentColumnNames[i] = parentColumnNames[i];
116 // Keep a copy of childColumnNames to resolve later
117 _childColumnNames = new string [childColumnNames.Length];
118 for (i = 0; i < childColumnNames.Length; i++)
119 _childColumnNames[i] = childColumnNames[i];
122 _acceptRejectRule = acceptRejectRule;
123 _deleteRule = deleteRule;
124 _updateRule = updateRule;
127 internal override void FinishInit (DataTable childTable)
129 if (childTable.DataSet == null)
130 throw new InvalidConstraintException ("ChildTable : " + childTable.TableName + " does not belong to any DataSet");
132 DataSet dataSet = childTable.DataSet;
133 _childTableName = childTable.TableName;
135 if (!dataSet.Tables.Contains (_parentTableName))
136 throw new InvalidConstraintException ("Table : " + _parentTableName + "does not exist in DataSet : " + dataSet);
138 DataTable parentTable = dataSet.Tables [_parentTableName];
142 if (_parentColumnNames.Length < 0 || _childColumnNames.Length < 0)
143 throw new InvalidConstraintException ("Neither parent nor child columns can be zero length");
145 if (_parentColumnNames.Length != _childColumnNames.Length)
146 throw new InvalidConstraintException ("Both parent and child columns must be of same length");
147 DataColumn[] parentColumns = new DataColumn [_parentColumnNames.Length];
148 DataColumn[] childColumns = new DataColumn [_childColumnNames.Length];
150 foreach (string parentCol in _parentColumnNames){
151 if (!parentTable.Columns.Contains (parentCol))
152 throw new InvalidConstraintException ("Table : " + _parentTableName + "does not contain the column :" + parentCol);
153 parentColumns [i++] = parentTable. Columns [parentCol];
156 foreach (string childCol in _childColumnNames){
157 if (!childTable.Columns.Contains (childCol))
158 throw new InvalidConstraintException ("Table : " + _childTableName + "does not contain the column : " + childCol);
159 childColumns [j++] = childTable.Columns [childCol];
161 _validateColumns (parentColumns, childColumns);
162 _parentColumns = parentColumns;
163 _childColumns = childColumns;
164 parentTable.Namespace = _parentTableNamespace;
165 InitInProgress = false;
169 public ForeignKeyConstraint (string constraintName, string parentTableName, string parentTableNamespace, string[] parentColumnNames, string[] childColumnNames, AcceptRejectRule acceptRejectRule, Rule deleteRule, Rule updateRule)
171 InitInProgress = true;
172 base.ConstraintName = constraintName;
174 // "parentTableName" is searched in the "DataSet" to which the "DataTable"
175 // from which AddRange() is called
176 // childTable is the "DataTable" which calls AddRange()
178 // Keep reference to parentTableName to resolve later
179 _parentTableName = parentTableName;
180 _parentTableNamespace = parentTableNamespace;
182 // Keep reference to parentColumnNames to resolve later
183 _parentColumnNames = parentColumnNames;
185 // Keep reference to childColumnNames to resolve later
186 _childColumnNames = childColumnNames;
188 _acceptRejectRule = acceptRejectRule;
189 _deleteRule = deleteRule;
190 _updateRule = updateRule;
193 private void _foreignKeyConstraint(string constraintName, DataColumn[] parentColumns,
194 DataColumn[] childColumns)
198 _validateColumns(parentColumns, childColumns);
200 //Set Constraint Name
201 base.ConstraintName = constraintName;
203 //copy the columns - Do not keep reference #672113
204 _parentColumns = new DataColumn [parentColumns.Length];
205 for (i = 0; i < parentColumns.Length; i++)
206 _parentColumns[i] = parentColumns[i];
208 _childColumns = new DataColumn [childColumns.Length];
209 for (i = 0; i < childColumns.Length; i++)
210 _childColumns[i] = childColumns[i];
213 #endregion // Constructors
217 private void _validateColumns(DataColumn[] parentColumns, DataColumn[] childColumns)
220 if (null == parentColumns || null == childColumns)
221 throw new ArgumentNullException();
223 //at least one element in each array
224 if (parentColumns.Length < 1 || childColumns.Length < 1)
225 throw new ArgumentException("Neither ParentColumns or ChildColumns can't be" +
229 if (parentColumns.Length != childColumns.Length)
230 throw new ArgumentException("Parent columns and child columns must be the same length.");
233 DataTable ptable = parentColumns[0].Table;
234 DataTable ctable = childColumns[0].Table;
236 for (int i = 0; i < parentColumns.Length; i++) {
237 DataColumn pc = parentColumns[i];
238 DataColumn cc = childColumns[i];
241 if (null == pc.Table)
242 throw new ArgumentException("All columns must belong to a table." +
243 " ColumnName: " + pc.ColumnName + " does not belong to a table.");
245 //All columns must belong to the same table
246 if (ptable != pc.Table)
247 throw new InvalidConstraintException("Parent columns must all belong to the same table.");
250 if (null == cc.Table)
251 throw new ArgumentException("All columns must belong to a table." +
252 " ColumnName: " + pc.ColumnName + " does not belong to a table.");
254 //All columns must belong to the same table.
255 if (ctable != cc.Table)
256 throw new InvalidConstraintException("Child columns must all belong to the same table.");
258 if (pc.CompiledExpression != null)
259 throw new ArgumentException(String.Format("Cannot create a constraint based on Expression column {0}.", pc.ColumnName));
261 if (cc.CompiledExpression != null)
262 throw new ArgumentException(String.Format("Cannot create a constraint based on Expression column {0}.", cc.ColumnName));
266 //Same dataset. If both are null it's ok
267 if (ptable.DataSet != ctable.DataSet)
269 //LAMESPEC: spec says InvalidConstraintExceptoin
270 // impl does InvalidOperationException
271 throw new InvalidOperationException("Parent column and child column must belong to" +
272 " tables that belong to the same DataSet.");
277 int identicalCols = 0;
278 for (int i = 0; i < parentColumns.Length; i++)
280 DataColumn pc = parentColumns[i];
281 DataColumn cc = childColumns[i];
288 if (!pc.DataTypeMatches (cc)) {
289 //LAMESPEC: spec says throw InvalidConstraintException
290 // implementation throws InvalidOperationException
291 throw new InvalidOperationException ("Parent column is not type compatible with it's child"
295 if (identicalCols == parentColumns.Length)
296 throw new InvalidOperationException ("Property not accessible because 'ParentKey and ChildKey are identical.'.");
300 //Checks to see if a related unique constraint exists
301 //if it doesn't then a unique constraint is created.
302 //if a unique constraint can't be created an exception will be thrown
303 private void _ensureUniqueConstraintExists(ConstraintCollection collection,
304 DataColumn [] parentColumns)
307 if (null == parentColumns) throw new ArgumentNullException(
308 "ParentColumns can't be null");
310 UniqueConstraint uc = null;
312 //see if unique constraint already exists
313 //if not create unique constraint
314 if(parentColumns[0] != null)
315 uc = UniqueConstraint.GetUniqueConstraintForColumnSet(parentColumns[0].Table.Constraints, parentColumns);
318 uc = new UniqueConstraint(parentColumns, false); //could throw
319 parentColumns [0].Table.Constraints.Add (uc);
323 _parentUniqueConstraint = uc;
324 _parentUniqueConstraint.ChildConstraint = this;
332 [DataCategory ("Data")]
333 [DefaultValue (AcceptRejectRule.None)]
334 public virtual AcceptRejectRule AcceptRejectRule {
335 get { return _acceptRejectRule; }
336 set { _acceptRejectRule = value; }
339 [DataCategory ("Data")]
341 public virtual DataColumn[] Columns {
342 get { return _childColumns; }
345 [DataCategory ("Data")]
346 [DefaultValue (Rule.Cascade)]
347 public virtual Rule DeleteRule {
348 get { return _deleteRule; }
349 set { _deleteRule = value; }
352 [DataCategory ("Data")]
353 [DefaultValue (Rule.Cascade)]
354 public virtual Rule UpdateRule {
355 get { return _updateRule; }
356 set { _updateRule = value; }
359 [DataCategory ("Data")]
361 public virtual DataColumn[] RelatedColumns {
362 get { return _parentColumns; }
365 [DataCategory ("Data")]
367 public virtual DataTable RelatedTable {
369 if (_parentColumns != null)
370 if (_parentColumns.Length > 0)
371 return _parentColumns[0].Table;
373 throw new InvalidOperationException ("Property not accessible because 'Object reference not set to an instance of an object'");
377 [DataCategory ("Data")]
379 public override DataTable Table {
381 if (_childColumns != null)
382 if (_childColumns.Length > 0)
383 return _childColumns[0].Table;
385 throw new InvalidOperationException ("Property not accessible because 'Object reference not set to an instance of an object'");
389 internal UniqueConstraint ParentConstraint {
390 get { return _parentUniqueConstraint; }
393 #endregion // Properties
397 public override bool Equals(object key)
399 ForeignKeyConstraint fkc = key as ForeignKeyConstraint;
400 if (null == fkc) return false;
402 //if the fk constrains the same columns then they are equal
403 if (! DataColumn.AreColumnSetsTheSame( this.RelatedColumns, fkc.RelatedColumns))
405 if (! DataColumn.AreColumnSetsTheSame( this.Columns, fkc.Columns) )
411 public override int GetHashCode()
413 //initialize hash1 and hash2 with default hashes
414 //any two DIFFERENT numbers will do here
415 int hash1 = 32, hash2 = 88;
418 //derive the hash code from the columns that way
419 //Equals and GetHashCode return Equal objects to be the
422 //Get the first parent column hash
423 if (this.Columns.Length > 0)
424 hash1 ^= this.Columns[0].GetHashCode();
426 //get the rest of the parent column hashes if there any
427 for (i = 1; i < this.Columns.Length; i++)
429 hash1 ^= this.Columns[1].GetHashCode();
433 //Get the child column hash
434 if (this.RelatedColumns.Length > 0)
435 hash2 ^= this.Columns[0].GetHashCode();
437 for (i = 1; i < this.RelatedColumns.Length; i++)
439 hash2 ^= this.RelatedColumns[1].GetHashCode();
442 //combine the two hashes
443 return hash1 ^ hash2;
446 internal override void AddToConstraintCollectionSetup(
447 ConstraintCollection collection)
449 if (collection.Table != Table)
450 throw new InvalidConstraintException("This constraint cannot be added since ForeignKey doesn't belong to table " + RelatedTable.TableName + ".");
452 //run Ctor rules again
453 _validateColumns(_parentColumns, _childColumns);
455 //we must have a unique constraint on the parent
456 _ensureUniqueConstraintExists(collection, _parentColumns);
458 if ( (Table.DataSet != null && Table.DataSet.EnforceConstraints)
459 || (Table.DataSet == null && Table.EnforceConstraints)) {
460 if (IsConstraintViolated())
461 throw new ArgumentException("This constraint cannot be enabled as not all values have corresponding parent values.");
463 //FIXME : if this fails and we created a unique constraint
464 //we should probably roll it back
465 // and remove index form Table
468 internal override void RemoveFromConstraintCollectionCleanup(
469 ConstraintCollection collection)
471 _parentUniqueConstraint.ChildConstraint = null;
475 internal override bool IsConstraintViolated()
477 if (Table.DataSet == null || RelatedTable.DataSet == null)
480 bool hasErrors = false;
481 foreach (DataRow row in Table.Rows) {
482 if (row.RowState == DataRowState.Deleted)
485 // we check if all values in _childColumns place are nulls.
487 if (row.IsNullColumns(_childColumns))
490 // check whenever there is (at least one) parent row in RelatedTable
491 if(!RelatedTable.RowsExist(_parentColumns,_childColumns,row)) {
492 // if no parent row exists - constraint is violated
494 string[] values = new string[_childColumns.Length];
495 for (int i = 0; i < _childColumns.Length; i++){
496 DataColumn col = _childColumns[i];
497 values[i] = row[col].ToString();
500 row.RowError = String.Format("ForeignKeyConstraint {0} requires the child key values ({1}) to exist in the parent table.",
501 ConstraintName, String.Join(",", values));
506 //throw new ConstraintException("Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints.");
512 internal override void AssertConstraint(DataRow row)
514 // first we check if all values in _childColumns place are nulls.
516 if (row.IsNullColumns(_childColumns))
519 // check whenever there is (at least one) parent row in RelatedTable
520 if(!RelatedTable.RowsExist(_parentColumns,_childColumns,row)) {
521 // if no parent row exists - constraint is violated
522 throw new InvalidConstraintException(GetErrorMessage(row));
526 internal override bool IsColumnContained(DataColumn column)
528 for (int i = 0; i < _parentColumns.Length; i++)
529 if (column == _parentColumns[i])
532 for (int i = 0; i < _childColumns.Length; i++)
533 if (column == _childColumns[i])
539 internal override bool CanRemoveFromCollection(ConstraintCollection col, bool shouldThrow){
543 private string GetErrorMessage(DataRow row)
545 System.Text.StringBuilder sb = new System.Text.StringBuilder();
546 for (int i = 0; i < _childColumns.Length; i++) {
547 sb.Append(row[_childColumns[0]].ToString());
548 if (i != _childColumns.Length - 1) {
552 string valStr = sb.ToString();
553 return "ForeignKeyConstraint " + ConstraintName + " requires the child key values (" + valStr + ") to exist in the parent table.";
556 #endregion // Methods