DataRelation.cs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802
  1. //------------------------------------------------------------------------------
  2. // <copyright file="DataRelation.cs" company="Microsoft">
  3. // Copyright (c) Microsoft Corporation. All rights reserved.
  4. // </copyright>
  5. // <owner current="true" primary="true">[....]</owner>
  6. // <owner current="true" primary="false">[....]</owner>
  7. // <owner current="false" primary="false">[....]</owner>
  8. //------------------------------------------------------------------------------
  9. /*****************************************************************************************************
  10. Rules for Multiple Nested Parent, enforce following constraints
  11. 1) At all times, only 1(ONE) FK can be NON-Null in a row.
  12. 2) NULL FK values are not associated with PARENT(x), even if if PK is NULL in Parent
  13. 3) Enforce <rule 1> when
  14. a) Any FK value is changed
  15. b) A relation created that result in Multiple Nested Child
  16. WriteXml
  17. 1) WriteXml will throw if <rule 1> is violated
  18. 2) if NON-Null FK has parentRow (boolean check) print as Nested, else it will get written as normal row
  19. additional notes:
  20. We decided to enforce the rule 1 just if Xml being persisted
  21. ******************************************************************************************************/
  22. namespace System.Data {
  23. using System;
  24. using System.ComponentModel;
  25. using System.Diagnostics;
  26. using System.Globalization;
  27. using System.Data.Common;
  28. using System.Collections.Generic;
  29. /// <devdoc>
  30. /// <para>
  31. /// Represents a parent/child relationship between two tables.
  32. /// </para>
  33. /// </devdoc>
  34. [
  35. DefaultProperty("RelationName"),
  36. Editor("Microsoft.VSDesigner.Data.Design.DataRelationEditor, " + AssemblyRef.MicrosoftVSDesigner, "System.Drawing.Design.UITypeEditor, " + AssemblyRef.SystemDrawing),
  37. TypeConverter(typeof(RelationshipConverter)),
  38. ]
  39. public class DataRelation {
  40. // properties
  41. private DataSet dataSet = null;
  42. internal PropertyCollection extendedProperties = null;
  43. internal string relationName = "";
  44. // events
  45. private PropertyChangedEventHandler onPropertyChangingDelegate = null;
  46. // state
  47. private DataKey childKey;
  48. private DataKey parentKey;
  49. private UniqueConstraint parentKeyConstraint = null;
  50. private ForeignKeyConstraint childKeyConstraint = null;
  51. // Design time serialization
  52. internal string[] parentColumnNames = null;
  53. internal string[] childColumnNames = null;
  54. internal string parentTableName = null;
  55. internal string childTableName = null;
  56. internal string parentTableNamespace= null;
  57. internal string childTableNamespace = null;
  58. /// <devdoc>
  59. /// this stores whether the child element appears beneath the parent in the XML persised files.
  60. /// </devdoc>
  61. internal bool nested = false;
  62. /// <devdoc>
  63. /// this stores whether the the relationship should make sure that KeyConstraints and ForeignKeyConstraints
  64. /// exist when added to the ConstraintsCollections of the table.
  65. /// </devdoc>
  66. internal bool createConstraints;
  67. private bool _checkMultipleNested = true;
  68. private static int _objectTypeCount; // Bid counter
  69. private readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
  70. /// <devdoc>
  71. /// <para>
  72. /// Initializes a new instance of the <see cref='System.Data.DataRelation'/> class using the specified name,
  73. /// parent, and child columns.
  74. /// </para>
  75. /// </devdoc>
  76. public DataRelation(string relationName, DataColumn parentColumn, DataColumn childColumn)
  77. : this(relationName, parentColumn, childColumn, true) {
  78. }
  79. /// <devdoc>
  80. /// <para>
  81. /// Initializes a new instance of the <see cref='System.Data.DataRelation'/> class using the specified name, parent, and child columns, and
  82. /// value to create constraints.
  83. /// </para>
  84. /// </devdoc>
  85. public DataRelation(string relationName, DataColumn parentColumn, DataColumn childColumn, bool createConstraints) {
  86. Bid.Trace("<ds.DataRelation.DataRelation|API> %d#, relationName='%ls', parentColumn=%d, childColumn=%d, createConstraints=%d{bool}\n",
  87. ObjectID, relationName, (parentColumn != null) ? parentColumn.ObjectID : 0, (childColumn != null) ? childColumn.ObjectID : 0,
  88. createConstraints);
  89. DataColumn[] parentColumns = new DataColumn[1];
  90. parentColumns[0] = parentColumn;
  91. DataColumn[] childColumns = new DataColumn[1];
  92. childColumns[0] = childColumn;
  93. Create(relationName, parentColumns, childColumns, createConstraints);
  94. }
  95. /// <devdoc>
  96. /// <para>
  97. /// Initializes a new instance of the <see cref='System.Data.DataRelation'/> class using the specified name
  98. /// and matched arrays of parent and child columns.
  99. /// </para>
  100. /// </devdoc>
  101. public DataRelation(string relationName, DataColumn[] parentColumns, DataColumn[] childColumns)
  102. : this(relationName, parentColumns, childColumns, true) {
  103. }
  104. /// <devdoc>
  105. /// <para>
  106. /// Initializes a new instance of the <see cref='System.Data.DataRelation'/> class using the specified name, matched arrays of parent
  107. /// and child columns, and value to create constraints.
  108. /// </para>
  109. /// </devdoc>
  110. public DataRelation(string relationName, DataColumn[] parentColumns, DataColumn[] childColumns, bool createConstraints) {
  111. Create(relationName, parentColumns, childColumns, createConstraints);
  112. }
  113. // Design time constructor
  114. /// <devdoc>
  115. /// <para>[To be supplied.]</para>
  116. /// </devdoc>
  117. [Browsable(false)]
  118. public DataRelation(string relationName, string parentTableName, string childTableName, string[] parentColumnNames, string[] childColumnNames, bool nested) {
  119. this.relationName = relationName;
  120. this.parentColumnNames = parentColumnNames;
  121. this.childColumnNames = childColumnNames;
  122. this.parentTableName = parentTableName;
  123. this.childTableName = childTableName;
  124. this.nested = nested;
  125. // DataRelation(relationName, parentTableName, null, childTableName, null, parentColumnNames, childColumnNames, nested)
  126. }
  127. [Browsable(false)]
  128. // Design time constructor
  129. public DataRelation(string relationName, string parentTableName, string parentTableNamespace, string childTableName, string childTableNamespace, string[] parentColumnNames, string[] childColumnNames, bool nested) {
  130. this.relationName = relationName;
  131. this.parentColumnNames = parentColumnNames;
  132. this.childColumnNames = childColumnNames;
  133. this.parentTableName = parentTableName;
  134. this.childTableName = childTableName;
  135. this.parentTableNamespace = parentTableNamespace;
  136. this.childTableNamespace = childTableNamespace;
  137. this.nested = nested;
  138. }
  139. /// <devdoc>
  140. /// <para>
  141. /// Gets the child columns of this relation.
  142. /// </para>
  143. /// </devdoc>
  144. [
  145. ResCategoryAttribute(Res.DataCategory_Data),
  146. ResDescriptionAttribute(Res.DataRelationChildColumnsDescr)
  147. ]
  148. public virtual DataColumn[] ChildColumns {
  149. get {
  150. CheckStateForProperty();
  151. return childKey.ToArray();
  152. }
  153. }
  154. internal DataColumn[] ChildColumnsReference {
  155. get {
  156. CheckStateForProperty();
  157. return childKey.ColumnsReference;
  158. }
  159. }
  160. /// <devdoc>
  161. /// The internal Key object for the child table.
  162. /// </devdoc>
  163. internal DataKey ChildKey {
  164. get {
  165. CheckStateForProperty();
  166. return childKey;
  167. }
  168. }
  169. /// <devdoc>
  170. /// <para>
  171. /// Gets the child table of this relation.
  172. /// </para>
  173. /// </devdoc>
  174. public virtual DataTable ChildTable {
  175. get {
  176. CheckStateForProperty();
  177. return childKey.Table;
  178. }
  179. }
  180. /// <devdoc>
  181. /// <para>
  182. /// Gets the <see cref='System.Data.DataSet'/> to which the relations' collection belongs to.
  183. /// </para>
  184. /// </devdoc>
  185. [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), Browsable(false)]
  186. public virtual DataSet DataSet {
  187. get {
  188. CheckStateForProperty();
  189. return dataSet;
  190. }
  191. }
  192. internal string[] ParentColumnNames {
  193. get {
  194. return parentKey.GetColumnNames();
  195. }
  196. }
  197. internal string[] ChildColumnNames {
  198. get {
  199. return childKey.GetColumnNames();
  200. }
  201. }
  202. private static bool IsKeyNull(object[] values) {
  203. for (int i = 0; i < values.Length; i++) {
  204. if (!DataStorage.IsObjectNull(values[i]))
  205. return false;
  206. }
  207. return true;
  208. }
  209. /// <devdoc>
  210. /// Gets the child rows for the parent row across the relation using the version given
  211. /// </devdoc>
  212. internal static DataRow[] GetChildRows(DataKey parentKey, DataKey childKey, DataRow parentRow, DataRowVersion version) {
  213. object[] values = parentRow.GetKeyValues(parentKey, version);
  214. if (IsKeyNull(values)) {
  215. return childKey.Table.NewRowArray(0);
  216. }
  217. Index index = childKey.GetSortIndex((version == DataRowVersion.Original) ? DataViewRowState.OriginalRows : DataViewRowState.CurrentRows);
  218. return index.GetRows(values);
  219. }
  220. /// <devdoc>
  221. /// Gets the parent rows for the given child row across the relation using the version given
  222. /// </devdoc>
  223. internal static DataRow[] GetParentRows(DataKey parentKey, DataKey childKey, DataRow childRow, DataRowVersion version) {
  224. object[] values = childRow.GetKeyValues(childKey, version);
  225. if (IsKeyNull(values)) {
  226. return parentKey.Table.NewRowArray(0);
  227. }
  228. Index index = parentKey.GetSortIndex((version == DataRowVersion.Original) ? DataViewRowState.OriginalRows : DataViewRowState.CurrentRows);
  229. return index.GetRows(values);
  230. }
  231. internal static DataRow GetParentRow(DataKey parentKey, DataKey childKey, DataRow childRow, DataRowVersion version) {
  232. if (!childRow.HasVersion((version == DataRowVersion.Original) ? DataRowVersion.Original : DataRowVersion.Current))
  233. if (childRow.tempRecord == -1)
  234. return null;
  235. object[] values = childRow.GetKeyValues(childKey, version);
  236. if (IsKeyNull(values)) {
  237. return null;
  238. }
  239. Index index = parentKey.GetSortIndex((version == DataRowVersion.Original) ? DataViewRowState.OriginalRows : DataViewRowState.CurrentRows);
  240. Range range = index.FindRecords(values);
  241. if (range.IsNull) {
  242. return null;
  243. }
  244. if (range.Count > 1) {
  245. throw ExceptionBuilder.MultipleParents();
  246. }
  247. return parentKey.Table.recordManager[index.GetRecord(range.Min)];
  248. }
  249. /// <devdoc>
  250. /// Internally sets the DataSet pointer.
  251. /// </devdoc>
  252. internal void SetDataSet(DataSet dataSet) {
  253. if (this.dataSet != dataSet) {
  254. this.dataSet = dataSet;
  255. }
  256. }
  257. internal void SetParentRowRecords(DataRow childRow, DataRow parentRow) {
  258. object[] parentKeyValues = parentRow.GetKeyValues(ParentKey);
  259. if (childRow.tempRecord != -1) {
  260. ChildTable.recordManager.SetKeyValues(childRow.tempRecord, ChildKey, parentKeyValues);
  261. }
  262. if (childRow.newRecord != -1) {
  263. ChildTable.recordManager.SetKeyValues(childRow.newRecord, ChildKey, parentKeyValues);
  264. }
  265. if (childRow.oldRecord != -1) {
  266. ChildTable.recordManager.SetKeyValues(childRow.oldRecord, ChildKey, parentKeyValues);
  267. }
  268. }
  269. /// <devdoc>
  270. /// <para>
  271. /// Gets the parent columns of this relation.
  272. /// </para>
  273. /// </devdoc>
  274. [
  275. ResCategoryAttribute(Res.DataCategory_Data),
  276. ResDescriptionAttribute(Res.DataRelationParentColumnsDescr)
  277. ]
  278. public virtual DataColumn[] ParentColumns {
  279. get {
  280. CheckStateForProperty();
  281. return parentKey.ToArray();
  282. }
  283. }
  284. internal DataColumn[] ParentColumnsReference {
  285. get {
  286. return parentKey.ColumnsReference;
  287. }
  288. }
  289. /// <devdoc>
  290. /// The internal constraint object for the parent table.
  291. /// </devdoc>
  292. internal DataKey ParentKey {
  293. get {
  294. CheckStateForProperty();
  295. return parentKey;
  296. }
  297. }
  298. /// <devdoc>
  299. /// <para>
  300. /// Gets the parent table of this relation.
  301. /// </para>
  302. /// </devdoc>
  303. public virtual DataTable ParentTable {
  304. get {
  305. CheckStateForProperty();
  306. return parentKey.Table;
  307. }
  308. }
  309. /// <devdoc>
  310. /// <para>
  311. /// Gets or sets
  312. /// the name used to look up this relation in the parent
  313. /// data set's <see cref='System.Data.DataRelationCollection'/>.
  314. /// </para>
  315. /// </devdoc>
  316. [
  317. ResCategoryAttribute(Res.DataCategory_Data),
  318. DefaultValue(""),
  319. ResDescriptionAttribute(Res.DataRelationRelationNameDescr)
  320. ]
  321. public virtual string RelationName {
  322. get {
  323. CheckStateForProperty();
  324. return relationName;
  325. }
  326. set {
  327. IntPtr hscp;
  328. Bid.ScopeEnter(out hscp, "<ds.DataRelation.set_RelationName|API> %d#, '%ls'\n", ObjectID, value);
  329. try {
  330. if (value == null)
  331. value = "";
  332. CultureInfo locale = (dataSet != null ? dataSet.Locale : CultureInfo.CurrentCulture);
  333. if (String.Compare(relationName, value, true, locale) != 0) {
  334. if (dataSet != null) {
  335. if (value.Length == 0)
  336. throw ExceptionBuilder.NoRelationName();
  337. dataSet.Relations.RegisterName(value);
  338. if (relationName.Length != 0)
  339. dataSet.Relations.UnregisterName(relationName);
  340. }
  341. this.relationName = value;
  342. ((DataRelationCollection.DataTableRelationCollection)(ParentTable.ChildRelations)).OnRelationPropertyChanged(new CollectionChangeEventArgs(CollectionChangeAction.Refresh, this));
  343. ((DataRelationCollection.DataTableRelationCollection)(ChildTable.ParentRelations)).OnRelationPropertyChanged(new CollectionChangeEventArgs(CollectionChangeAction.Refresh, this));
  344. }
  345. else if (String.Compare(relationName, value, false, locale) != 0) {
  346. relationName = value;
  347. ((DataRelationCollection.DataTableRelationCollection)(ParentTable.ChildRelations)).OnRelationPropertyChanged(new CollectionChangeEventArgs(CollectionChangeAction.Refresh, this));
  348. ((DataRelationCollection.DataTableRelationCollection)(ChildTable.ParentRelations)).OnRelationPropertyChanged(new CollectionChangeEventArgs(CollectionChangeAction.Refresh, this));
  349. }
  350. }
  351. finally{
  352. Bid.ScopeLeave(ref hscp);
  353. }
  354. }
  355. }
  356. internal void CheckNamespaceValidityForNestedRelations(string ns) {
  357. foreach(DataRelation rel in ChildTable.ParentRelations) {
  358. if (rel == this || rel.Nested) {
  359. if (rel.ParentTable.Namespace != ns) {
  360. throw ExceptionBuilder.InValidNestedRelation(ChildTable.TableName);
  361. }
  362. }
  363. }
  364. }
  365. internal void CheckNestedRelations() {
  366. Bid.Trace("<ds.DataRelation.CheckNestedRelations|INFO> %d#\n", ObjectID);
  367. Debug.Assert(DataSet == null || ! nested, "this relation supposed to be not in dataset or not nested");
  368. // 1. There is no other relation (R) that has this.ChildTable as R.ChildTable
  369. // This is not valid for Whidbey anymore so the code has been removed
  370. // 2. There is no loop in nested relations
  371. #if DEBUG
  372. int numTables = ParentTable.DataSet.Tables.Count;
  373. #endif
  374. DataTable dt = ParentTable;
  375. if (ChildTable == ParentTable){
  376. if (String.Compare(ChildTable.TableName, ChildTable.DataSet.DataSetName, true, ChildTable.DataSet.Locale) == 0)
  377. throw ExceptionBuilder.SelfnestedDatasetConflictingName(ChildTable.TableName);
  378. return; //allow self join tables.
  379. }
  380. List<DataTable> list = new List<DataTable>();
  381. list.Add(ChildTable);
  382. // We have already checked for nested relaion UP
  383. for(int i = 0; i < list.Count; ++i) {
  384. DataRelation[] relations = list[i].NestedParentRelations;
  385. foreach(DataRelation rel in relations) {
  386. if (rel.ParentTable == ChildTable && rel.ChildTable != ChildTable) {
  387. throw ExceptionBuilder.LoopInNestedRelations(ChildTable.TableName);
  388. }
  389. if (!list.Contains (rel.ParentTable)) { // check for self nested
  390. list.Add(rel.ParentTable);
  391. }
  392. }
  393. }
  394. }
  395. /********************
  396. The Namespace of a table nested inside multiple parents can be
  397. 1. Explicitly specified
  398. 2. Inherited from Parent Table
  399. 3. Empty (Form = unqualified case)
  400. However, Schema does not allow (3) to be a global element and multiple nested child has to be a global element.
  401. Therefore we'll reduce case (3) to (2) if all parents have same namespace else throw.
  402. ********************/
  403. /// <devdoc>
  404. /// <para>
  405. /// Gets or sets a value indicating whether relations are nested.
  406. /// </para>
  407. /// </devdoc>
  408. [
  409. ResCategoryAttribute(Res.DataCategory_Data),
  410. DefaultValue(false),
  411. ResDescriptionAttribute(Res.DataRelationNested)
  412. ]
  413. public virtual bool Nested {
  414. get {
  415. CheckStateForProperty();
  416. return nested;
  417. }
  418. set {
  419. IntPtr hscp;
  420. Bid.ScopeEnter(out hscp, "<ds.DataRelation.set_Nested|API> %d#, %d{bool}\n", ObjectID, value);
  421. try {
  422. if (nested != value) {
  423. if (dataSet != null) {
  424. if (value) {
  425. if (ChildTable.IsNamespaceInherited()) { // if not added to collection, don't do this check
  426. CheckNamespaceValidityForNestedRelations(ParentTable.Namespace);
  427. }
  428. Debug.Assert(ChildTable != null, "On a DataSet, but not on Table. Bad state");
  429. ForeignKeyConstraint constraint = ChildTable.Constraints.FindForeignKeyConstraint(ChildKey.ColumnsReference, ParentKey.ColumnsReference);
  430. if (constraint != null) {
  431. constraint.CheckConstraint();
  432. }
  433. ValidateMultipleNestedRelations();
  434. }
  435. }
  436. if (!value && (parentKey.ColumnsReference[0].ColumnMapping == MappingType.Hidden))
  437. throw ExceptionBuilder.RelationNestedReadOnly();
  438. if (value) {
  439. this.ParentTable.Columns.RegisterColumnName(this.ChildTable.TableName, null);
  440. } else {
  441. this.ParentTable.Columns.UnregisterName(this.ChildTable.TableName);
  442. }
  443. RaisePropertyChanging("Nested");
  444. if(value) {
  445. CheckNestedRelations();
  446. if (this.DataSet != null)
  447. if (ParentTable == ChildTable) {
  448. foreach(DataRow row in ChildTable.Rows)
  449. row.CheckForLoops(this);
  450. if (ChildTable.DataSet != null && ( String.Compare(ChildTable.TableName, ChildTable.DataSet.DataSetName, true, ChildTable.DataSet.Locale) == 0) )
  451. throw ExceptionBuilder.DatasetConflictingName(dataSet.DataSetName);
  452. ChildTable.fNestedInDataset = false;
  453. }
  454. else {
  455. foreach(DataRow row in ChildTable.Rows)
  456. row.GetParentRow(this);
  457. }
  458. this.ParentTable.ElementColumnCount++;
  459. }
  460. else {
  461. this.ParentTable.ElementColumnCount--;
  462. }
  463. this.nested = value;
  464. ChildTable.CacheNestedParent();
  465. if (value) {
  466. if (ADP.IsEmpty(ChildTable.Namespace) && ((ChildTable.NestedParentsCount > 1) ||
  467. ((ChildTable.NestedParentsCount > 0) && ! (ChildTable.DataSet.Relations.Contains(this.RelationName))))) {
  468. string parentNs = null;
  469. foreach(DataRelation rel in ChildTable.ParentRelations) {
  470. if (rel.Nested) {
  471. if (null == parentNs) {
  472. parentNs = rel.ParentTable.Namespace;
  473. }
  474. else {
  475. if (String.Compare(parentNs, rel.ParentTable.Namespace, StringComparison.Ordinal) != 0) {
  476. this.nested = false;
  477. throw ExceptionBuilder.InvalidParentNamespaceinNestedRelation(ChildTable.TableName);
  478. }
  479. }
  480. }
  481. }
  482. // if not already in memory , form == unqualified
  483. if (CheckMultipleNested && ChildTable.tableNamespace != null && ChildTable.tableNamespace.Length == 0) {
  484. throw ExceptionBuilder.TableCantBeNestedInTwoTables(ChildTable.TableName);
  485. }
  486. ChildTable.tableNamespace = null; // if we dont throw, then let it inherit the Namespace
  487. }
  488. }
  489. }
  490. }
  491. finally{
  492. Bid.ScopeLeave(ref hscp);
  493. }
  494. }
  495. }
  496. /// <devdoc>
  497. /// <para>
  498. /// Gets the constraint which ensures values in a column are unique.
  499. /// </para>
  500. /// </devdoc>
  501. public virtual UniqueConstraint ParentKeyConstraint {
  502. get {
  503. CheckStateForProperty();
  504. return parentKeyConstraint;
  505. }
  506. }
  507. internal void SetParentKeyConstraint(UniqueConstraint value) {
  508. Debug.Assert(parentKeyConstraint == null || value == null, "ParentKeyConstraint should not have been set already.");
  509. parentKeyConstraint = value;
  510. }
  511. /// <devdoc>
  512. /// <para>
  513. /// Gets the <see cref='System.Data.ForeignKeyConstraint'/> for the relation.
  514. /// </para>
  515. /// </devdoc>
  516. public virtual ForeignKeyConstraint ChildKeyConstraint {
  517. get {
  518. CheckStateForProperty();
  519. return childKeyConstraint;
  520. }
  521. }
  522. /// <devdoc>
  523. /// <para>Gets the collection of custom user information.</para>
  524. /// </devdoc>
  525. [
  526. ResCategoryAttribute(Res.DataCategory_Data),
  527. Browsable(false),
  528. ResDescriptionAttribute(Res.ExtendedPropertiesDescr)
  529. ]
  530. public PropertyCollection ExtendedProperties {
  531. get {
  532. if (extendedProperties == null) {
  533. extendedProperties = new PropertyCollection();
  534. }
  535. return extendedProperties;
  536. }
  537. }
  538. internal bool CheckMultipleNested {
  539. get {
  540. return _checkMultipleNested;
  541. }
  542. set {
  543. _checkMultipleNested = value;
  544. }
  545. }
  546. internal void SetChildKeyConstraint(ForeignKeyConstraint value) {
  547. Debug.Assert(childKeyConstraint == null || value == null, "ChildKeyConstraint should not have been set already.");
  548. childKeyConstraint = value;
  549. }
  550. internal event PropertyChangedEventHandler PropertyChanging {
  551. add {
  552. onPropertyChangingDelegate += value;
  553. }
  554. remove {
  555. onPropertyChangingDelegate -= value;
  556. }
  557. }
  558. // If we're not in a dataSet relations collection, we need to verify on every property get that we're
  559. // still a good relation object.
  560. internal void CheckState() {
  561. if (dataSet == null) {
  562. parentKey.CheckState();
  563. childKey.CheckState();
  564. if (parentKey.Table.DataSet != childKey.Table.DataSet) {
  565. throw ExceptionBuilder.RelationDataSetMismatch();
  566. }
  567. if (childKey.ColumnsEqual(parentKey)) {
  568. throw ExceptionBuilder.KeyColumnsIdentical();
  569. }
  570. for (int i = 0; i < parentKey.ColumnsReference.Length; i++) {
  571. if ((parentKey.ColumnsReference[i].DataType != childKey.ColumnsReference[i].DataType) ||
  572. ((parentKey.ColumnsReference[i].DataType == typeof(DateTime)) &&
  573. (parentKey.ColumnsReference[i].DateTimeMode != childKey.ColumnsReference[i].DateTimeMode) &&
  574. ((parentKey.ColumnsReference[i].DateTimeMode & childKey.ColumnsReference[i].DateTimeMode) != DataSetDateTime.Unspecified)))
  575. // alow unspecified and unspecifiedlocal
  576. throw ExceptionBuilder.ColumnsTypeMismatch();
  577. }
  578. }
  579. }
  580. /// <devdoc>
  581. /// <para>Checks to ensure the DataRelation is a valid object, even if it doesn't
  582. /// belong to a <see cref='System.Data.DataSet'/>.</para>
  583. /// </devdoc>
  584. protected void CheckStateForProperty() {
  585. try {
  586. CheckState();
  587. }
  588. catch (Exception e) {
  589. //
  590. if (ADP.IsCatchableExceptionType(e)) {
  591. throw ExceptionBuilder.BadObjectPropertyAccess(e.Message);
  592. }
  593. throw;
  594. }
  595. }
  596. private void Create(string relationName, DataColumn[] parentColumns, DataColumn[] childColumns, bool createConstraints) {
  597. IntPtr hscp;
  598. Bid.ScopeEnter(out hscp, "<ds.DataRelation.Create|INFO> %d#, relationName='%ls', createConstraints=%d{bool}\n",
  599. ObjectID, relationName, createConstraints);
  600. try {
  601. this.parentKey = new DataKey(parentColumns, true);
  602. this.childKey = new DataKey(childColumns, true);
  603. if (parentColumns.Length != childColumns.Length)
  604. throw ExceptionBuilder.KeyLengthMismatch();
  605. for(int i = 0; i < parentColumns.Length; i++){
  606. if ((parentColumns[i].Table.DataSet == null) || (childColumns[i].Table.DataSet == null))
  607. throw ExceptionBuilder.ParentOrChildColumnsDoNotHaveDataSet();
  608. }
  609. CheckState();
  610. this.relationName = (relationName == null ? "" : relationName);
  611. this.createConstraints = createConstraints;
  612. }
  613. finally{
  614. Bid.ScopeLeave(ref hscp);
  615. }
  616. }
  617. internal DataRelation Clone(DataSet destination) {
  618. Bid.Trace("<ds.DataRelation.Clone|INFO> %d#, destination=%d\n", ObjectID, (destination != null) ? destination.ObjectID : 0);
  619. DataTable parent = destination.Tables[ParentTable.TableName, ParentTable.Namespace];
  620. DataTable child = destination.Tables[ChildTable.TableName, ChildTable.Namespace];
  621. int keyLength = parentKey.ColumnsReference.Length;
  622. DataColumn[] parentColumns = new DataColumn[keyLength];
  623. DataColumn[] childColumns = new DataColumn[keyLength];
  624. for (int i = 0; i < keyLength; i++) {
  625. parentColumns[i] = parent.Columns[ParentKey.ColumnsReference[i].ColumnName];
  626. childColumns[i] = child.Columns[ChildKey.ColumnsReference[i].ColumnName];
  627. }
  628. DataRelation clone = new DataRelation(relationName, parentColumns, childColumns, false);
  629. clone.CheckMultipleNested = false; // disable the check in clone as it is already created
  630. clone.Nested = this.Nested;
  631. clone.CheckMultipleNested = true; // enable the check
  632. // ...Extended Properties
  633. if (this.extendedProperties != null) {
  634. foreach(Object key in this.extendedProperties.Keys) {
  635. clone.ExtendedProperties[key]=this.extendedProperties[key];
  636. }
  637. }
  638. return clone;
  639. }
  640. protected internal void OnPropertyChanging(PropertyChangedEventArgs pcevent) {
  641. if (onPropertyChangingDelegate != null) {
  642. Bid.Trace("<ds.DataRelation.OnPropertyChanging|INFO> %d#\n", ObjectID);
  643. onPropertyChangingDelegate(this, pcevent);
  644. }
  645. }
  646. protected internal void RaisePropertyChanging(string name) {
  647. OnPropertyChanging(new PropertyChangedEventArgs(name));
  648. }
  649. /// <devdoc>
  650. /// </devdoc>
  651. public override string ToString() {
  652. return RelationName;
  653. }
  654. internal void ValidateMultipleNestedRelations() {
  655. // find all nested relations that this child table has
  656. // if this relation is the only relation it has, then fine,
  657. // otherwise check if all relations are created from XSD, without using Key/KeyRef
  658. // check all keys to see autogenerated
  659. if (!this.Nested || !CheckMultipleNested) // no need for this verification
  660. return;
  661. if (0 < ChildTable.NestedParentRelations.Length) {
  662. DataColumn[] childCols = ChildColumns;
  663. if (childCols.Length != 1 || !IsAutoGenerated(childCols[0])) {
  664. throw ExceptionBuilder.TableCantBeNestedInTwoTables(ChildTable.TableName);
  665. }
  666. if (!XmlTreeGen.AutoGenerated(this)) {
  667. throw ExceptionBuilder.TableCantBeNestedInTwoTables(ChildTable.TableName);
  668. }
  669. foreach (Constraint cs in ChildTable.Constraints) {
  670. if (cs is ForeignKeyConstraint) {
  671. ForeignKeyConstraint fk = (ForeignKeyConstraint) cs;
  672. if (!XmlTreeGen.AutoGenerated(fk, true)) {
  673. throw ExceptionBuilder.TableCantBeNestedInTwoTables(ChildTable.TableName);
  674. }
  675. }
  676. else {
  677. UniqueConstraint unique = (UniqueConstraint) cs;
  678. if (!XmlTreeGen.AutoGenerated(unique)) {
  679. throw ExceptionBuilder.TableCantBeNestedInTwoTables(ChildTable.TableName);
  680. }
  681. }
  682. }
  683. }
  684. }
  685. private bool IsAutoGenerated(DataColumn col) {
  686. if (col.ColumnMapping != MappingType.Hidden)
  687. return false;
  688. if (col.DataType != typeof(int))
  689. return false;
  690. string generatedname = col.Table.TableName+"_Id";
  691. if ((col.ColumnName == generatedname) || (col.ColumnName == generatedname+"_0"))
  692. return true;
  693. generatedname = this.ParentColumnsReference[0].Table.TableName+"_Id";
  694. if ((col.ColumnName == generatedname) || (col.ColumnName == generatedname+"_0"))
  695. return true;
  696. return false;
  697. }
  698. internal int ObjectID {
  699. get {
  700. return _objectID;
  701. }
  702. }
  703. }
  704. }