2004-03-31 Juraj Skripsky <juraj@hotfeet.ch>
[mono.git] / mcs / class / System.Data / System.Data / DataRowCollection.cs
1 //
2 // System.Data.DataRowCollection.cs
3 //
4 // Author:
5 //   Daniel Morgan <danmorg@sc.rr.com>
6 //   Tim Coleman <tim@timcoleman.com>
7 //
8 // (C) Ximian, Inc 2002
9 // (C) Copyright 2002 Tim Coleman
10 // (C) Copyright 2002 Daniel Morgan
11 //
12
13 using System;
14 using System.Collections;
15 using System.ComponentModel;
16
17 namespace System.Data
18 {
19         /// <summary>
20         /// Collection of DataRows in a DataTable
21         /// </summary>
22         [Serializable]
23         public class DataRowCollection : InternalDataCollectionBase 
24         {
25                 private DataTable table;
26
27                 /// <summary>
28                 /// Internal constructor used to build a DataRowCollection.
29                 /// </summary>
30                 internal DataRowCollection (DataTable table) : base ()
31                 {
32                         this.table = table;
33                 }
34
35                 /// <summary>
36                 /// Gets the row at the specified index.
37                 /// </summary>
38                 public DataRow this[int index] 
39                 {
40                         get { 
41                                 if (index >= Count)
42                                         throw new IndexOutOfRangeException ("There is no row at position " + index + ".");
43
44                                 return (DataRow) list[index]; 
45                         }
46                 }
47
48                 /// <summary>
49                 /// This member overrides InternalDataCollectionBase.List
50                 /// </summary>
51                 protected override ArrayList List 
52                 {
53                         get { return list; }
54                 }               
55
56                 /// <summary>
57                 /// Adds the specified DataRow to the DataRowCollection object.
58                 /// </summary>
59                 public void Add (DataRow row) 
60                 {
61                         //TODO: validation
62                         if (row == null)
63                                 throw new ArgumentNullException("row", "'row' argument cannot be null.");
64
65                         if (row.Table != this.table)
66                                 throw new ArgumentException ("This row already belongs to another table.");
67                         
68                         // If row id is not -1, we know that it is in the collection.
69                         if (row.RowID != -1)
70                                 throw new ArgumentException ("This row already belongs to this table.");
71                         
72
73                         if ((table.DataSet == null || table.DataSet.EnforceConstraints) && !table._duringDataLoad)
74                                 // we have to check that the new row doesn't colide with existing row
75                                 ValidateDataRowInternal(row);
76                         
77                         row.HasParentCollection = true;
78                         list.Add (row);
79                         // Set the row id.
80                         row.RowID = list.Count - 1;
81                         row.AttachRow ();
82                         row.Table.ChangedDataRow (row, DataRowAction.Add);
83                 }
84
85                 /// <summary>
86                 /// Creates a row using specified values and adds it to the DataRowCollection.
87                 /// </summary>
88                 public virtual DataRow Add (object[] values) 
89                 {
90                         DataRow row = table.NewRow ();
91                         row.ItemArray = values;
92                         Add (row);
93                         return row;
94                 }
95
96                 /// <summary>
97                 /// Clears the collection of all rows.
98                 /// </summary>
99                 public void Clear () 
100                 {
101                         if (this.table.DataSet != null)
102                         {
103                                 foreach (DataTable table in this.table.DataSet.Tables)
104                                 {
105                                         foreach (Constraint c in table.Constraints)
106                                         {
107                                                 if (c is ForeignKeyConstraint)
108                                                 {
109                                                         if (((ForeignKeyConstraint) c).RelatedTable.Equals(this.table))
110                                                                 throw new InvalidConstraintException("Cannot clear table Parent because ForeignKeyConstraint " + c.ConstraintName + " enforces Child.");
111                                                 }
112                                         }
113                                 }
114                         }
115                         list.Clear ();
116                 }
117
118                 /// <summary>
119                 /// Gets a value indicating whether the primary key of any row in the collection contains
120                 /// the specified value.
121                 /// </summary>
122                 public bool Contains (object key) 
123                 {
124                         return Find (key) != null;
125                 }
126
127                 /// <summary>
128                 /// Gets a value indicating whether the primary key column(s) of any row in the 
129                 /// collection contains the values specified in the object array.
130                 /// </summary>
131                 public bool Contains (object[] keys) 
132                 {
133                         if (table.PrimaryKey.Length != keys.Length)
134                                 throw new ArgumentException ("Expecting " + table.PrimaryKey.Length + " value(s) for the key " + 
135                                                              "being indexed, but received " + keys.Length + " value(s).");
136
137                         return Find (keys) != null;
138                 }
139
140                 /// <summary>
141                 /// Gets the row specified by the primary key value.
142                 /// </summary>
143                 public DataRow Find (object key) 
144                 {
145                         if (table.PrimaryKey.Length == 0)
146                                 throw new MissingPrimaryKeyException ("Table doesn't have a primary key.");
147                         if (table.PrimaryKey.Length > 1)
148                                 throw new ArgumentException ("Expecting " + table.PrimaryKey.Length +" value(s) for the key being indexed, but received 1 value(s).");
149
150                         if( key==null)
151                                 throw new ArgumentException("Expecting 1 value(s) for the key being indexed, but received 0 value(s).");
152
153                         string primColumnName = table.PrimaryKey [0].ColumnName;
154                         Type coltype = null;
155                         object newKey = null;
156                         
157                         foreach (DataRow row in this) {
158                                 
159                                 if (row.RowState != DataRowState.Deleted)
160                                 {
161                                         object primValue = row [primColumnName];
162                                                                                
163                                         newKey = Convert.ChangeType (key, Type.GetTypeCode(primValue.GetType ()));
164
165                                         if (primValue.Equals (newKey))
166                                                 return row;
167                                 }
168                         }
169                                                 
170                         return null;
171                 }
172
173                 /// <summary>
174                 /// Gets the row containing the specified primary key values.
175                 /// </summary>
176                 public DataRow Find (object[] keys) 
177                 {
178                         if (table.PrimaryKey.Length == 0)
179                                 throw new MissingPrimaryKeyException ("Table doesn't have a primary key.");
180
181                         string  [] primColumnNames = new string [table.PrimaryKey.Length];
182                         
183                         for (int i = 0; i < primColumnNames.Length; i++)
184                                 primColumnNames [i] = table.PrimaryKey [i].ColumnName;
185
186                         Type coltype = null;
187                         object newKey = null;
188                         
189                         foreach (DataRow row in this) {
190                                 
191                                 if (row.RowState != DataRowState.Deleted)
192                                 {
193                                         bool eq = true;
194                                         for (int i = 0; i < keys.Length; i++) 
195                                         {
196                                         
197                                                 object primValue = row [primColumnNames [i]];
198                                                 object keyValue = keys [i];
199                                                 if (keyValue == null) 
200                                                 {
201                                                         if (primValue == null)
202                                                                 return row;
203                                                         else 
204                                                                 continue;
205                                                 }
206                                                                        
207                                                 newKey = Convert.ChangeType (keyValue, Type.GetTypeCode(primValue.GetType ()));
208
209                                                 if (!primValue.Equals (newKey)) 
210                                                 {
211                                                         eq = false;
212                                                         break;
213                                                 }                                               
214                                         }
215
216                                         if (eq)
217                                                 return row;
218                                 }
219                         }
220                                                 
221                         return null;
222                 }
223
224                 /// <summary>
225                 /// Inserts a new row into the collection at the specified location.
226                 /// </summary>
227                 public void InsertAt (DataRow row, int pos) 
228                 {
229                         if (pos < 0)
230                                 throw new IndexOutOfRangeException ("The row insert position " + pos + " is invalid.");
231                         
232                         if (row == null)
233                                 throw new ArgumentNullException("row", "'row' argument cannot be null.");
234         
235                         if (row.Table != this.table)
236                                 throw new ArgumentException ("This row already belongs to another table.");
237
238                         // If row id is not -1, we know that it is in the collection.
239                         if (row.RowID != -1)
240                                 throw new ArgumentException ("This row already belongs to this table.");
241                         
242                         if ((table.DataSet == null || table.DataSet.EnforceConstraints) && !table._duringDataLoad)
243                                 // we have to check that the new row doesn't colide with existing row
244                                 ValidateDataRowInternal(row);
245                                 
246                         if (pos >= list.Count)
247                                 list.Add (row);
248                         else
249                                 list.Insert (pos, row);
250                                 
251                         row.HasParentCollection = true;
252                         row.AttachRow ();
253                         row.Table.ChangedDataRow (row, DataRowAction.Add);
254                 }
255
256                 /// <summary>
257                 /// Removes the specified DataRow from the internal list. Used by DataRow to commit the removing.
258                 /// </summary>
259                 internal void RemoveInternal (DataRow row) {
260                         if (row == null) {
261                                 throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
262                         }
263                         int index = list.IndexOf(row);
264                         if (index < 0) {
265                                 throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
266                         }
267                         list.RemoveAt(index);
268                 }
269
270                 /// <summary>
271                 /// Removes the specified DataRow from the collection.
272                 /// </summary>
273                 public void Remove (DataRow row) 
274                 {
275                         if (row == null)
276                                 throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
277                         int index = list.IndexOf(row);
278                         if (index < 0)
279                                 throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
280                         row.Delete();
281                         // if the row was in added state it will be in Detached state after the
282                         // delete operation, so we have to check it.
283                         if (row.RowState != DataRowState.Detached)
284                                 row.AcceptChanges();
285                 }
286
287                 /// <summary>
288                 /// Removes the row at the specified index from the collection.
289                 /// </summary>
290                 public void RemoveAt (int index) 
291                 {                       
292                         if (index < 0 || index >= list.Count)
293                                 throw new IndexOutOfRangeException ("There is no row at position " + index + ".");
294                         DataRow row = (DataRow)list [index];
295                         row.Delete();
296                         row.AcceptChanges();
297                 }
298
299                 ///<summary>
300                 ///Internal method used to validate a given DataRow with respect
301                 ///to the DataRowCollection
302                 ///</summary>
303                 [MonoTODO]
304                 internal void ValidateDataRowInternal(DataRow row)
305                 {
306                         //first check for null violations.
307                         row.CheckNullConstraints();
308                         // This validates constraints in the specific order : 
309                         // first unique/primary keys first, then Foreignkeys, etc
310                         ArrayList uniqueConstraintsDone = new ArrayList();
311                         ArrayList foreignKeyConstraintsDone = new ArrayList();
312                         try {
313                                 foreach(Constraint constraint in table.Constraints.UniqueConstraints) {
314                                         constraint.AssertConstraint(row);
315                                         uniqueConstraintsDone.Add(constraint);
316                                 }
317                         
318                                 foreach(Constraint constraint in table.Constraints.ForeignKeyConstraints) {
319                                         constraint.AssertConstraint(row);
320                                         foreignKeyConstraintsDone.Add(constraint);
321                                 }
322                         }
323                         // if one of the AssertConstraint failed - we need to "rollback" all the changes
324                         // caused by AssertCoinstraint calls already succeeded
325                         catch(ConstraintException e) {
326                                 RollbackAsserts(row,foreignKeyConstraintsDone,uniqueConstraintsDone);
327                                 throw e;
328                         }
329                         catch(InvalidConstraintException e) {   
330                                 RollbackAsserts(row,foreignKeyConstraintsDone,uniqueConstraintsDone);
331                                 throw e;
332                         }
333                 }
334
335                 private void RollbackAsserts(DataRow row,ICollection foreignKeyConstraintsDone,
336                         ICollection uniqueConstraintsDone)
337                 {
338                         // if any of constraints assert failed - 
339                         // we have to rollback all the asserts scceeded
340                         // on order reverse to thier original execution
341                         foreach(Constraint constraint in foreignKeyConstraintsDone) {
342                                 constraint.RollbackAssert(row);
343                         }
344
345                         foreach(Constraint constraint in uniqueConstraintsDone) {
346                                 constraint.RollbackAssert(row);
347                         }
348                 }
349         }
350 }