f702cf3739cbf9faa94546e1562652f0cc507c40
[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 //
14 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
15 //
16 // Permission is hereby granted, free of charge, to any person obtaining
17 // a copy of this software and associated documentation files (the
18 // "Software"), to deal in the Software without restriction, including
19 // without limitation the rights to use, copy, modify, merge, publish,
20 // distribute, sublicense, and/or sell copies of the Software, and to
21 // permit persons to whom the Software is furnished to do so, subject to
22 // the following conditions:
23 // 
24 // The above copyright notice and this permission notice shall be
25 // included in all copies or substantial portions of the Software.
26 // 
27 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 //
35
36 using System;
37 using System.Collections;
38 using System.ComponentModel;
39
40 namespace System.Data
41 {
42         /// <summary>
43         /// Collection of DataRows in a DataTable
44         /// </summary>
45         [Serializable]
46         public class DataRowCollection : InternalDataCollectionBase 
47         {
48                 private DataTable table;
49
50                 /// <summary>
51                 /// Internal constructor used to build a DataRowCollection.
52                 /// </summary>
53                 internal DataRowCollection (DataTable table) : base ()
54                 {
55                         this.table = table;
56                 }
57
58                 /// <summary>
59                 /// Gets the row at the specified index.
60                 /// </summary>
61                 public DataRow this[int index] 
62                 {
63                         get { 
64                                 if (index >= Count)
65                                         throw new IndexOutOfRangeException ("There is no row at position " + index + ".");
66
67                                 return (DataRow) List[index]; 
68                         }
69                 }
70
71                 /// <summary>
72                 /// This member overrides InternalDataCollectionBase.List
73                 /// </summary>
74                 protected override ArrayList List 
75                 {
76                         get { return base.List; }
77                 }               
78
79                 /// <summary>
80                 /// Adds the specified DataRow to the DataRowCollection object.
81                 /// </summary>
82                 public void Add (DataRow row) 
83                 {
84                         //TODO: validation
85                         if (row == null)
86                                 throw new ArgumentNullException("row", "'row' argument cannot be null.");
87
88                         if (row.Table != this.table)
89                                 throw new ArgumentException ("This row already belongs to another table.");
90                         
91                         // If row id is not -1, we know that it is in the collection.
92                         if (row.RowID != -1)
93                                 throw new ArgumentException ("This row already belongs to this table.");
94                         
95
96                         if ((table.DataSet == null || table.DataSet.EnforceConstraints) && !table._duringDataLoad)
97                                 // we have to check that the new row doesn't colide with existing row
98                                 ValidateDataRowInternal(row);
99                         
100                         row.Table.ChangingDataRow (row, DataRowAction.Add);
101                         row.HasParentCollection = true;
102                         List.Add (row);
103                         // Set the row id.
104                         row.RowID = List.Count - 1;
105                         row.AttachRow ();
106                         row.Table.ChangedDataRow (row, DataRowAction.Add);
107                 }
108
109                 /// <summary>
110                 /// Creates a row using specified values and adds it to the DataRowCollection.
111                 /// </summary>
112                 public virtual DataRow Add (object[] values) 
113                 {
114                         if (values.Length > table.Columns.Count)
115                                 throw new ArgumentException ("The array is larger than the number of columns in the table.");
116                         DataRow row = table.NewRow ();
117                         row.ItemArray = values;
118                         Add (row);
119                         return row;
120                 }
121
122                 /// <summary>
123                 /// Clears the collection of all rows.
124                 /// </summary>
125                 public void Clear () 
126                 {
127                         if (this.table.DataSet != null && this.table.DataSet.EnforceConstraints)
128                         {
129                                 foreach (DataTable table in this.table.DataSet.Tables)
130                                 {
131                                         foreach (Constraint c in table.Constraints)
132                                         {
133                                                 if (c is ForeignKeyConstraint)
134                                                 {
135                                                         if (((ForeignKeyConstraint) c).RelatedTable.Equals(this.table))
136 #if NET_1_1
137                                                                 throw new InvalidConstraintException (String.Format ("Cannot clear table Parent because ForeignKeyConstraint {0} enforces Child.", c.ConstraintName));
138 #else
139                                                                 throw new ArgumentException (String.Format ("Cannot clear table Parent because ForeignKeyConstraint {0} enforces Child.", c.ConstraintName));
140 #endif
141                                                 }
142                                         }
143                                 }
144                         }
145                         List.Clear ();
146                 }
147
148                 /// <summary>
149                 /// Gets a value indicating whether the primary key of any row in the collection contains
150                 /// the specified value.
151                 /// </summary>
152                 public bool Contains (object key) 
153                 {
154                         return Find (key) != null;
155                 }
156
157                 /// <summary>
158                 /// Gets a value indicating whether the primary key column(s) of any row in the 
159                 /// collection contains the values specified in the object array.
160                 /// </summary>
161                 public bool Contains (object[] keys) 
162                 {
163                         if (table.PrimaryKey.Length != keys.Length)
164                                 throw new ArgumentException ("Expecting " + table.PrimaryKey.Length + " value(s) for the key " + 
165                                                              "being indexed, but received " + keys.Length + " value(s).");
166
167                         return Find (keys) != null;
168                 }
169
170                 /// <summary>
171                 /// Gets the row specified by the primary key value.
172                 /// </summary>
173                 public DataRow Find (object key) 
174                 {
175                         if (table.PrimaryKey.Length == 0)
176                                 throw new MissingPrimaryKeyException ("Table doesn't have a primary key.");
177                         if (table.PrimaryKey.Length > 1)
178                                 throw new ArgumentException ("Expecting " + table.PrimaryKey.Length +" value(s) for the key being indexed, but received 1 value(s).");
179
180                         if (key == null)
181 #if NET_1_1
182                                 return null;
183 #else
184                                 throw new ArgumentException("Expecting 1 value(s) for the key being indexed, but received 0 value(s).");
185 #endif
186
187                         DataColumn primaryKey = table.PrimaryKey[0];
188                         Index primaryKeyIndex = table.GetIndexByColumns(table.PrimaryKey);
189                         int tmpRecord = table.RecordCache.NewRecord();
190                         try {
191                                 primaryKey.DataContainer[tmpRecord] = key;
192
193                                 // if we can search through index
194                                 if (primaryKeyIndex != null) {
195                                         // get the child rows from the index
196                                         Node node = primaryKeyIndex.FindSimple(tmpRecord,1,true);
197                                         if (node != null) {
198                                                 return node.Row;
199                                         }
200                                 }
201                         
202                                 //loop through all collection rows                      
203                                 foreach (DataRow row in this) {
204                                         if (row.RowState != DataRowState.Deleted) {
205                                                 int index = row.IndexFromVersion(DataRowVersion.Default); 
206                                                 if (primaryKey.DataContainer.CompareValues(index, tmpRecord) == 0) {
207                                                         return row;
208                                                 }
209                                         }
210                                 }
211                                 return null;
212                         }
213                         finally {
214                                 table.RecordCache.DisposeRecord(tmpRecord);
215                         }
216                 }
217
218                 /// <summary>
219                 /// Gets the row containing the specified primary key values.
220                 /// </summary>
221                 public DataRow Find (object[] keys) 
222                 {
223                         if (table.PrimaryKey.Length == 0)
224                                 throw new MissingPrimaryKeyException ("Table doesn't have a primary key.");
225
226                         if (keys == null)
227                                 throw new ArgumentException ("Expecting " + table.PrimaryKey.Length +" value(s) for the key being indexed, but received 0 value(s).");
228                         if (table.PrimaryKey.Length != keys.Length)
229                                 throw new ArgumentException ("Expecting " + table.PrimaryKey.Length +" value(s) for the key being indexed, but received " + keys.Length +  " value(s).");
230                                                                                                     
231                         DataColumn[] primaryKey = table.PrimaryKey;
232                         int tmpRecord = table.RecordCache.NewRecord();
233                         try {
234                                 int numColumn = keys.Length;
235                                 for (int i = 0; i < numColumn; i++) {
236                                         // according to MSDN: the DataType value for both columns must be identical.
237                                         primaryKey[i].DataContainer[tmpRecord] = keys[i];
238                                 }
239                                 return Find(tmpRecord,numColumn);
240                         }
241                         finally {
242                                 table.RecordCache.DisposeRecord(tmpRecord);
243                         }
244                 }
245
246                 internal DataRow Find(int index, int length)
247                 {
248                         DataColumn[] primaryKey = table.PrimaryKey;
249                         Index primaryKeyIndex = table.GetIndexByColumns(primaryKey);
250                         // if we can search through index
251                         if (primaryKeyIndex != null) {
252                                 // get the child rows from the index
253                                 Node node = primaryKeyIndex.FindSimple(index,length,true);
254                                 if ( node != null ) {
255                                         return node.Row;
256                                 }
257                         }
258                 
259                         //loop through all collection rows                      
260                         foreach (DataRow row in this) {
261                                 if (row.RowState != DataRowState.Deleted) {
262                                         int rowIndex = row.IndexFromVersion(DataRowVersion.Default);
263                                         bool match = true;
264                                         for (int columnCnt = 0; columnCnt < length; ++columnCnt) { 
265                                                 if (primaryKey[columnCnt].DataContainer.CompareValues(rowIndex, index) != 0) {
266                                                         match = false;
267                                                 }
268                                         }
269                                         if ( match ) {
270                                                 return row;
271                                         }
272                                 }
273                         }
274                         return null;
275                 }
276
277                 /// <summary>
278                 /// Inserts a new row into the collection at the specified location.
279                 /// </summary>
280                 public void InsertAt (DataRow row, int pos) 
281                 {
282                         if (pos < 0)
283                                 throw new IndexOutOfRangeException ("The row insert position " + pos + " is invalid.");
284                         
285                         if (row == null)
286                                 throw new ArgumentNullException("row", "'row' argument cannot be null.");
287         
288                         if (row.Table != this.table)
289                                 throw new ArgumentException ("This row already belongs to another table.");
290
291                         // If row id is not -1, we know that it is in the collection.
292                         if (row.RowID != -1)
293                                 throw new ArgumentException ("This row already belongs to this table.");
294                         
295                         if ((table.DataSet == null || table.DataSet.EnforceConstraints) && !table._duringDataLoad)
296                                 // we have to check that the new row doesn't colide with existing row
297                                 ValidateDataRowInternal(row);
298                                 
299                         row.Table.ChangingDataRow (row, DataRowAction.Add);
300
301                         if (pos >= List.Count) {
302                                 row.RowID = List.Count;
303                                 List.Add (row);
304                         }
305                         else {
306                                 List.Insert (pos, row);
307                                 row.RowID = pos;
308                                 for (int i = pos+1; i < List.Count; i++) {
309                                         ((DataRow)List [i]).RowID = i;
310                                 }
311                         }
312                                 
313                         row.HasParentCollection = true;
314                         row.AttachRow ();
315                         row.Table.ChangedDataRow (row, DataRowAction.Add);
316                 }
317
318                 /// <summary>
319                 /// Removes the specified DataRow from the internal list. Used by DataRow to commit the removing.
320                 /// </summary>
321                 internal void RemoveInternal (DataRow row) {
322                         if (row == null) {
323                                 throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
324                         }
325                         int index = List.IndexOf(row);
326                         if (index < 0) {
327                                 throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
328                         }
329                         List.RemoveAt(index);
330                 }
331
332                 /// <summary>
333                 /// Removes the specified DataRow from the collection.
334                 /// </summary>
335                 public void Remove (DataRow row) 
336                 {
337                         if (row == null)
338                                 throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
339                         int index = List.IndexOf(row);
340                         if (index < 0)
341                                 throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
342                         row.Delete();
343                         // if the row was in added state it will be in Detached state after the
344                         // delete operation, so we have to check it.
345                         if (row.RowState != DataRowState.Detached)
346                                 row.AcceptChanges();
347                 }
348
349                 /// <summary>
350                 /// Removes the row at the specified index from the collection.
351                 /// </summary>
352                 public void RemoveAt (int index) 
353                 {                       
354                         if (index < 0 || index >= List.Count)
355                                 throw new IndexOutOfRangeException ("There is no row at position " + index + ".");
356                         DataRow row = (DataRow)List [index];
357                         row.Delete();
358                         // if the row was in added state it will be in Detached state after the
359                         // delete operation, so we have to check it.
360                         if (row.RowState != DataRowState.Detached)
361                                 row.AcceptChanges();
362                 }
363
364                 ///<summary>
365                 ///Internal method used to validate a given DataRow with respect
366                 ///to the DataRowCollection
367                 ///</summary>
368                 [MonoTODO]
369                 internal void ValidateDataRowInternal(DataRow row)
370                 {
371                         //first check for null violations.
372                         row._nullConstraintViolation = true;
373                         row.CheckNullConstraints();
374                         // This validates constraints in the specific order : 
375                         // first unique/primary keys first, then Foreignkeys, etc
376                         ArrayList uniqueConstraintsDone = new ArrayList();
377                         ArrayList foreignKeyConstraintsDone = new ArrayList();
378                         try {
379                                 foreach(Constraint constraint in table.Constraints.UniqueConstraints) {
380                                         constraint.AssertConstraint(row);
381                                         uniqueConstraintsDone.Add(constraint);
382                                 }
383                         
384                                 foreach(Constraint constraint in table.Constraints.ForeignKeyConstraints) {
385                                         constraint.AssertConstraint(row);
386                                         foreignKeyConstraintsDone.Add(constraint);
387                                 }
388                         }
389                         // if one of the AssertConstraint failed - we need to "rollback" all the changes
390                         // caused by AssertCoinstraint calls already succeeded
391                         catch(ConstraintException e) {
392                                 RollbackAsserts(row,foreignKeyConstraintsDone,uniqueConstraintsDone);
393                                 throw e;
394                         }
395                         catch(InvalidConstraintException e) {   
396                                 RollbackAsserts(row,foreignKeyConstraintsDone,uniqueConstraintsDone);
397                                 throw e;
398                         }
399                 }
400
401                 private void RollbackAsserts(DataRow row,ICollection foreignKeyConstraintsDone,
402                         ICollection uniqueConstraintsDone)
403                 {
404                         // if any of constraints assert failed - 
405                         // we have to rollback all the asserts scceeded
406                         // on order reverse to thier original execution
407                         foreach(Constraint constraint in foreignKeyConstraintsDone) {
408                                 constraint.RollbackAssert(row);
409                         }
410
411                         foreach(Constraint constraint in uniqueConstraintsDone) {
412                                 constraint.RollbackAssert(row);
413                         }
414                 }
415         }
416 }