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