importing messaging-2008 branch to trunk [continued]
[mono.git] / mcs / class / System.Data / System.Data / DataRelation.cs
1 //
2 // System.Data.DataRelation.cs
3 //
4 // Author:
5 //   Daniel Morgan <danmorg@sc.rr.com>
6 //   Alan Tam Siu Lung <Tam@SiuLung.com>
7 //   Tim Coleman <tim@timcoleman.com>
8 //
9 // (C) 2002 Daniel Morgan
10 // (C) 2002 Ximian, Inc.
11 // Copyright (C) Tim Coleman, 2003
12 //
13
14 //
15 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
16 //
17 // Permission is hereby granted, free of charge, to any person obtaining
18 // a copy of this software and associated documentation files (the
19 // "Software"), to deal in the Software without restriction, including
20 // without limitation the rights to use, copy, modify, merge, publish,
21 // distribute, sublicense, and/or sell copies of the Software, and to
22 // permit persons to whom the Software is furnished to do so, subject to
23 // the following conditions:
24 //
25 // The above copyright notice and this permission notice shall be
26 // included in all copies or substantial portions of the Software.
27 //
28 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
29 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
30 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
31 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
32 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
33 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
34 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35 //
36
37 using System;
38 using System.ComponentModel;
39 using System.Runtime.Serialization;
40
41 namespace System.Data
42 {
43         /// <summary>
44         /// DataRelation is used for a parent/child relationship
45         /// between two DataTable objects
46         /// </summary>
47         [Editor ("Microsoft.VSDesigner.Data.Design.DataRelationEditor, " + Consts.AssemblyMicrosoft_VSDesigner,
48                  "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
49         [DefaultProperty ("RelationName")]
50 #if !NET_2_0
51         [Serializable]
52 #endif
53         [TypeConverterAttribute (typeof (RelationshipConverter))]
54         public class DataRelation {
55                 private DataSet dataSet;
56                 private string relationName;
57                 private UniqueConstraint parentKeyConstraint;
58                 private ForeignKeyConstraint childKeyConstraint;
59                 private DataColumn [] parentColumns;
60                 private DataColumn [] childColumns;
61                 private bool nested;
62                 internal bool createConstraints = true;
63                 private bool initFinished;
64                 private PropertyCollection extendedProperties;
65                 private PropertyChangedEventHandler onPropertyChangingDelegate;
66
67                 string _relationName;
68                 string _parentTableName;
69                 string _childTableName;
70                 string [] _parentColumnNames;
71                 string [] _childColumnNames;
72                 bool _nested;
73                 bool initInProgress = false;
74 #if NET_2_0
75                 string _parentTableNameSpace = String.Empty;
76                 string _childTableNameSpace = String.Empty;
77 #endif
78
79                 #region Constructors
80
81                 public DataRelation (string relationName, DataColumn parentColumn, DataColumn childColumn)
82                         : this (relationName, parentColumn, childColumn, true)
83                 {
84                 }
85
86                 public DataRelation (string relationName, DataColumn [] parentColumns, DataColumn [] childColumns)
87                         : this (relationName, parentColumns, childColumns, true)
88                 {
89                 }
90
91                 public DataRelation (string relationName, DataColumn parentColumn, DataColumn childColumn, bool createConstraints)
92                         : this (relationName, new DataColumn[] { parentColumn }, new DataColumn [] { childColumn }, createConstraints)
93                 {
94                 }
95
96                 public DataRelation (string relationName, DataColumn [] parentColumns, DataColumn [] childColumns, bool createConstraints)
97                 {
98                         this.extendedProperties = new PropertyCollection ();
99                         this.relationName = relationName == null ? string.Empty : relationName;
100                         if (parentColumns == null)
101                                 throw new ArgumentNullException ("parentColumns");
102                         this.parentColumns = parentColumns;
103                         if (childColumns == null)
104                                 throw new ArgumentNullException ("childColumns");
105                         this.childColumns = childColumns;
106                         this.createConstraints = createConstraints;
107                         if (parentColumns.Length != childColumns.Length)
108                                 throw new ArgumentException ("ParentColumns and ChildColumns should be the same length");
109                         DataTable parentTable = parentColumns [0].Table;
110                         DataTable childTable = childColumns [0].Table;
111                         if (parentTable.DataSet != childTable.DataSet)
112                                 throw new InvalidConstraintException ();
113                         foreach (DataColumn column in parentColumns)
114                                 if (column.Table != parentTable)
115                                         throw new InvalidConstraintException ();
116                         foreach (DataColumn column in childColumns)
117                                 if (column.Table != childTable)
118                                         throw new InvalidConstraintException ();
119
120                         for (int i = 0; i < ChildColumns.Length; i++)
121                                 if (!parentColumns [i].DataTypeMatches (childColumns [i]))
122                                         throw new InvalidConstraintException (
123                                                 "Parent Columns and Child Columns don't have matching column types");
124                 }
125
126                 [Browsable (false)]
127                 public DataRelation (string relationName, string parentTableName, string childTableName, string [] parentColumnNames, string[] childColumnNames, bool nested)
128                 {
129                         _relationName = relationName;
130                         _parentTableName = parentTableName;
131                         _childTableName = childTableName;
132                         _parentColumnNames = parentColumnNames;
133                         _childColumnNames = childColumnNames;
134                         _nested = nested;
135                         InitInProgress = true;
136                 }
137
138 #if NET_2_0
139                 [Browsable (false)]
140                 public DataRelation (string relationName, string parentTableName,
141                                      string parentTableNameSpace, string childTableName,
142                                      string childTableNameSpace, string[] parentColumnNames,
143                                      string[] childColumnNames, bool nested)
144                 {
145                         _relationName = relationName;
146                         _parentTableName = parentTableName;
147                         _parentTableNameSpace = parentTableNameSpace;
148                         _childTableName = childTableName;
149                         _childTableNameSpace = childTableNameSpace;
150                         _parentColumnNames = parentColumnNames;
151                         _childColumnNames = childColumnNames;
152                         _nested = nested;
153                         InitInProgress = true;
154                 }
155 #endif
156
157                 internal bool InitInProgress {
158                         get { return initInProgress; }
159                         set { initInProgress = value; }
160                 }
161
162                 internal void FinishInit (DataSet ds)
163                 {
164                         if (!ds.Tables.Contains (_parentTableName) || !ds.Tables.Contains (_childTableName))
165                                 throw new InvalidOperationException ();
166
167                         if (_parentColumnNames.Length != _childColumnNames.Length)
168                                 throw new InvalidOperationException ();
169
170                         DataTable parent = ds.Tables [_parentTableName];
171                         DataTable child = ds.Tables [_childTableName];
172
173                         parentColumns = new DataColumn [_parentColumnNames.Length];
174                         childColumns = new DataColumn [_childColumnNames.Length];
175
176                         for (int i = 0; i < _parentColumnNames.Length; ++i) {
177                                 if (!parent.Columns.Contains (_parentColumnNames [i]))
178                                         throw new InvalidOperationException ();
179                                 parentColumns [i] = parent.Columns [_parentColumnNames [i]];
180                                 if (!child.Columns.Contains (_childColumnNames [i]))
181                                         throw new InvalidOperationException ();
182                                 childColumns [i] = child.Columns [_childColumnNames [i]];
183                         }
184
185                         this.RelationName = _relationName;
186                         this.Nested = _nested;
187                         this.initFinished = true;
188                         this.extendedProperties = new PropertyCollection ();
189                         InitInProgress = false;
190 #if NET_2_0
191                         if (_parentTableNameSpace != String.Empty)
192                                 parent.Namespace = _parentTableNameSpace;
193                         if (_childTableNameSpace != String.Empty)
194                                 child.Namespace = _childTableNameSpace;
195 #endif
196                 }
197
198                 #endregion // Constructors
199
200                 #region Properties
201
202                 [DataCategory ("Data")]
203 #if !NET_2_0
204                 [DataSysDescription ("Indicates the child columns of this relation.")]
205 #endif
206                 public virtual DataColumn [] ChildColumns {
207                         get { return childColumns; }
208                 }
209
210                 public virtual ForeignKeyConstraint ChildKeyConstraint {
211                         get { return childKeyConstraint; }
212                 }
213
214                 internal void SetChildKeyConstraint (ForeignKeyConstraint foreignKeyConstraint)
215                 {
216                         childKeyConstraint = foreignKeyConstraint;
217                 }
218
219                 public virtual DataTable ChildTable {
220                         get { return childColumns [0].Table; }
221                 }
222
223                 [Browsable (false)]
224                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
225                 public virtual DataSet DataSet {
226                         get { return childColumns [0].Table.DataSet; }
227                 }
228
229                 [Browsable (false)]
230                 [DataCategory ("Data")]
231 #if !NET_2_0
232                 [DataSysDescription ("The collection that holds custom user information.")]
233 #endif
234                 public PropertyCollection ExtendedProperties {
235                         get {
236                                 if (extendedProperties == null)
237                                         extendedProperties = new PropertyCollection();
238                                 return extendedProperties;
239                         }
240                 }
241
242                 [DataCategory ("Data")]
243 #if !NET_2_0
244                 [DataSysDescription ("Indicates whether relations are nested.")]
245 #endif
246                 [DefaultValue (false)]
247                 public virtual bool Nested {
248                         get { return nested; }
249                         set { nested = value; }
250                 }
251
252                 [DataCategory ("Data")]
253 #if !NET_2_0
254                 [DataSysDescription ("Indicates the parent columns of this relation.")]
255 #endif
256                 public virtual DataColumn[] ParentColumns {
257                         get { return parentColumns; }
258                 }
259
260                 public virtual UniqueConstraint ParentKeyConstraint {
261                         get { return parentKeyConstraint; }
262                 }
263
264                 internal void SetParentKeyConstraint (UniqueConstraint uniqueConstraint)
265                 {
266                         parentKeyConstraint = uniqueConstraint;
267                 }
268
269                 internal void SetDataSet (DataSet ds)
270                 {
271                         dataSet = ds;
272                 }
273
274                 public virtual DataTable ParentTable {
275                         get { return parentColumns [0].Table; }
276                 }
277
278                 [DataCategory ("Data")]
279 #if !NET_2_0
280                 [DataSysDescription ("The name used to look up this relation in the Relations collection of a DataSet.")]
281 #endif
282                 [DefaultValue ("")]
283                 public virtual string RelationName {
284                         get { return relationName; }
285                         set { relationName = value; }
286                 }
287
288                 #endregion // Properties
289
290                 #region Methods
291
292                 protected void CheckStateForProperty ()
293                 {
294                         // TODO: check consistency of constraints
295                         DataTable parentTable = parentColumns [0].Table;
296                         DataTable childTable = childColumns [0].Table;
297                         if (parentTable.DataSet != childTable.DataSet)
298                                 throw new DataException ();
299                         bool allColumnsEqual = false;
300                         for (int colCnt = 0; colCnt < parentColumns.Length; ++colCnt) {
301                                 if (!parentColumns [colCnt].DataType.Equals (childColumns [colCnt].DataType))
302                                         throw new DataException ();
303                                 if (parentColumns [colCnt] != childColumns [colCnt])
304                                         allColumnsEqual = false;
305                         }
306                         if (allColumnsEqual)
307                                 throw new DataException ();
308                 }
309
310                 protected internal void OnPropertyChanging (PropertyChangedEventArgs pcevent)
311                 {
312                         if (onPropertyChangingDelegate != null)
313                                 onPropertyChangingDelegate (this, pcevent);
314                 }
315
316                 protected internal void RaisePropertyChanging (string name)
317                 {
318                         OnPropertyChanging(new PropertyChangedEventArgs(name));
319                 }
320
321                 public override string ToString ()
322                 {
323                         return relationName;
324                 }
325
326                 internal void UpdateConstraints ()
327                 {
328                         if (initFinished || ! createConstraints)
329                                 return;
330
331                         ForeignKeyConstraint    foreignKeyConstraint    = null;
332                         UniqueConstraint        uniqueConstraint        = null;
333
334                         foreignKeyConstraint    = FindForeignKey (ChildTable.Constraints);
335                         uniqueConstraint        = FindUniqueConstraint (ParentTable.Constraints);
336
337                         // if we did not find the unique constraint in the parent table.
338                         // we generate new uniqueConstraint and add it to the parent table.
339                         if (uniqueConstraint == null) {
340                                 uniqueConstraint = new UniqueConstraint (ParentColumns, false);
341                                 ParentTable.Constraints.Add (uniqueConstraint);
342                         }
343
344                         // if we did not find the foreign key constraint in the parent table.
345                         // we generate new foreignKeyConstraint and add it to the parent table.
346                         if (foreignKeyConstraint == null) {
347                                 foreignKeyConstraint = new ForeignKeyConstraint (RelationName,
348                                                                                  ParentColumns,
349                                                                                  ChildColumns);
350                                 ChildTable.Constraints.Add (foreignKeyConstraint);
351                         }
352
353                         SetParentKeyConstraint (uniqueConstraint);
354                         SetChildKeyConstraint (foreignKeyConstraint);
355                 }
356
357                 private static bool CompareDataColumns (DataColumn [] dc1, DataColumn [] dc2)
358                 {
359                         if (dc1.Length != dc2.Length)
360                                 return false;
361
362                         for (int columnCnt = 0; columnCnt < dc1.Length; ++columnCnt){
363                                 if (dc1 [columnCnt] != dc2 [columnCnt])
364                                         return false;
365                         }
366                         return true;
367                 }
368
369                 private ForeignKeyConstraint FindForeignKey (ConstraintCollection cl)
370                 {
371                         ForeignKeyConstraint fkc = null;
372                         foreach (Constraint o in cl) {
373                                 if (! (o is ForeignKeyConstraint))
374                                         continue;
375                                 fkc = (ForeignKeyConstraint) o;
376                                 /* Check ChildColumns & ParentColumns */
377                                 if (CompareDataColumns (ChildColumns, fkc.Columns) &&
378                                     CompareDataColumns (ParentColumns, fkc.RelatedColumns))
379                                         return fkc;
380                         }
381                         return null;
382                 }
383
384                 private UniqueConstraint FindUniqueConstraint (ConstraintCollection cl)
385                 {
386                         UniqueConstraint uc = null;
387                         // find if the unique constraint already exists in the parent table.
388                         foreach (Constraint o in cl){
389                                 if (! (o is UniqueConstraint))
390                                         continue;
391                                 uc = (UniqueConstraint) o;
392                                 //Check in ParentColumns
393                                 if (CompareDataColumns (ParentColumns, uc.Columns))
394                                         return uc;
395                         }
396                         return null;
397                 }
398
399                 /// <summary>
400                 ///     Check whether the given column is part of this relation.
401                 /// <summary>
402                 /// <returns>
403                 ///     true if the column is part of this relation, otherwise false.
404                 /// </returns>
405                 internal bool Contains (DataColumn column)
406                 {
407                         foreach (DataColumn col in ParentColumns)
408                                 if (col == column)
409                                         return true;
410
411                         foreach (DataColumn col in ChildColumns)
412                                 if (col == column)
413                                         return true;
414                         return false;
415                 }
416
417                 #endregion // Methods
418         }
419 }