New test.
[mono.git] / mcs / class / System.Data / System.Data / DataRelationCollection.cs
1 //
2 // System.Data.DataRelationCollection.cs
3 //
4 // Author:
5 //   Christopher Podurgiel (cpodurgiel@msn.com)
6 //   Daniel Morgan <danmorg@sc.rr.com>
7 //   Tim Coleman (tim@timcoleman.com)
8 //   Alan Tam Siu Lung <Tam@SiuLung.com>
9 //
10 // (C) Chris Podurgiel
11 // (C) 2002 Daniel Morgan
12 // Copyright (C) Tim Coleman, 2002
13 //
14
15 //
16 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
17 //
18 // Permission is hereby granted, free of charge, to any person obtaining
19 // a copy of this software and associated documentation files (the
20 // "Software"), to deal in the Software without restriction, including
21 // without limitation the rights to use, copy, modify, merge, publish,
22 // distribute, sublicense, and/or sell copies of the Software, and to
23 // permit persons to whom the Software is furnished to do so, subject to
24 // the following conditions:
25 // 
26 // The above copyright notice and this permission notice shall be
27 // included in all copies or substantial portions of the Software.
28 // 
29 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
30 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
31 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
32 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
33 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
34 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
35 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 //
37
38 using System;
39 using System.Collections;
40 using System.ComponentModel;
41
42 namespace System.Data {
43         /// <summary>
44         /// Represents the collection of DataRelation objects for this DataSet.
45         /// </summary>
46         [Editor ("Microsoft.VSDesigner.Data.Design.DataRelationCollectionEditor, " + Consts.AssemblyMicrosoft_VSDesigner,
47                  "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
48         [DefaultEvent ("CollectionChanged")]
49         [DefaultProperty ("Table")]
50 #if !NET_2_0
51         [Serializable]
52 #endif
53         public abstract class DataRelationCollection : InternalDataCollectionBase
54         {
55                 /// <summary>
56                 /// Summary description for DataTableRelationCollection.
57                 /// </summary>
58                 internal class DataSetRelationCollection : DataRelationCollection
59                 {
60                         private DataSet dataSet;
61                         DataRelation[] mostRecentRelations;
62                         
63                         /// <summary>
64                         /// Initializes a new instance of the DataSetRelationCollection class.
65                         /// </summary>
66                         internal DataSetRelationCollection (DataSet dataSet)
67                         {
68                                 this.dataSet = dataSet;
69                         }
70
71                         protected override DataSet GetDataSet()
72                         {
73                                 return dataSet;
74                         }
75
76                         /// <summary>
77                         /// Performs verification on the table.
78                         /// </summary>
79                         /// <param name="relation">The relation to check.</param>
80                         protected override void AddCore (DataRelation relation)
81                         {
82                                 if (relation.ChildTable.DataSet != this.dataSet 
83                                      || relation.ParentTable.DataSet != this.dataSet)
84                                    throw new DataException ();
85                                                                 
86                                 base.AddCore (relation);
87                                 relation.ParentTable.ChildRelations.Add (relation);
88                                 relation.ChildTable.ParentRelations.Add (relation);                
89                                 relation.SetDataSet (dataSet);
90                                 relation.UpdateConstraints ();
91                         }
92
93                         protected override void RemoveCore (DataRelation relation)
94                         {
95                                 base.RemoveCore(relation);
96                                 relation.SetDataSet (null);
97                                 relation.ParentTable.ChildRelations.Remove (relation);
98                                 relation.ChildTable.ParentRelations.Remove (relation);
99                                 relation.SetParentKeyConstraint (null);
100                                 relation.SetChildKeyConstraint (null);
101                         }
102
103                         public override void AddRange (DataRelation[] relations)
104                         {
105                                 if (relations == null)
106                                         return;
107
108                                 if (dataSet != null && dataSet.InitInProgress){
109                                         mostRecentRelations = relations;
110                                         return; 
111                                 }
112
113                                 foreach (DataRelation rel in relations){
114                                         if (rel == null)
115                                                 continue;
116                                         Add (rel);
117                                 }
118                         }
119
120                         internal override void PostAddRange ()
121                         {
122                                 if (mostRecentRelations == null)
123                                         return;
124
125                                 foreach (DataRelation rel in mostRecentRelations){
126                                         if (rel == null)
127                                                 continue;
128                                         if (rel.InitInProgress)
129                                                 rel.FinishInit (dataSet);
130                                         Add (rel);
131                                 }
132                                 mostRecentRelations = null;
133                         }
134
135                         protected override ArrayList List {
136                                 get {
137                                         return base.List;
138                                 }
139                         }
140                 }
141
142                 /// <summary>
143                 /// Summary description for DataTableRelationCollection.
144                 /// </summary>
145                 internal class DataTableRelationCollection : DataRelationCollection
146                 {
147                         private DataTable dataTable;
148                         
149                         /// <summary>
150                         /// Initializes a new instance of the DataTableRelationCollection class.
151                         /// </summary>
152                         internal DataTableRelationCollection (DataTable dataTable)
153                         {
154                                 this.dataTable = dataTable;
155                         }
156
157                         protected override DataSet GetDataSet()
158                         {
159                                 return dataTable.DataSet;
160                         }
161
162                         /// <summary>
163                         /// Performs verification on the table.
164                         /// </summary>
165                         /// <param name="relation">The relation to check.</param>
166                         protected override void AddCore (DataRelation relation)
167                         {
168                 if (dataTable.ParentRelations == this && relation.ChildTable != dataTable)
169                     throw new ArgumentException ("Cannot add a relation to this table's " +
170                                                                                                         "ParentRelations where this table is not" +
171                                                                                                         " the Child table.");
172
173                 if (dataTable.ChildRelations == this && relation.ParentTable != dataTable)   
174                     throw new ArgumentException("Cannot add a relation to this table's " +
175                                                 "ChildRelations where this table is not" +
176                                                 " the Parent table.");
177                 
178                                 dataTable.DataSet.Relations.Add(relation);
179                 base.AddCore (relation);
180
181                         }
182                         
183                         protected override void RemoveCore (DataRelation relation)
184                         {
185                                 relation.DataSet.Relations.Remove(relation);
186                                 base.RemoveCore (relation);
187                         }
188
189                         protected override ArrayList List {
190                                 get {
191                                         return base.List;
192                                 }
193                         }
194                 }
195
196                 private int defaultNameIndex;
197                 private DataRelation inTransition;
198                 int index;
199
200                 
201                 /// <summary>
202                 /// Initializes a new instance of the DataRelationCollection class.
203                 /// </summary>
204                 protected DataRelationCollection () 
205                         : base ()
206                 {
207                         inTransition = null;
208                         defaultNameIndex = 1;
209                 }
210
211                 /// <summary>
212                 /// Gets the DataRelation object specified by name.
213                 /// </summary>
214                 public DataRelation this [string name] {
215                         get {
216                                 int index = IndexOf (name, true);
217                                 return index < 0 ? null : (DataRelation) List[index];
218                         }
219                 }
220
221                 /// <summary>
222                 /// Gets the DataRelation object at the specified index.
223                 /// </summary>
224                 public DataRelation this [int index] {
225                         get {
226                                 if (index < 0 || index >= List.Count)
227                                         throw new IndexOutOfRangeException (String.Format ("Cannot find relation {0}.", index));
228
229                                 return (DataRelation)List [index];
230                         }
231                 }
232
233                 
234                 #region Add Methods
235                 private string GetNextDefaultRelationName ()
236                 {
237                         int index = 1;
238                         string defRelationName = "Relation" +index;
239                         for (; Contains (defRelationName); ++index) {
240                                 defRelationName = "Relation" + index;
241                         }
242                         return defRelationName;
243                 }
244
245                 /// <summary>
246                 /// Adds a DataRelation to the DataRelationCollection.
247                 /// </summary>
248                 /// <param name="relation">The DataRelation to add to the collection.</param>
249                 [MonoTODO]
250                 public void Add(DataRelation relation)
251                 {
252             // To prevent endless recursion
253                         if(inTransition == relation) {
254                                 return; 
255                         }
256                         else { 
257                                 inTransition = relation; 
258                         }
259
260                         try
261                         {
262                                 this.AddCore (relation);
263                                 if(relation.RelationName == string.Empty)
264                                         relation.RelationName = GenerateRelationName();
265                         
266                                 relation.ParentTable.ResetPropertyDescriptorsCache();
267                                 relation.ChildTable.ResetPropertyDescriptorsCache();
268                         
269                                 CollectionChangeEventArgs e = new CollectionChangeEventArgs(CollectionChangeAction.Add, this);
270                                 OnCollectionChanged(e);
271                         }
272                         finally
273                         {
274                                 inTransition = null;
275                         }
276                 }
277
278                 private string GenerateRelationName()
279                 {
280                         index++;
281                         return "Relation" + index;
282                 }
283
284                 /// <summary>
285                 /// Creates a relation given the parameters and adds it to the collection. The name is defaulted.
286                 /// An ArgumentException is generated if this relation already belongs to this collection or belongs to another collection.
287                 /// An InvalidConstraintException is generated if the relation can't be created based on the parameters.
288                 /// The CollectionChanged event is fired if it succeeds.
289                 /// </summary>
290                 /// <param name="parentColumn">parent column of relation.</param>
291                 /// <param name="childColumn">child column of relation.</param>
292                 /// <returns>The created DataRelation.</returns>
293                 public virtual DataRelation Add(DataColumn parentColumn, DataColumn childColumn)
294                 {       
295                         DataRelation dataRelation = new DataRelation(GetNextDefaultRelationName (), parentColumn, childColumn);
296                         Add(dataRelation);
297                         return dataRelation;
298                 }
299
300                 /// <summary>
301                 /// Creates a relation given the parameters and adds it to the collection. The name is defaulted.
302                 /// An ArgumentException is generated if this relation already belongs to this collection or belongs to another collection.
303                 /// An InvalidConstraintException is generated if the relation can't be created based on the parameters.
304                 /// The CollectionChanged event is raised if it succeeds.
305                 /// </summary>
306                 /// <param name="parentColumns">An array of parent DataColumn objects.</param>
307                 /// <param name="childColumns">An array of child DataColumn objects.</param>
308                 /// <returns>The created DataRelation.</returns>
309                 public virtual DataRelation Add(DataColumn[] parentColumns, DataColumn[] childColumns)
310                 {
311                         DataRelation dataRelation = new DataRelation(GetNextDefaultRelationName (), parentColumns, childColumns);
312                         Add(dataRelation);
313                         return dataRelation;
314                 }
315
316                 /// <summary>
317                 /// Creates a relation given the parameters and adds it to the collection.
318                 /// An ArgumentException is generated if this relation already belongs to this collection or belongs to another collection.
319                 /// A DuplicateNameException is generated if this collection already has a relation with the same name (case insensitive).
320                 /// An InvalidConstraintException is generated if the relation can't be created based on the parameters.
321                 /// The CollectionChanged event is raised if it succeeds.
322                 /// </summary>
323                 /// <param name="name">The name of the relation.</param>
324                 /// <param name="parentColumn">parent column of relation.</param>
325                 /// <returns>The created DataRelation.</returns>
326                 /// <returns></returns>
327                 public virtual DataRelation Add(string name, DataColumn parentColumn, DataColumn childColumn)
328                 {
329                         //If no name was supplied, give it a default name.
330                         if (name == null || name == "") name = GetNextDefaultRelationName ();
331
332                         DataRelation dataRelation = new DataRelation(name, parentColumn, childColumn);
333                         Add(dataRelation);
334                         return dataRelation;
335                 }
336
337                 /// <summary>
338                 /// Creates a DataRelation with the specified name, and arrays of parent and child columns, and adds it to the collection.
339                 /// </summary>
340                 /// <param name="name">The name of the DataRelation to create.</param>
341                 /// <param name="parentColumns">An array of parent DataColumn objects.</param>
342                 /// <param name="childColumns">An array of child DataColumn objects.</param>
343                 /// <returns>The created DataRelation.</returns>
344                 public virtual DataRelation Add(string name, DataColumn[] parentColumns, DataColumn[] childColumns)
345                 {
346                         //If no name was supplied, give it a default name.
347                         if (name == null || name == "") name = GetNextDefaultRelationName ();
348
349                         DataRelation dataRelation = new DataRelation(name, parentColumns, childColumns);
350                         Add(dataRelation);
351                         return dataRelation;
352                 }
353
354                 /// <summary>
355                 /// Creates a relation given the parameters and adds it to the collection.
356                 /// An ArgumentException is generated if this relation already belongs to this collection or belongs to another collection.
357                 /// A DuplicateNameException is generated if this collection already has a relation with the same name (case insensitive).
358                 /// An InvalidConstraintException is generated if the relation can't be created based on the parameters.
359                 /// The CollectionChanged event is raised if it succeeds.
360                 /// </summary>
361                 /// <param name="name">The name of the relation.</param>
362                 /// <param name="parentColumn">parent column of relation.</param>
363                 /// <param name="childColumn">child column of relation.</param>
364                 /// <param name="createConstraints">true to create constraints; otherwise false. (default is true)</param>
365                 /// <returns>The created DataRelation.</returns>
366                 public virtual DataRelation Add(string name, DataColumn parentColumn, DataColumn childColumn, bool createConstraints)
367                 {
368                         //If no name was supplied, give it a default name.
369                         if (name == null || name == "") name = GetNextDefaultRelationName ();
370
371                         DataRelation dataRelation = new DataRelation(name, parentColumn, childColumn, createConstraints);
372                         Add(dataRelation);
373                         return dataRelation;
374                 }
375
376                 /// <summary>
377                 /// Creates a DataRelation with the specified name, arrays of parent and child columns, 
378                 /// and value specifying whether to create a constraint, and adds it to the collection.
379                 /// </summary>
380                 /// <param name="name">The name of the DataRelation to create.</param>
381                 /// <param name="parentColumns">An array of parent DataColumn objects.</param>
382                 /// <param name="childColumns">An array of child DataColumn objects.</param>
383                 /// <param name="createConstraints">true to create a constraint; otherwise false.</param>
384                 /// <returns>The created DataRelation.</returns>
385                 public virtual DataRelation Add(string name, DataColumn[] parentColumns, DataColumn[] childColumns, bool createConstraints)
386                 {
387                         //If no name was supplied, give it a default name.
388                         if (name == null || name == "") name = GetNextDefaultRelationName ();
389
390                         DataRelation dataRelation = new DataRelation(name, parentColumns, childColumns, createConstraints);
391                         Add(dataRelation);
392                         return dataRelation;
393                 }
394                 #endregion
395         
396                 /// <summary>
397                 /// Adds to the list
398                 /// </summary>
399                 /// <param name="relation">The relation to check.</param>
400                 [MonoTODO]
401                 protected virtual void AddCore(DataRelation relation)
402                 {
403                         if (relation == null) {
404                                 //TODO: Issue a good exception message.
405                                 throw new ArgumentNullException();
406                         }
407                         if(List.IndexOf(relation) != -1) {
408                                 //TODO: Issue a good exception message.
409                                 throw new ArgumentException();
410                         }
411
412                         // check if the collection has a relation with the same name.
413                         int tmp = IndexOf(relation.RelationName);
414                         // if we found a relation with same name we have to check
415                         // that it is the same case.
416                         // indexof can return a table with different case letters.
417                         if (tmp != -1 &&
418                                 relation.RelationName == this[tmp].RelationName)
419                                         throw new DuplicateNameException("A DataRelation named '" + relation.RelationName + "' already belongs to this DataSet.");
420                                         
421                         // check whether the relation exists between the columns already
422                         foreach (DataRelation rel in this) {    
423                                 // compare child columns
424                                 bool differs = false;
425                                 foreach (DataColumn current in relation.ChildColumns) {
426                                         bool exists = false;
427                                         foreach (DataColumn col in rel.ChildColumns) {
428                                                 if (col == current) {
429                                                         exists = true;
430                                                         break;
431                                                 }
432                                         }
433                                         if (!exists) {
434                                                 differs = true;
435                                                 break;
436                                         }
437                                 }
438                                 
439                                 if (! differs) {
440                                         // compare parent columns
441                                         differs = false;
442                                         foreach (DataColumn current in relation.ParentColumns) {
443                                                 bool exists = false;
444                                                 foreach (DataColumn col in rel.ParentColumns) {
445                                                         if (col == current) {
446                                                                 exists = true;
447                                                                 break;
448                                                         }
449                                                 }
450                                                 if (!exists) {
451                                                         differs = true;
452                                                         break;
453                                                 }
454                                         }
455                                         
456                                         if (! differs)
457                                                 throw new ArgumentException ("A relation already exists for these child columns");
458                                 }
459                         }               
460                         
461                         // Add to collection
462                         List.Add(relation);
463                 }
464                 
465                 /// <summary>
466                 /// Copies the elements of the specified DataRelation array to the end of the collection.
467                 /// </summary>
468                 /// <param name="relations">The array of DataRelation objects to add to the collection.</param>
469                 public virtual void AddRange(DataRelation[] relations)
470                 {
471                         if (relations == null)
472                                 return;
473
474                         foreach (DataRelation relation in relations)
475                                 Add (relation);
476                 }
477
478                 public void CopyTo (DataRelation [] array, int index)
479                 {
480                         CopyTo ((Array) array, index);
481                 }
482
483                 internal virtual void PostAddRange ()
484                 {
485                 }
486
487                 public virtual bool CanRemove(DataRelation relation)
488                 {
489                         if (relation == null || !GetDataSet().Equals(relation.DataSet))
490                                 return false;
491
492                         // check if the relation doesnot belong to this collection
493                         int tmp = IndexOf(relation.RelationName);
494                         // if we found a relation with same name we have to check
495                         // that it is the same case.
496                         // indexof can return a table with different case letters.
497                         if (tmp != -1) {
498                                if(relation.RelationName != this[tmp].RelationName)
499                                         return false;
500                         }
501                         else {
502                                 return false;
503                         }                                       
504                                                                                         
505                         return true;
506                 }
507
508                 public virtual void Clear()
509                 {
510                         for (int i = 0; i < Count; i++)
511                                 Remove(this[i]);
512
513                         List.Clear();
514                 }
515
516                 public virtual bool Contains(string name)
517                 {
518                         return (-1 != IndexOf (name, false));
519                 }
520
521                 private CollectionChangeEventArgs CreateCollectionChangeEvent (CollectionChangeAction action)
522                 {
523                         return new CollectionChangeEventArgs (action, this);
524                 }
525
526                 protected abstract DataSet GetDataSet();
527
528                 public virtual int IndexOf(DataRelation relation)
529                 {
530                         return List.IndexOf(relation);
531                 }
532
533                 public virtual int IndexOf(string relationName)
534                 {
535                         return IndexOf(relationName, false);
536                 }
537
538                 private int IndexOf (string name, bool error)
539                 {
540                         int count = 0, match = -1;
541                         for (int i = 0; i < List.Count; i++)
542                         {
543                                 String name2 = ((DataRelation) List[i]).RelationName;
544                                 if (String.Compare (name, name2, true) == 0)
545                                 {
546                                         if (String.Compare (name, name2, false) == 0)
547                                                 return i;
548                                         match = i;
549                                         count++;
550                                 }
551                         }
552                         if (count == 1)
553                                 return match;
554                         if (count > 1 && error)
555                                 throw new ArgumentException ("There is no match for the name in the same case and there are multiple matches in different case.");
556                         return -1;
557                 }
558
559                 protected virtual void OnCollectionChanged (CollectionChangeEventArgs ccevent)
560                 {
561                         if (CollectionChanged != null)
562                                 CollectionChanged (this, ccevent);
563                 }
564
565                 [MonoTODO]
566                 protected internal virtual void OnCollectionChanging (CollectionChangeEventArgs ccevent)
567                 {
568                         throw new NotImplementedException ();
569                 }
570
571                 public void Remove (DataRelation relation)
572                 {
573                         // To prevent endless recursion
574                         if(inTransition == relation) {
575                                 return; 
576                         }
577                         else { 
578                                 inTransition = relation; 
579                         }
580
581                         if (relation == null)
582                                 return;
583
584                         try
585                         {
586                                 // check if the list doesnot contains this relation.
587                                 if (!(List.Contains(relation)))
588                                         throw new ArgumentException("Relation doesnot belong to this Collection.");
589
590                                 RemoveCore (relation);
591                                 string name = "Relation" + index;
592                                 if (relation.RelationName == name)
593                                         index--;
594
595                                 OnCollectionChanged (CreateCollectionChangeEvent (CollectionChangeAction.Remove));
596                         }
597                         finally
598                         {
599                                 inTransition = null;
600                         }
601                 }
602
603                 public void Remove (string name)
604                 {
605                         DataRelation relation = this[name];
606                         if (relation == null)
607                                 throw new ArgumentException("Relation doesnot belong to this Collection.");
608                         Remove(relation);
609                 }
610
611                 public void RemoveAt (int index)
612                 {               
613                         DataRelation relation = this[index];
614                         if (relation == null)
615                                 throw new IndexOutOfRangeException(String.Format("Cannot find relation {0}", index));
616                         Remove(relation);
617                 }
618
619                 [MonoTODO]
620                 protected virtual void RemoveCore(DataRelation relation)
621                 {
622                         // Remove from collection
623                         List.Remove(relation);
624                 }
625
626                 #region Events
627
628                 [ResDescriptionAttribute ("Occurs whenever this collection's membership changes.")]
629                 public event CollectionChangeEventHandler CollectionChanged;
630
631                 #endregion
632         }
633 }