ForeignKeyConstraint.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. //
  2. // System.Data.ForeignKeyConstraint.cs
  3. //
  4. // Author:
  5. // Franklin Wise <[email protected]>
  6. // Daniel Morgan <[email protected]>
  7. //
  8. // (C) 2002 Franklin Wise
  9. // (C) 2002 Daniel Morgan
  10. //
  11. using System;
  12. using System.Collections;
  13. using System.ComponentModel;
  14. using System.Runtime.InteropServices;
  15. namespace System.Data {
  16. [Editor]
  17. [DefaultProperty ("ConstraintName")]
  18. [Serializable]
  19. public class ForeignKeyConstraint : Constraint
  20. {
  21. private UniqueConstraint _parentUniqueConstraint;
  22. private DataColumn [] _parentColumns;
  23. private DataColumn [] _childColumns;
  24. private Rule _deleteRule = Rule.Cascade;
  25. private Rule _updateRule = Rule.Cascade;
  26. private AcceptRejectRule _acceptRejectRule = AcceptRejectRule.None;
  27. #region Constructors
  28. public ForeignKeyConstraint(DataColumn parentColumn, DataColumn childColumn)
  29. {
  30. if (null == parentColumn || null == childColumn) {
  31. throw new ArgumentNullException("Neither parentColumn or" +
  32. " childColumn can be null.");
  33. }
  34. _foreignKeyConstraint(null, new DataColumn[] {parentColumn},
  35. new DataColumn[] {childColumn});
  36. }
  37. public ForeignKeyConstraint(DataColumn[] parentColumns, DataColumn[] childColumns)
  38. {
  39. _foreignKeyConstraint(null, parentColumns, childColumns);
  40. }
  41. public ForeignKeyConstraint(string constraintName, DataColumn parentColumn, DataColumn childColumn)
  42. {
  43. if (null == parentColumn || null == childColumn) {
  44. throw new ArgumentNullException("Neither parentColumn or" +
  45. " childColumn can be null.");
  46. }
  47. _foreignKeyConstraint(constraintName, new DataColumn[] {parentColumn},
  48. new DataColumn[] {childColumn});
  49. }
  50. public ForeignKeyConstraint(string constraintName, DataColumn[] parentColumns, DataColumn[] childColumns)
  51. {
  52. _foreignKeyConstraint(constraintName, parentColumns, childColumns);
  53. }
  54. //special case
  55. [MonoTODO]
  56. [Browsable (false)]
  57. public ForeignKeyConstraint(string constraintName, string parentTableName, string[] parentColumnNames, string[] childColumnNames, AcceptRejectRule acceptRejectRule, Rule deleteRule, Rule updateRule)
  58. {
  59. }
  60. private void _foreignKeyConstraint(string constraintName, DataColumn[] parentColumns,
  61. DataColumn[] childColumns)
  62. {
  63. //Validate
  64. _validateColumns(parentColumns, childColumns);
  65. //Set Constraint Name
  66. base.ConstraintName = constraintName;
  67. //Keep reference to columns
  68. _parentColumns = parentColumns;
  69. _childColumns = childColumns;
  70. }
  71. #endregion // Constructors
  72. #region Helpers
  73. private void _validateColumns(DataColumn[] parentColumns, DataColumn[] childColumns)
  74. {
  75. //not null
  76. if (null == parentColumns || null == childColumns)
  77. throw new ArgumentNullException();
  78. //at least one element in each array
  79. if (parentColumns.Length < 1 || childColumns.Length < 1)
  80. throw new ArgumentException("Neither ParentColumns or ChildColumns can't be" +
  81. " zero length.");
  82. //same size arrays
  83. if (parentColumns.Length != childColumns.Length)
  84. throw new ArgumentException("Parent columns and child columns must be the same length.");
  85. DataTable ptable = parentColumns[0].Table;
  86. DataTable ctable = childColumns[0].Table;
  87. foreach (DataColumn pc in parentColumns)
  88. {
  89. //not null check
  90. if (null == pc.Table)
  91. {
  92. throw new ArgumentException("All columns must belong to a table." +
  93. " ColumnName: " + pc.ColumnName + " does not belong to a table.");
  94. }
  95. //All columns must belong to the same table
  96. if (ptable != pc.Table)
  97. throw new InvalidConstraintException("Parent columns must all belong to the same table.");
  98. foreach (DataColumn cc in childColumns)
  99. {
  100. //not null
  101. if (null == pc.Table)
  102. {
  103. throw new ArgumentException("All columns must belong to a table." +
  104. " ColumnName: " + pc.ColumnName + " does not belong to a table.");
  105. }
  106. //All columns must belong to the same table.
  107. if (ctable != cc.Table)
  108. throw new InvalidConstraintException("Child columns must all belong to the same table.");
  109. //Can't be the same column
  110. if (pc == cc)
  111. throw new InvalidOperationException("Parent and child columns can't be the same column.");
  112. foreach (DataColumn c2 in childColumns) {
  113. if (!Object.ReferenceEquals (c2.Table, cc.Table))
  114. throw new InvalidConstraintException ("Cannot create a Key from Columns thath belong to different tables.");
  115. }
  116. if (! pc.DataType.Equals(cc.DataType))
  117. {
  118. //LAMESPEC: spec says throw InvalidConstraintException
  119. // implementation throws InvalidOperationException
  120. throw new InvalidConstraintException("Parent column is not type compatible with it's child"
  121. + " column.");
  122. }
  123. }
  124. }
  125. //Same dataset. If both are null it's ok
  126. if (ptable.DataSet != ctable.DataSet)
  127. {
  128. //LAMESPEC: spec says InvalidConstraintExceptoin
  129. // impl does InvalidOperationException
  130. throw new InvalidOperationException("Parent column and child column must belong to" +
  131. " tables that belong to the same DataSet.");
  132. }
  133. }
  134. private void _validateRemoveParentConstraint(ConstraintCollection sender,
  135. Constraint constraint, ref bool cancel, ref string failReason)
  136. {
  137. //if we hold a reference to the parent then cancel it
  138. if (constraint == _parentUniqueConstraint)
  139. {
  140. cancel = true;
  141. failReason = "Cannot remove UniqueConstraint because the"
  142. + " ForeignKeyConstraint " + this.ConstraintName + " exists.";
  143. }
  144. }
  145. //Checks to see if a related unique constraint exists
  146. //if it doesn't then a unique constraint is created.
  147. //if a unique constraint can't be created an exception will be thrown
  148. private void _ensureUniqueConstraintExists(ConstraintCollection collection,
  149. DataColumn [] parentColumns)
  150. {
  151. //not null
  152. if (null == parentColumns) throw new ArgumentNullException(
  153. "ParentColumns can't be null");
  154. UniqueConstraint uc = null;
  155. //see if unique constraint already exists
  156. //if not create unique constraint
  157. if(parentColumns[0] != null)
  158. uc = UniqueConstraint.GetUniqueConstraintForColumnSet(parentColumns[0].Table.Constraints, parentColumns);
  159. if (null == uc) {
  160. uc = new UniqueConstraint(parentColumns, false); //could throw
  161. parentColumns [0].Table.Constraints.Add (uc);
  162. }
  163. //keep reference
  164. _parentUniqueConstraint = uc;
  165. //parentColumns [0].Table.Constraints.Add (uc);
  166. //if this unique constraint is attempted to be removed before us
  167. //we can fail the validation
  168. //collection.ValidateRemoveConstraint += new DelegateValidateRemoveConstraint(
  169. // _validateRemoveParentConstraint);
  170. parentColumns [0].Table.Constraints.ValidateRemoveConstraint += new DelegateValidateRemoveConstraint(
  171. _validateRemoveParentConstraint);
  172. }
  173. #endregion //Helpers
  174. #region Properties
  175. [DataCategory ("Data")]
  176. [DataSysDescription ("For accept and reject changes, indicates what kind of cascading should take place across this relation.")]
  177. [DefaultValue (AcceptRejectRule.None)]
  178. public virtual AcceptRejectRule AcceptRejectRule {
  179. get { return _acceptRejectRule; }
  180. set { _acceptRejectRule = value; }
  181. }
  182. [DataCategory ("Data")]
  183. [DataSysDescription ("Indicates the child columns of this constraint.")]
  184. [ReadOnly (true)]
  185. public virtual DataColumn[] Columns {
  186. get { return _childColumns; }
  187. }
  188. [DataCategory ("Data")]
  189. [DataSysDescription ("For deletions, indicates what kind of cascading should take place across this relation.")]
  190. [DefaultValue (Rule.Cascade)]
  191. public virtual Rule DeleteRule {
  192. get { return _deleteRule; }
  193. set { _deleteRule = value; }
  194. }
  195. [DataCategory ("Data")]
  196. [DataSysDescription ("For updates, indicates what kind of cascading should take place across this relation.")]
  197. [DefaultValue (Rule.Cascade)]
  198. public virtual Rule UpdateRule {
  199. get { return _updateRule; }
  200. set { _updateRule = value; }
  201. }
  202. [DataCategory ("Data")]
  203. [DataSysDescription ("Indicates the parent columns of this constraint.")]
  204. [ReadOnly (true)]
  205. public virtual DataColumn[] RelatedColumns {
  206. get { return _parentColumns; }
  207. }
  208. [DataCategory ("Data")]
  209. [DataSysDescription ("Indicates the child table of this constraint.")]
  210. [ReadOnly (true)]
  211. public virtual DataTable RelatedTable {
  212. get {
  213. if (_parentColumns != null)
  214. if (_parentColumns.Length > 0)
  215. return _parentColumns[0].Table;
  216. return null;
  217. }
  218. }
  219. [DataCategory ("Data")]
  220. [DataSysDescription ("Indicates the table of this constraint.")]
  221. [ReadOnly (true)]
  222. public override DataTable Table {
  223. get {
  224. if (_childColumns != null)
  225. if (_childColumns.Length > 0)
  226. return _childColumns[0].Table;
  227. return null;
  228. }
  229. }
  230. #endregion // Properties
  231. #region Methods
  232. public override bool Equals(object key)
  233. {
  234. ForeignKeyConstraint fkc = key as ForeignKeyConstraint;
  235. if (null == fkc) return false;
  236. //if the fk constrains the same columns then they are equal
  237. if (! DataColumn.AreColumnSetsTheSame( this.RelatedColumns, fkc.RelatedColumns))
  238. return false;
  239. if (! DataColumn.AreColumnSetsTheSame( this.Columns, fkc.Columns) )
  240. return false;
  241. return true;
  242. }
  243. public override int GetHashCode()
  244. {
  245. //initialize hash1 and hash2 with default hashes
  246. //any two DIFFERENT numbers will do here
  247. int hash1 = 32, hash2 = 88;
  248. int i;
  249. //derive the hash code from the columns that way
  250. //Equals and GetHashCode return Equal objects to be the
  251. //same
  252. //Get the first parent column hash
  253. if (this.Columns.Length > 0)
  254. hash1 ^= this.Columns[0].GetHashCode();
  255. //get the rest of the parent column hashes if there any
  256. for (i = 1; i < this.Columns.Length; i++)
  257. {
  258. hash1 ^= this.Columns[1].GetHashCode();
  259. }
  260. //Get the child column hash
  261. if (this.RelatedColumns.Length > 0)
  262. hash2 ^= this.Columns[0].GetHashCode();
  263. for (i = 1; i < this.RelatedColumns.Length; i++)
  264. {
  265. hash2 ^= this.RelatedColumns[1].GetHashCode();
  266. }
  267. //combine the two hashes
  268. return hash1 ^ hash2;
  269. }
  270. internal override void AddToConstraintCollectionSetup(
  271. ConstraintCollection collection)
  272. {
  273. //run Ctor rules again
  274. _validateColumns(_parentColumns, _childColumns);
  275. //we must have a unique constraint on the parent
  276. _ensureUniqueConstraintExists(collection, _parentColumns);
  277. //Make sure we can create this thing
  278. AssertConstraint(); //TODO:if this fails and we created a unique constraint
  279. //we should probably roll it back
  280. if (collection.Table != Table)
  281. throw new InvalidConstraintException("This constraint cannot be added since ForeignKey doesn't belong to table " + RelatedTable.TableName + ".");
  282. }
  283. [MonoTODO]
  284. internal override void RemoveFromConstraintCollectionCleanup(
  285. ConstraintCollection collection)
  286. {
  287. return; //no rules yet
  288. }
  289. [MonoTODO]
  290. internal override void AssertConstraint()
  291. {
  292. //Constraint only works if both tables are part of the same dataset
  293. //if DataSet ...
  294. if (Table == null || RelatedTable == null) return; //TODO: Do we want this
  295. if (Table.DataSet == null || RelatedTable.DataSet == null) return; //
  296. //TODO:
  297. //check for orphaned children
  298. //check for...
  299. }
  300. [MonoTODO]
  301. internal override void AssertConstraint(DataRow row)
  302. {
  303. // first we check if all values in _childColumns place are DBNull.
  304. // if yes we return.
  305. bool allNull = true;
  306. for (int i = 0; i < _childColumns.Length; i++)
  307. {
  308. if (row[_childColumns[i]] != DBNull.Value)
  309. {
  310. allNull = false;
  311. break;
  312. }
  313. }
  314. if (allNull)
  315. return;
  316. // check that there is a parent for this row.
  317. foreach (DataRow parentRow in this.RelatedTable.Rows)
  318. {
  319. if (parentRow.RowState != DataRowState.Deleted)
  320. {
  321. bool match = true;
  322. // check if the values in the constraints columns are equal
  323. for (int i = 0; i < _parentColumns.Length; i++)
  324. {
  325. if (!row[_childColumns[i]].Equals(parentRow[_parentColumns[i]]))
  326. {
  327. match = false;
  328. break;
  329. }
  330. }
  331. if (match) // there is a parent row for this row.
  332. return;
  333. }
  334. }
  335. string values = "";
  336. for (int i = 0; i < _childColumns.Length; i++)
  337. {
  338. values += row[_childColumns[0]].ToString();
  339. if (i != _childColumns.Length - 1)
  340. values += ",";
  341. }
  342. throw new InvalidConstraintException("ForeignKeyConstraint " + ConstraintName + " requires the child key values (" + values + ") to exist in the parent table.");
  343. }
  344. #endregion // Methods
  345. }
  346. }