* DataSet.cs : Don't check constraints for dataset clear
[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.HasParentCollection = true;
101                         List.Add (row);
102                         // Set the row id.
103                         row.RowID = List.Count - 1;
104                         row.AttachRow ();
105                         row.Table.ChangedDataRow (row, DataRowAction.Add);
106                 }
107
108                 /// <summary>
109                 /// Creates a row using specified values and adds it to the DataRowCollection.
110                 /// </summary>
111                 public virtual DataRow Add (object[] values) 
112                 {
113                         if (values.Length > table.Columns.Count)
114                                 throw new ArgumentException ("The array is larger than the number of columns in the table.");
115                         DataRow row = table.NewRow ();
116                         row.ItemArray = values;
117                         Add (row);
118                         return row;
119                 }
120
121                 /// <summary>
122                 /// Clears the collection of all rows.
123                 /// </summary>
124                 public void Clear () 
125                 {
126                         if (this.table.DataSet != null && this.table.DataSet.EnforceConstraints)
127                         {
128                                 foreach (DataTable table in this.table.DataSet.Tables)
129                                 {
130                                         foreach (Constraint c in table.Constraints)
131                                         {
132                                                 if (c is ForeignKeyConstraint)
133                                                 {
134                                                         if (((ForeignKeyConstraint) c).RelatedTable.Equals(this.table))
135 #if NET_1_1
136                                                                 throw new InvalidConstraintException (String.Format ("Cannot clear table Parent because ForeignKeyConstraint {0} enforces Child.", c.ConstraintName));
137 #else
138                                                                 throw new ArgumentException (String.Format ("Cannot clear table Parent because ForeignKeyConstraint {0} enforces Child.", c.ConstraintName));
139 #endif
140                                                 }
141                                         }
142                                 }
143                         }
144                         List.Clear ();
145                 }
146
147                 /// <summary>
148                 /// Gets a value indicating whether the primary key of any row in the collection contains
149                 /// the specified value.
150                 /// </summary>
151                 public bool Contains (object key) 
152                 {
153                         return Find (key) != null;
154                 }
155
156                 /// <summary>
157                 /// Gets a value indicating whether the primary key column(s) of any row in the 
158                 /// collection contains the values specified in the object array.
159                 /// </summary>
160                 public bool Contains (object[] keys) 
161                 {
162                         if (table.PrimaryKey.Length != keys.Length)
163                                 throw new ArgumentException ("Expecting " + table.PrimaryKey.Length + " value(s) for the key " + 
164                                                              "being indexed, but received " + keys.Length + " value(s).");
165
166                         return Find (keys) != null;
167                 }
168
169                 /// <summary>
170                 /// Gets the row specified by the primary key value.
171                 /// </summary>
172                 public DataRow Find (object key) 
173                 {
174                         if (table.PrimaryKey.Length == 0)
175                                 throw new MissingPrimaryKeyException ("Table doesn't have a primary key.");
176                         if (table.PrimaryKey.Length > 1)
177                                 throw new ArgumentException ("Expecting " + table.PrimaryKey.Length +" value(s) for the key being indexed, but received 1 value(s).");
178
179                         if (key == null)
180 #if NET_1_1
181                                 return null;
182 #else
183                                 throw new ArgumentException("Expecting 1 value(s) for the key being indexed, but received 0 value(s).");
184 #endif
185
186                         DataColumn primaryKey = table.PrimaryKey[0];
187                         Index primaryKeyIndex = table.GetIndexByColumns(table.PrimaryKey);
188                         int tmpRecord = table.RecordCache.NewRecord();
189                         try {
190                                 primaryKey.DataContainer[tmpRecord] = key;
191
192                                 // if we can search through index
193                                 if (primaryKeyIndex != null) {
194                                         // get the child rows from the index
195                                         Node node = primaryKeyIndex.FindSimple(tmpRecord,1,true);
196                                         if (node != null) {
197                                                 return node.Row;
198                                         }
199                                 }
200                         
201                                 //loop through all collection rows                      
202                                 foreach (DataRow row in this) {
203                                         if (row.RowState != DataRowState.Deleted) {
204                                                 int index = row.IndexFromVersion(DataRowVersion.Default); 
205                                                 if (primaryKey.DataContainer.CompareValues(index, tmpRecord) == 0) {
206                                                         return row;
207                                                 }
208                                         }
209                                 }
210                                 return null;
211                         }
212                         finally {
213                                 table.RecordCache.DisposeRecord(tmpRecord);
214                         }
215                 }
216
217                 /// <summary>
218                 /// Gets the row containing the specified primary key values.
219                 /// </summary>
220                 public DataRow Find (object[] keys) 
221                 {
222                         if (table.PrimaryKey.Length == 0)
223                                 throw new MissingPrimaryKeyException ("Table doesn't have a primary key.");
224
225                         if (keys == null)
226                                 throw new ArgumentException ("Expecting " + table.PrimaryKey.Length +" value(s) for the key being indexed, but received 0 value(s).");
227                         if (table.PrimaryKey.Length != keys.Length)
228                                 throw new ArgumentException ("Expecting " + table.PrimaryKey.Length +" value(s) for the key being indexed, but received " + keys.Length +  " value(s).");
229                                                                                                     
230                         DataColumn[] primaryKey = table.PrimaryKey;
231                         int tmpRecord = table.RecordCache.NewRecord();
232                         try {
233                                 int numColumn = keys.Length;
234                                 for (int i = 0; i < numColumn; i++) {
235                                         // according to MSDN: the DataType value for both columns must be identical.
236                                         primaryKey[i].DataContainer[tmpRecord] = keys[i];
237                                 }
238                                 return Find(tmpRecord,numColumn);
239                         }
240                         finally {
241                                 table.RecordCache.DisposeRecord(tmpRecord);
242                         }
243                 }
244
245                 internal DataRow Find(int index, int length)
246                 {
247                         DataColumn[] primaryKey = table.PrimaryKey;
248                         Index primaryKeyIndex = table.GetIndexByColumns(primaryKey);
249                         // if we can search through index
250                         if (primaryKeyIndex != null) {
251                                 // get the child rows from the index
252                                 Node node = primaryKeyIndex.FindSimple(index,length,true);
253                                 if ( node != null ) {
254                                         return node.Row;
255                                 }
256                         }
257                 
258                         //loop through all collection rows                      
259                         foreach (DataRow row in this) {
260                                 if (row.RowState != DataRowState.Deleted) {
261                                         int rowIndex = row.IndexFromVersion(DataRowVersion.Default);
262                                         bool match = true;
263                                         for (int columnCnt = 0; columnCnt < length; ++columnCnt) { 
264                                                 if (primaryKey[columnCnt].DataContainer.CompareValues(rowIndex, index) != 0) {
265                                                         match = false;
266                                                 }
267                                         }
268                                         if ( match ) {
269                                                 return row;
270                                         }
271                                 }
272                         }
273                         return null;
274                 }
275
276                 /// <summary>
277                 /// Inserts a new row into the collection at the specified location.
278                 /// </summary>
279                 public void InsertAt (DataRow row, int pos) 
280                 {
281                         if (pos < 0)
282                                 throw new IndexOutOfRangeException ("The row insert position " + pos + " is invalid.");
283                         
284                         if (row == null)
285                                 throw new ArgumentNullException("row", "'row' argument cannot be null.");
286         
287                         if (row.Table != this.table)
288                                 throw new ArgumentException ("This row already belongs to another table.");
289
290                         // If row id is not -1, we know that it is in the collection.
291                         if (row.RowID != -1)
292                                 throw new ArgumentException ("This row already belongs to this table.");
293                         
294                         if ((table.DataSet == null || table.DataSet.EnforceConstraints) && !table._duringDataLoad)
295                                 // we have to check that the new row doesn't colide with existing row
296                                 ValidateDataRowInternal(row);
297                                 
298                         if (pos >= List.Count) {
299                                 row.RowID = List.Count;
300                                 List.Add (row);
301                         }
302                         else {
303                                 List.Insert (pos, row);
304                                 row.RowID = pos;
305                                 for (int i = pos+1; i < List.Count; i++) {
306                                         ((DataRow)List [i]).RowID = i;
307                                 }
308                         }
309                                 
310                         row.HasParentCollection = true;
311                         row.AttachRow ();
312                         row.Table.ChangedDataRow (row, DataRowAction.Add);
313                 }
314
315                 /// <summary>
316                 /// Removes the specified DataRow from the internal list. Used by DataRow to commit the removing.
317                 /// </summary>
318                 internal void RemoveInternal (DataRow row) {
319                         if (row == null) {
320                                 throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
321                         }
322                         int index = List.IndexOf(row);
323                         if (index < 0) {
324                                 throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
325                         }
326                         List.RemoveAt(index);
327                 }
328
329                 /// <summary>
330                 /// Removes the specified DataRow from the collection.
331                 /// </summary>
332                 public void Remove (DataRow row) 
333                 {
334                         if (row == null)
335                                 throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
336                         int index = List.IndexOf(row);
337                         if (index < 0)
338                                 throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
339                         row.Delete();
340                         // if the row was in added state it will be in Detached state after the
341                         // delete operation, so we have to check it.
342                         if (row.RowState != DataRowState.Detached)
343                                 row.AcceptChanges();
344                 }
345
346                 /// <summary>
347                 /// Removes the row at the specified index from the collection.
348                 /// </summary>
349                 public void RemoveAt (int index) 
350                 {                       
351                         if (index < 0 || index >= List.Count)
352                                 throw new IndexOutOfRangeException ("There is no row at position " + index + ".");
353                         DataRow row = (DataRow)List [index];
354                         row.Delete();
355                         // if the row was in added state it will be in Detached state after the
356                         // delete operation, so we have to check it.
357                         if (row.RowState != DataRowState.Detached)
358                                 row.AcceptChanges();
359                 }
360
361                 ///<summary>
362                 ///Internal method used to validate a given DataRow with respect
363                 ///to the DataRowCollection
364                 ///</summary>
365                 [MonoTODO]
366                 internal void ValidateDataRowInternal(DataRow row)
367                 {
368                         //first check for null violations.
369                         row._nullConstraintViolation = true;
370                         row.CheckNullConstraints();
371                         // This validates constraints in the specific order : 
372                         // first unique/primary keys first, then Foreignkeys, etc
373                         ArrayList uniqueConstraintsDone = new ArrayList();
374                         ArrayList foreignKeyConstraintsDone = new ArrayList();
375                         try {
376                                 foreach(Constraint constraint in table.Constraints.UniqueConstraints) {
377                                         constraint.AssertConstraint(row);
378                                         uniqueConstraintsDone.Add(constraint);
379                                 }
380                         
381                                 foreach(Constraint constraint in table.Constraints.ForeignKeyConstraints) {
382                                         constraint.AssertConstraint(row);
383                                         foreignKeyConstraintsDone.Add(constraint);
384                                 }
385                         }
386                         // if one of the AssertConstraint failed - we need to "rollback" all the changes
387                         // caused by AssertCoinstraint calls already succeeded
388                         catch(ConstraintException e) {
389                                 RollbackAsserts(row,foreignKeyConstraintsDone,uniqueConstraintsDone);
390                                 throw e;
391                         }
392                         catch(InvalidConstraintException e) {   
393                                 RollbackAsserts(row,foreignKeyConstraintsDone,uniqueConstraintsDone);
394                                 throw e;
395                         }
396                 }
397
398                 private void RollbackAsserts(DataRow row,ICollection foreignKeyConstraintsDone,
399                         ICollection uniqueConstraintsDone)
400                 {
401                         // if any of constraints assert failed - 
402                         // we have to rollback all the asserts scceeded
403                         // on order reverse to thier original execution
404                         foreach(Constraint constraint in foreignKeyConstraintsDone) {
405                                 constraint.RollbackAssert(row);
406                         }
407
408                         foreach(Constraint constraint in uniqueConstraintsDone) {
409                                 constraint.RollbackAssert(row);
410                         }
411                 }
412         }
413 }