2 // System.Data.DataRowCollection.cs
5 // Daniel Morgan <danmorg@sc.rr.com>
6 // Tim Coleman <tim@timcoleman.com>
8 // (C) Ximian, Inc 2002
9 // (C) Copyright 2002 Tim Coleman
10 // (C) Copyright 2002 Daniel Morgan
14 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
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:
24 // The above copyright notice and this permission notice shall be
25 // included in all copies or substantial portions of the Software.
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.
37 using System.Collections;
38 using System.ComponentModel;
43 /// Collection of DataRows in a DataTable
46 public class DataRowCollection : InternalDataCollectionBase
48 private DataTable table;
51 /// Internal constructor used to build a DataRowCollection.
53 internal DataRowCollection (DataTable table) : base ()
59 /// Gets the row at the specified index.
61 public DataRow this[int index]
65 throw new IndexOutOfRangeException ("There is no row at position " + index + ".");
67 return (DataRow) List[index];
72 /// This member overrides InternalDataCollectionBase.List
74 protected override ArrayList List
76 get { return base.List; }
80 /// Adds the specified DataRow to the DataRowCollection object.
82 public void Add (DataRow row)
86 throw new ArgumentNullException("row", "'row' argument cannot be null.");
88 if (row.Table != this.table)
89 throw new ArgumentException ("This row already belongs to another table.");
91 // If row id is not -1, we know that it is in the collection.
93 throw new ArgumentException ("This row already belongs to this table.");
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);
100 row.Table.ChangingDataRow (row, DataRowAction.Add);
101 row.HasParentCollection = true;
104 row.RowID = List.Count - 1;
106 row.Table.ChangedDataRow (row, DataRowAction.Add);
110 /// Creates a row using specified values and adds it to the DataRowCollection.
113 public virtual DataRow Add (params object[] values)
115 public virtual DataRow Add (object[] values)
118 if (values.Length > table.Columns.Count)
119 throw new ArgumentException ("The array is larger than the number of columns in the table.");
120 DataRow row = table.NewRow ();
121 row.ItemArray = values;
127 /// Clears the collection of all rows.
131 if (this.table.DataSet != null && this.table.DataSet.EnforceConstraints) {
132 foreach (DataTable table in this.table.DataSet.Tables) {
133 foreach (Constraint c in table.Constraints) {
134 if (c is ForeignKeyConstraint) {
135 ForeignKeyConstraint fk = (ForeignKeyConstraint) c;
136 if (fk.RelatedTable.Equals(this.table)
137 && fk.Table.Rows.Count > 0) // check does not make sense if we don't have rows
139 throw new InvalidConstraintException (String.Format ("Cannot clear table Parent" +
140 " because ForeignKeyConstraint "+
141 "{0} enforces Child.",
144 throw new ArgumentException (String.Format ("Cannot clear table Parent because " +
145 "ForeignKeyConstraint {0} enforces Child.",
156 /// Gets a value indicating whether the primary key of any row in the collection contains
157 /// the specified value.
159 public bool Contains (object key)
161 return Find (key) != null;
165 /// Gets a value indicating whether the primary key column(s) of any row in the
166 /// collection contains the values specified in the object array.
168 public bool Contains (object[] keys)
170 if (table.PrimaryKey.Length != keys.Length)
171 throw new ArgumentException ("Expecting " + table.PrimaryKey.Length + " value(s) for the key " +
172 "being indexed, but received " + keys.Length + " value(s).");
174 return Find (keys) != null;
178 /// Gets the row specified by the primary key value.
180 public DataRow Find (object key)
182 if (table.PrimaryKey.Length == 0)
183 throw new MissingPrimaryKeyException ("Table doesn't have a primary key.");
184 if (table.PrimaryKey.Length > 1)
185 throw new ArgumentException ("Expecting " + table.PrimaryKey.Length +" value(s) for the key being indexed, but received 1 value(s).");
191 throw new ArgumentException("Expecting 1 value(s) for the key being indexed, but received 0 value(s).");
194 DataColumn primaryKey = table.PrimaryKey[0];
195 Index primaryKeyIndex = table.GetIndexByColumns(table.PrimaryKey);
196 int tmpRecord = table.RecordCache.NewRecord();
198 primaryKey.DataContainer[tmpRecord] = key;
200 // if we can search through index
201 if (primaryKeyIndex != null) {
202 // get the child rows from the index
203 Node node = primaryKeyIndex.FindSimple(tmpRecord,1,true);
209 //loop through all collection rows
210 foreach (DataRow row in this) {
211 if (row.RowState != DataRowState.Deleted) {
212 int index = row.IndexFromVersion(DataRowVersion.Default);
213 if (primaryKey.DataContainer.CompareValues(index, tmpRecord) == 0) {
221 table.RecordCache.DisposeRecord(tmpRecord);
226 /// Gets the row containing the specified primary key values.
228 public DataRow Find (object[] keys)
230 if (table.PrimaryKey.Length == 0)
231 throw new MissingPrimaryKeyException ("Table doesn't have a primary key.");
234 throw new ArgumentException ("Expecting " + table.PrimaryKey.Length +" value(s) for the key being indexed, but received 0 value(s).");
235 if (table.PrimaryKey.Length != keys.Length)
236 throw new ArgumentException ("Expecting " + table.PrimaryKey.Length +" value(s) for the key being indexed, but received " + keys.Length + " value(s).");
238 DataColumn[] primaryKey = table.PrimaryKey;
239 int tmpRecord = table.RecordCache.NewRecord();
241 int numColumn = keys.Length;
242 for (int i = 0; i < numColumn; i++) {
243 // according to MSDN: the DataType value for both columns must be identical.
244 primaryKey[i].DataContainer[tmpRecord] = keys[i];
246 return Find(tmpRecord,numColumn);
249 table.RecordCache.DisposeRecord(tmpRecord);
253 internal DataRow Find(int index, int length)
255 DataColumn[] primaryKey = table.PrimaryKey;
256 Index primaryKeyIndex = table.GetIndexByColumns(primaryKey);
257 // if we can search through index
258 if (primaryKeyIndex != null) {
259 // get the child rows from the index
260 Node node = primaryKeyIndex.FindSimple(index,length,true);
261 if ( node != null ) {
266 //loop through all collection rows
267 foreach (DataRow row in this) {
268 if (row.RowState != DataRowState.Deleted) {
269 int rowIndex = row.IndexFromVersion(DataRowVersion.Default);
271 for (int columnCnt = 0; columnCnt < length; ++columnCnt) {
272 if (primaryKey[columnCnt].DataContainer.CompareValues(rowIndex, index) != 0) {
285 /// Inserts a new row into the collection at the specified location.
287 public void InsertAt (DataRow row, int pos)
290 throw new IndexOutOfRangeException ("The row insert position " + pos + " is invalid.");
293 throw new ArgumentNullException("row", "'row' argument cannot be null.");
295 if (row.Table != this.table)
296 throw new ArgumentException ("This row already belongs to another table.");
298 // If row id is not -1, we know that it is in the collection.
300 throw new ArgumentException ("This row already belongs to this table.");
302 if ((table.DataSet == null || table.DataSet.EnforceConstraints) && !table._duringDataLoad)
303 // we have to check that the new row doesn't colide with existing row
304 ValidateDataRowInternal(row);
306 row.Table.ChangingDataRow (row, DataRowAction.Add);
308 if (pos >= List.Count) {
309 row.RowID = List.Count;
313 List.Insert (pos, row);
315 for (int i = pos+1; i < List.Count; i++) {
316 ((DataRow)List [i]).RowID = i;
320 row.HasParentCollection = true;
322 row.Table.ChangedDataRow (row, DataRowAction.Add);
326 /// Removes the specified DataRow from the internal list. Used by DataRow to commit the removing.
328 internal void RemoveInternal (DataRow row) {
330 throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
332 int index = List.IndexOf(row);
334 throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
336 List.RemoveAt(index);
340 /// Removes the specified DataRow from the collection.
342 public void Remove (DataRow row)
345 throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
346 int index = List.IndexOf(row);
348 throw new IndexOutOfRangeException ("The given datarow is not in the current DataRowCollection.");
350 // if the row was in added state it will be in Detached state after the
351 // delete operation, so we have to check it.
352 if (row.RowState != DataRowState.Detached)
357 /// Removes the row at the specified index from the collection.
359 public void RemoveAt (int index)
361 if (index < 0 || index >= List.Count)
362 throw new IndexOutOfRangeException ("There is no row at position " + index + ".");
363 DataRow row = (DataRow)List [index];
365 // if the row was in added state it will be in Detached state after the
366 // delete operation, so we have to check it.
367 if (row.RowState != DataRowState.Detached)
372 ///Internal method used to validate a given DataRow with respect
373 ///to the DataRowCollection
376 internal void ValidateDataRowInternal(DataRow row)
378 //first check for null violations.
379 row._nullConstraintViolation = true;
380 row.CheckNullConstraints();
381 // This validates constraints in the specific order :
382 // first unique/primary keys first, then Foreignkeys, etc
383 ArrayList uniqueConstraintsDone = new ArrayList();
384 ArrayList foreignKeyConstraintsDone = new ArrayList();
386 foreach(Constraint constraint in table.Constraints.UniqueConstraints) {
387 constraint.AssertConstraint(row);
388 uniqueConstraintsDone.Add(constraint);
391 foreach(Constraint constraint in table.Constraints.ForeignKeyConstraints) {
392 constraint.AssertConstraint(row);
393 foreignKeyConstraintsDone.Add(constraint);
396 // if one of the AssertConstraint failed - we need to "rollback" all the changes
397 // caused by AssertCoinstraint calls already succeeded
398 catch(ConstraintException e) {
399 RollbackAsserts(row,foreignKeyConstraintsDone,uniqueConstraintsDone);
402 catch(InvalidConstraintException e) {
403 RollbackAsserts(row,foreignKeyConstraintsDone,uniqueConstraintsDone);
408 private void RollbackAsserts(DataRow row,ICollection foreignKeyConstraintsDone,
409 ICollection uniqueConstraintsDone)
411 // if any of constraints assert failed -
412 // we have to rollback all the asserts scceeded
413 // on order reverse to thier original execution
414 foreach(Constraint constraint in foreignKeyConstraintsDone) {
415 constraint.RollbackAssert(row);
418 foreach(Constraint constraint in uniqueConstraintsDone) {
419 constraint.RollbackAssert(row);