BindingFlags.Public needed here as Exception.HResult is now public in .NET 4.5. This...
[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 using System.Data.Common;
40
41 namespace System.Data
42 {
43         /// <summary>
44         /// Collection of DataRows in a DataTable
45         /// </summary>
46         public partial class DataRowCollection : InternalDataCollectionBase {
47                 private DataTable table;
48
49                 internal event ListChangedEventHandler ListChanged;
50
51                 /// <summary>
52                 /// Internal constructor used to build a DataRowCollection.
53                 /// </summary>
54                 internal DataRowCollection (DataTable table)
55                 {
56                         this.table = table;
57                 }
58
59                 /// <summary>
60                 /// Gets the row at the specified index.
61                 /// </summary>
62                 public DataRow this [int index] {
63                         get {
64                                 if (index < 0 || 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                 /// Adds the specified DataRow to the DataRowCollection object.
73                 /// </summary>
74                 public void Add (DataRow row)
75                 {
76                         //TODO: validation
77                         if (row == null)
78                                 throw new ArgumentNullException ("row", "'row' argument cannot be null.");
79
80                         if (row.Table != this.table)
81                                 throw new ArgumentException ("This row already belongs to another table.");
82
83                         // If row id is not -1, we know that it is in the collection.
84                         if (row.RowID != -1)
85                                 throw new ArgumentException ("This row already belongs to this table.");
86
87                         row.BeginEdit ();
88
89                         row.Validate ();
90
91                         AddInternal (row);
92                 }
93
94 #if NET_2_0
95                 public
96 #else
97                 internal
98 #endif
99                 int IndexOf (DataRow row)
100                 {
101                         if (row == null || row.Table != table)
102                                 return -1;
103
104                         int id = row.RowID;
105                         return (id >= 0 && id < List.Count && row == List [id]) ? id : -1;
106                 }
107
108                 internal void AddInternal (DataRow row)
109                 {
110                         AddInternal (row, DataRowAction.Add);
111                 }
112
113                 internal void AddInternal (DataRow row, DataRowAction action)
114                 {
115                         row.Table.ChangingDataRow (row, action);
116                         List.Add (row);
117                         row.AttachAt (List.Count - 1, action);
118                         row.Table.ChangedDataRow (row, action);
119                         if (row._rowChanged)
120                                 row._rowChanged = false;
121                 }
122
123                 /// <summary>
124                 /// Creates a row using specified values and adds it to the DataRowCollection.
125                 /// </summary>
126 #if NET_2_0
127                 public DataRow Add (params object[] values)
128 #else
129                 public virtual DataRow Add (object[] values)
130 #endif
131                 {
132                         if (values == null)
133                                 throw new NullReferenceException ();
134                         DataRow row = table.NewNotInitializedRow ();
135                         int newRecord = table.CreateRecord (values);
136                         row.ImportRecord (newRecord);
137
138                         row.Validate ();
139                         AddInternal (row);
140                         return row;
141                 }
142
143                 /// <summary>
144                 /// Clears the collection of all rows.
145                 /// </summary>
146                 public void Clear ()
147                 {
148                         if (this.table.DataSet != null && this.table.DataSet.EnforceConstraints) {
149                                 foreach (Constraint c in table.Constraints) {
150                                         UniqueConstraint uc = c as UniqueConstraint;
151                                         if (uc == null)
152                                                 continue;
153                                         if (uc.ChildConstraint == null || uc.ChildConstraint.Table.Rows.Count == 0)
154                                                 continue;
155
156                                         string err = String.Format ("Cannot clear table Parent because " +
157                                                                 "ForeignKeyConstraint {0} enforces Child.", uc.ConstraintName);
158 #if NET_1_1
159                                         throw new InvalidConstraintException (err);
160 #else
161                                         throw new ArgumentException (err);
162 #endif
163                                 }
164                         }
165
166 #if NET_2_0
167                         table.DataTableClearing ();
168 #endif  
169                         List.Clear ();
170
171                         // Remove from indexes
172                         table.ResetIndexes ();
173 #if NET_2_0
174                         table.DataTableCleared ();
175 #endif
176                         OnListChanged (this, new ListChangedEventArgs (ListChangedType.Reset, -1, -1));
177                 }
178
179                 /// <summary>
180                 /// Gets a value indicating whether the primary key of any row in the collection contains
181                 /// the specified value.
182                 /// </summary>
183                 public bool Contains (object key)
184                 {
185                         return Find (key) != null;
186                 }
187
188                 /// <summary>
189                 /// Gets a value indicating whether the primary key column(s) of any row in the
190                 /// collection contains the values specified in the object array.
191                 /// </summary>
192                 public bool Contains (object [] keys)
193                 {
194                         return Find (keys) != null;
195                 }
196
197                 /// <summary>
198                 /// Gets the row specified by the primary key value.
199                 /// </summary>
200                 public DataRow Find (object key)
201                 {
202                         return Find (new object []{key}, DataViewRowState.CurrentRows);
203                 }
204
205                 /// <summary>
206                 /// Gets the row containing the specified primary key values.
207                 /// </summary>
208                 public DataRow Find (object[] keys)
209                 {
210                         return Find (keys, DataViewRowState.CurrentRows);
211                 }
212
213                 /// <summary>
214                 /// Gets the row containing the specified primary key values by searching the rows
215                 /// filtered by the state.
216                 /// </summary>
217                 internal DataRow Find (object [] keys, DataViewRowState rowStateFilter)
218                 {
219                         if (table.PrimaryKey.Length == 0)
220                                 throw new MissingPrimaryKeyException ("Table doesn't have a primary key.");
221
222                         if (keys == null)
223                                 throw new ArgumentException ("Expecting " + table.PrimaryKey.Length +" value(s) for the key being indexed, but received 0 value(s).");
224
225                         Index index = table.GetIndex (table.PrimaryKey, null, rowStateFilter, null, false);
226                         int record = index.Find (keys);
227
228                         if (record != -1 || !table._duringDataLoad)
229                                 return (record != -1 ? table.RecordCache [record] : null);
230
231                         // If the key is not found using Index *and* if DataTable is under BeginLoadData
232                         // then, check all the DataRows for the key
233                         record = table.RecordCache.NewRecord ();
234                         try {
235                                 for (int i=0; i < table.PrimaryKey.Length; ++i)
236                                         table.PrimaryKey [i].DataContainer [record] = keys [i];
237
238                                 bool found;
239                                 foreach (DataRow row in this) {
240                                         int rowIndex = Key.GetRecord (row, rowStateFilter);
241                                         if (rowIndex == -1)
242                                                 continue;
243
244                                         found = true;
245                                         for (int columnCnt = 0; columnCnt < table.PrimaryKey.Length; ++columnCnt) {
246                                                 if (table.PrimaryKey [columnCnt].CompareValues (rowIndex, record) == 0)
247                                                         continue;
248                                                 found = false;
249                                                 break;
250                                         }
251                                         if (found)
252                                                 return row;
253                                 }
254                                 return null;
255                         } finally {
256                                 table.RecordCache.DisposeRecord (record);
257                         }
258                 }
259
260                 /// <summary>
261                 /// Inserts a new row into the collection at the specified location.
262                 /// </summary>
263                 public void InsertAt (DataRow row, int pos)
264                 {
265                         if (pos < 0)
266                                 throw new IndexOutOfRangeException ("The row insert position " + pos + " is invalid.");
267
268                         if (row == null)
269                                 throw new ArgumentNullException ("row", "'row' argument cannot be null.");
270
271                         if (row.Table != this.table)
272                                 throw new ArgumentException ("This row already belongs to another table.");
273
274                         // If row id is not -1, we know that it is in the collection.
275                         if (row.RowID != -1)
276                                 throw new ArgumentException ("This row already belongs to this table.");
277
278                         row.Validate ();
279
280                         row.Table.ChangingDataRow (row, DataRowAction.Add);
281
282                         if (pos >= List.Count) {
283                                 pos = List.Count;
284                                 List.Add (row);
285                         } else {
286                                 List.Insert (pos, row);
287                                 for (int i = pos+1; i < List.Count; i++)
288                                         ((DataRow) List [i]).RowID = i;
289                         }
290
291                         row.AttachAt (pos, DataRowAction.Add);
292                         row.Table.ChangedDataRow (row, DataRowAction.Add);
293                 }
294
295                 /// <summary>
296                 /// Removes the specified DataRow from the internal list. Used by DataRow to commit the removing.
297                 /// </summary>
298                 internal void RemoveInternal (DataRow row)
299                 {
300                         if (row == null)
301                                 throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
302                         int index = List.IndexOf (row);
303                         if (index < 0)
304                                 throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
305                         List.RemoveAt (index);
306                         for (; index < List.Count; ++index)
307                                 ((DataRow) List [index]).RowID = index;
308                 }
309
310                 /// <summary>
311                 /// Removes the specified DataRow from the collection.
312                 /// </summary>
313                 public void Remove (DataRow row)
314                 {
315                         if (IndexOf (row) < 0)
316                                 throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
317
318                         DataRowState state = row.RowState;
319                         if (state != DataRowState.Deleted && state != DataRowState.Detached) {
320                                 row.Delete ();
321                                 // if the row was in added state it will be in Detached state after the
322                                 // delete operation, so we have to check it.
323                                 if (row.RowState != DataRowState.Detached)
324                                         row.AcceptChanges ();
325                         }
326                 }
327
328                 /// <summary>
329                 /// Removes the row at the specified index from the collection.
330                 /// </summary>
331                 public void RemoveAt (int index)
332                 {
333                         Remove (this [index]);
334                 }
335
336                 internal void OnListChanged (object sender, ListChangedEventArgs args)
337                 {
338                         if (ListChanged != null)
339                                 ListChanged (sender, args);
340                 }
341         }
342
343 #if NET_2_0
344         sealed partial class DataRowCollection {
345                 public override int Count {
346                         get { return List.Count; }
347                 }
348
349                 public void CopyTo (DataRow [] array, int index)
350                 {
351                         CopyTo ((Array) array, index);
352                 }
353
354                 public override void CopyTo (Array array, int index)
355                 {
356                         base.CopyTo (array, index);
357                 }
358
359                 public override IEnumerator GetEnumerator ()
360                 {
361                         return base.GetEnumerator ();
362                 }
363         }
364 #else
365         [Serializable]
366         partial class DataRowCollection {
367                 /// <summary>
368                 /// This member overrides InternalDataCollectionBase.List
369                 /// </summary>
370                 protected override ArrayList List {
371                         get { return base.List; }
372                 }
373         }
374 #endif
375 }