2 // System.Data.DataRow.cs
5 // Rodrigo Moya <rodrigo@ximian.com>
6 // Daniel Morgan <danmorg@sc.rr.com>
7 // Tim Coleman <tim@timcoleman.com>
8 // Ville Palo <vi64pa@koti.soon.fi>
9 // Alan Tam Siu Lung <Tam@SiuLung.com>
10 // Sureshkumar T <tsureshkumar@novell.com>
11 // Veerapuram Varadhan <vvaradhan@novell.com>
13 // (C) Ximian, Inc 2002
14 // (C) Daniel Morgan 2002, 2003
15 // Copyright (C) 2002 Tim Coleman
19 // Copyright (C) 2004-2009 Novell, Inc (http://www.novell.com)
21 // Permission is hereby granted, free of charge, to any person obtaining
22 // a copy of this software and associated documentation files (the
23 // "Software"), to deal in the Software without restriction, including
24 // without limitation the rights to use, copy, modify, merge, publish,
25 // distribute, sublicense, and/or sell copies of the Software, and to
26 // permit persons to whom the Software is furnished to do so, subject to
27 // the following conditions:
29 // The above copyright notice and this permission notice shall be
30 // included in all copies or substantial portions of the Software.
32 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
33 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
34 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
35 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
36 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
37 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
38 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
42 using System.Data.Common;
43 using System.Collections;
44 using System.Globalization;
47 using System.ComponentModel;
50 namespace System.Data {
52 /// Represents a row of data in a DataTable.
57 public class DataRow {
60 private DataTable _table;
62 internal int _original = -1;
63 internal int _current = -1;
64 internal int _proposed = -1;
66 private ArrayList _columnErrors;
67 private string rowError;
68 internal int xmlRowID = 0;
69 internal bool _nullConstraintViolation;
70 private string _nullConstraintMessage;
71 private bool _inChangingEvent;
73 internal bool _rowChanged = false;
75 private XmlDataDocument.XmlDataElement mappedElement;
76 internal bool _inExpressionEvaluation = false;
83 /// This member supports the .NET Framework infrastructure and is not intended to be
84 /// used directly from your code.
86 protected internal DataRow (DataRowBuilder builder)
88 _table = builder.Table;
89 // Get the row id from the builder.
90 _rowId = builder._rowId;
92 rowError = String.Empty;
95 internal DataRow (DataTable table, int rowId)
101 #endregion // Constructors
105 private ArrayList ColumnErrors {
107 if (_columnErrors == null)
108 _columnErrors = new ArrayList ();
109 return _columnErrors;
112 set { _columnErrors = value; }
116 /// Gets a value indicating whether there are errors in a row.
118 public bool HasErrors {
120 if (RowError != string.Empty)
123 foreach (String columnError in ColumnErrors) {
124 if (columnError != null && columnError != string.Empty)
132 /// Gets or sets the data stored in the column specified by name.
134 public object this [string columnName] {
135 get { return this [columnName, DataRowVersion.Default]; }
137 DataColumn column = GetColumn (columnName);
138 this [column.Ordinal] = value;
143 /// Gets or sets the data stored in specified DataColumn
145 public object this [DataColumn column] {
146 get { return this [column, DataRowVersion.Default]; }
149 throw new ArgumentNullException ("column");
150 int columnIndex = _table.Columns.IndexOf (column);
151 if (columnIndex == -1)
152 throw new ArgumentException (string.Format (CultureInfo.InvariantCulture,
153 "The column '{0}' does not belong to the table : {1}.",
154 column.ColumnName, _table.TableName));
155 this [columnIndex] = value;
160 /// Gets or sets the data stored in column specified by index.
162 public object this [int columnIndex] {
163 get { return this [columnIndex, DataRowVersion.Default]; }
165 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
166 throw new IndexOutOfRangeException ();
167 if (RowState == DataRowState.Deleted)
168 throw new DeletedRowInaccessibleException ();
170 DataColumn column = _table.Columns [columnIndex];
171 _table.ChangingDataColumn (this, column, value);
174 if (value == null && column.DataType.IsValueType)
176 if (value == null && column.DataType != typeof (string))
178 throw new ArgumentException ("Canot set column '"
179 + column.ColumnName + "' to be null."
180 + " Please use DBNull instead.");
184 CheckValue (value, column);
185 bool already_editing = Proposed >= 0;
186 if (!already_editing)
189 column [Proposed] = value;
190 _table.ChangedDataColumn (this, column, value);
192 if (!already_editing)
198 /// Gets the specified version of data stored in the named column.
200 public object this [string columnName, DataRowVersion version] {
202 DataColumn column = GetColumn (columnName);
203 return this [column.Ordinal, version];
208 /// Gets the specified version of data stored in the specified DataColumn.
210 public object this [DataColumn column, DataRowVersion version] {
213 throw new ArgumentNullException ("column");
214 if (column.Table != Table)
215 throw new ArgumentException (string.Format (CultureInfo.InvariantCulture,
216 "The column '{0}' does not belong to the table : {1}.",
217 column.ColumnName, _table.TableName));
218 return this [column.Ordinal, version];
223 /// Set a value for the column into the offset specified by the version.<br>
224 /// If the value is auto increment or null, necessary auto increment value
225 /// or the default value will be used.
227 internal void SetValue (int column, object value, int version)
229 DataColumn dc = Table.Columns [column];
231 if (value == null && !dc.AutoIncrement) // set default value / auto increment
232 value = dc.DefaultValue;
234 Table.ChangingDataColumn (this, dc, value);
235 CheckValue (value, dc);
236 if (!dc.AutoIncrement)
237 dc [version] = value;
238 else if (_proposed >= 0 && _proposed != version) // proposed holds the AI
239 dc [version] = dc [_proposed];
243 /// Gets the data stored in the column, specified by index and version of the data to
246 public object this [int columnIndex, DataRowVersion version] {
248 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
249 throw new IndexOutOfRangeException ();
251 DataColumn column = _table.Columns [columnIndex];
252 int recordIndex = IndexFromVersion (version);
254 if (column.Expression != String.Empty && _table.Rows.IndexOf (this) != -1) {
255 // FIXME: how does this handle 'version'?
256 // TODO: Can we avoid the Eval each time by using the cached value?
257 object o = column.CompiledExpression.Eval (this);
258 if (o != null && o != DBNull.Value)
259 o = Convert.ChangeType (o, column.DataType);
260 column [recordIndex] = o;
261 return column [recordIndex];
264 return column [recordIndex];
269 /// Gets or sets all of the values for this row through an array.
271 public object [] ItemArray {
273 // Accessing deleted rows
274 if (RowState == DataRowState.Deleted)
275 throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
278 if (RowState == DataRowState.Detached) {
279 // Check if datarow is removed from the table.
281 throw new RowNotInTableException (
282 "This row has been removed from a table and does not have any data."
283 + " BeginEdit() will allow creation of new data in this row.");
287 object[] items = new object [_table.Columns.Count];
289 foreach(DataColumn column in _table.Columns)
290 items [column.Ordinal] = column [index];
294 if (value.Length > _table.Columns.Count)
295 throw new ArgumentException ();
297 if (RowState == DataRowState.Deleted)
298 throw new DeletedRowInaccessibleException ();
302 DataColumnChangeEventArgs e = new DataColumnChangeEventArgs ();
303 foreach (DataColumn column in _table.Columns) {
304 int i = column.Ordinal;
305 object newVal = i < value.Length ? value [i] : null;
310 e.Initialize (this, column, newVal);
311 CheckValue (e.ProposedValue, column);
312 _table.RaiseOnColumnChanging (e);
313 column [Proposed] = e.ProposedValue;
314 _table.RaiseOnColumnChanged (e);
322 /// Gets the current state of the row in regards to its relationship to the
323 /// DataRowCollection.
325 public DataRowState RowState {
328 if (Original == -1 && Current == -1)
329 return DataRowState.Detached;
330 if (Original == Current)
331 return DataRowState.Unchanged;
333 return DataRowState.Added;
335 return DataRowState.Deleted;
336 return DataRowState.Modified;
340 if (DataRowState.Detached == value) {
344 if (DataRowState.Unchanged == value)
346 if (DataRowState.Added == value)
348 if (DataRowState.Deleted == value)
355 public void SetAdded ()
357 if (RowState != DataRowState.Unchanged)
358 throw new InvalidOperationException ("SetAdded and SetModified can only be called on DataRows with Unchanged DataRowState.");
362 public void SetModified ()
364 if (RowState != DataRowState.Unchanged)
365 throw new InvalidOperationException ("SetAdded and SetModified can only be called on DataRows with Unchanged DataRowState.");
366 Current = _table.RecordCache.NewRecord ();
367 _table.RecordCache.CopyRecord (_table, Original, Current);
372 /// Gets the DataTable for which this row has a schema.
374 public DataTable Table {
375 get { return _table; }
377 internal set { _table = value; }
382 /// Gets and sets index of row. This is used from
385 internal int XmlRowID {
386 get { return xmlRowID; }
387 set { xmlRowID = value; }
391 /// Gets and sets index of row.
394 get { return _rowId; }
395 set { _rowId = value; }
398 internal int Original {
399 get { return _original; }
402 //Table.RecordCache[_original] = null;
403 Table.RecordCache [value] = this;
409 internal int Current {
410 get { return _current; }
413 //Table.RecordCache[_current] = null;
414 Table.RecordCache [value] = this;
420 internal int Proposed {
421 get { return _proposed; }
424 //Table.RecordCache[_proposed] = null;
425 Table.RecordCache [value] = this;
434 // Called by DataRowCollection.Add/InsertAt
435 internal void AttachAt (int row_id, DataRowAction action)
438 if (Proposed != -1) {
440 Table.RecordCache.DisposeRecord (Current);
445 if ((action & (DataRowAction.ChangeCurrentAndOriginal | DataRowAction.ChangeOriginal)) != 0)
452 Table.DeleteRowFromIndexes (this);
453 _table.Rows.RemoveInternal (this);
455 if (Proposed >= 0 && Proposed != Current && Proposed != Original)
456 _table.RecordCache.DisposeRecord (Proposed);
459 if (Current >= 0 && Current != Original)
460 _table.RecordCache.DisposeRecord (Current);
464 _table.RecordCache.DisposeRecord (Original);
470 internal void ImportRecord (int record)
472 if (HasVersion (DataRowVersion.Proposed))
473 Table.RecordCache.DisposeRecord (Proposed);
477 foreach (DataColumn column in Table.Columns.AutoIncrmentColumns)
478 column.UpdateAutoIncrementValue (column.DataContainer.GetInt64 (Proposed));
480 foreach (DataColumn col in Table.Columns)
481 CheckValue (this [col], col, false);
484 private void CheckValue (object v, DataColumn col)
486 CheckValue (v, col, true);
489 private void CheckValue (object v, DataColumn col, bool doROCheck)
491 if (doROCheck && _rowId != -1 && col.ReadOnly)
492 throw new ReadOnlyException ();
494 if (v == null || v == DBNull.Value) {
495 if (col.AllowDBNull || col.AutoIncrement || col.DefaultValue != DBNull.Value)
498 //Constraint violations during data load is raise in DataTable EndLoad
499 this._nullConstraintViolation = true;
500 if (this.Table._duringDataLoad || (Table.DataSet != null && !Table.DataSet.EnforceConstraints))
501 this.Table._nullConstraintViolationDuringDataLoad = true;
502 _nullConstraintMessage = "Column '" + col.ColumnName + "' does not allow nulls.";
507 /// Gets or sets the custom error description for a row.
509 public string RowError {
510 get { return rowError; }
511 set { rowError = value; }
514 internal int IndexFromVersion (DataRowVersion version)
517 case DataRowVersion.Default:
525 throw new RowNotInTableException ("This row has been removed from a table and does not have any data. BeginEdit() will allow creation of new data in this row.");
527 throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
529 case DataRowVersion.Proposed:
530 return AssertValidVersionIndex (version, Proposed);
531 case DataRowVersion.Current:
532 return AssertValidVersionIndex (version, Current);
533 case DataRowVersion.Original:
534 return AssertValidVersionIndex (version, Original);
536 throw new DataException ("Version must be Original, Current, or Proposed.");
540 private int AssertValidVersionIndex (DataRowVersion version, int index)
545 throw new VersionNotFoundException (String.Format ("There is no {0} data to access.", version));
548 internal DataRowVersion VersionFromIndex (int index)
551 throw new ArgumentException ("Index must not be negative.");
553 // the order of ifs matters
554 if (index == Current)
555 return DataRowVersion.Current;
556 if (index == Original)
557 return DataRowVersion.Original;
558 if (index == Proposed)
559 return DataRowVersion.Proposed;
561 throw new ArgumentException (String.Format ("The index {0} does not belong to this row.", index));
564 internal XmlDataDocument.XmlDataElement DataElement {
566 if (mappedElement != null || _table.DataSet == null || _table.DataSet._xmlDataDocument == null)
567 return mappedElement;
569 // create mapped XmlDataElement
570 mappedElement = new XmlDataDocument.XmlDataElement (
571 this, _table.Prefix, XmlHelper.Encode (_table.TableName),
572 _table.Namespace, _table.DataSet._xmlDataDocument);
573 return mappedElement;
575 set { mappedElement = value; }
578 internal void SetOriginalValue (string columnName, object val)
580 DataColumn column = _table.Columns [columnName];
581 _table.ChangingDataColumn (this, column, val);
583 if (Original < 0 || Original == Current) {
584 Original = Table.RecordCache.NewRecord ();
586 foreach (DataColumn col in _table.Columns)
587 col.DataContainer.CopyValue (Table.DefaultValuesRowIndex, Original);
590 CheckValue (val, column);
591 column [Original] = val;
595 /// Commits all the changes made to this row since the last time AcceptChanges was
598 public void AcceptChanges ()
600 EndEdit (); // in case it hasn't been called
602 _table.ChangingDataRow (this, DataRowAction.Commit);
603 CheckChildRows (DataRowAction.Commit);
605 case DataRowState.Added:
606 case DataRowState.Modified:
608 Table.RecordCache.DisposeRecord (Original);
611 case DataRowState.Deleted:
614 case DataRowState.Detached:
615 throw new RowNotInTableException("Cannot perform this operation on a row not in the table.");
618 _table.ChangedDataRow (this, DataRowAction.Commit);
622 /// Begins an edit operation on a DataRow object.
625 [EditorBrowsable (EditorBrowsableState.Advanced)]
627 public void BeginEdit ()
629 if (_inChangingEvent)
630 throw new InRowChangingEventException ("Cannot call BeginEdit inside an OnRowChanging event.");
631 if (RowState == DataRowState.Deleted)
632 throw new DeletedRowInaccessibleException ();
634 if (!HasVersion (DataRowVersion.Proposed)) {
635 Proposed = Table.RecordCache.NewRecord ();
636 int from = HasVersion (DataRowVersion.Current) ? Current : Table.DefaultValuesRowIndex;
637 for (int i = 0; i < Table.Columns.Count; i++){
638 DataColumn column = Table.Columns [i];
639 column.DataContainer.CopyValue (from, Proposed);
645 /// Cancels the current edit on the row.
648 [EditorBrowsable (EditorBrowsableState.Advanced)]
650 public void CancelEdit ()
652 if (_inChangingEvent)
653 throw new InRowChangingEventException ("Cannot call CancelEdit inside an OnRowChanging event.");
655 if (HasVersion (DataRowVersion.Proposed)) {
656 int oldRecord = Proposed;
657 DataRowState oldState = RowState;
658 Table.RecordCache.DisposeRecord(Proposed);
661 foreach (Index index in Table.Indexes)
662 index.Update (this, oldRecord, DataRowVersion.Proposed, oldState);
667 /// Clears the errors for the row, including the RowError and errors set with
670 public void ClearErrors ()
672 rowError = String.Empty;
673 ColumnErrors.Clear();
677 /// Deletes the DataRow.
679 public void Delete ()
681 _table.DeletingDataRow (this, DataRowAction.Delete);
683 case DataRowState.Added:
684 CheckChildRows (DataRowAction.Delete);
687 case DataRowState.Deleted:
688 case DataRowState.Detached:
691 // check what to do with child rows
692 CheckChildRows (DataRowAction.Delete);
696 int current = Current;
697 DataRowState oldState = RowState;
698 if (Current != Original)
699 _table.RecordCache.DisposeRecord (Current);
701 foreach (Index index in Table.Indexes)
702 index.Update (this, current, DataRowVersion.Current, oldState);
704 _table.DeletedDataRow (this, DataRowAction.Delete);
707 // check the child rows of this row before deleting the row.
708 private void CheckChildRows (DataRowAction action)
710 DataSet ds = _table.DataSet;
712 if (ds == null || !ds.EnforceConstraints)
715 // if the table we're attached-to doesn't have an constraints, no foreign keys are pointing to us ...
716 if (_table.Constraints.Count == 0)
719 foreach (DataTable table in ds.Tables) {
720 // loop on all ForeignKeyConstrain of the table.
721 foreach (Constraint constraint in table.Constraints) {
722 ForeignKeyConstraint fk = constraint as ForeignKeyConstraint;
723 if (fk == null || fk.RelatedTable != _table)
727 case DataRowAction.Delete:
728 CheckChildRows (fk, action, fk.DeleteRule);
730 case DataRowAction.Commit:
731 case DataRowAction.Rollback:
732 if (fk.AcceptRejectRule != AcceptRejectRule.None)
733 CheckChildRows (fk, action, Rule.Cascade);
736 CheckChildRows (fk, action, fk.UpdateRule);
743 private void CheckChildRows (ForeignKeyConstraint fkc, DataRowAction action, Rule rule)
745 DataRow [] childRows = GetChildRows (fkc, DataRowVersion.Current);
746 if (childRows == null)
750 case Rule.Cascade: // delete or change all relted rows.
752 case DataRowAction.Delete:
753 for (int j = 0; j < childRows.Length; j++) {
754 if (childRows [j].RowState != DataRowState.Deleted)
755 childRows [j].Delete ();
758 case DataRowAction.Change:
759 for (int j = 0; j < childRows.Length; j++) {
760 // if action is change we change the values in the child row
761 // change only the values in the key columns
762 // set the childcolumn value to the new parent row value
763 for (int k = 0; k < fkc.Columns.Length; k++)
764 if (!fkc.RelatedColumns [k].DataContainer [Current].Equals (fkc.RelatedColumns [k].DataContainer [Proposed]))
765 childRows [j][fkc.Columns [k]] = this [fkc.RelatedColumns [k], DataRowVersion.Proposed];
768 case DataRowAction.Rollback:
769 for (int j = 0; j < childRows.Length; j++) {
770 if (childRows [j].RowState != DataRowState.Unchanged)
771 childRows [j].RejectChanges ();
776 case Rule.None: // throw an exception if there are any child rows.
777 for (int j = 0; j < childRows.Length; j++) {
778 if (childRows[j].RowState != DataRowState.Deleted) {
779 string changeStr = "Cannot change this row because constraints are enforced on relation " + fkc.ConstraintName +", and changing this row will strand child rows.";
780 string delStr = "Cannot delete this row because constraints are enforced on relation " + fkc.ConstraintName +", and deleting this row will strand child rows.";
781 string message = action == DataRowAction.Delete ? delStr : changeStr;
782 throw new InvalidConstraintException (message);
786 case Rule.SetDefault: // set the values in the child rows to the default value of the columns.
787 if (childRows.Length > 0) {
788 int defaultValuesRowIndex = childRows [0].Table.DefaultValuesRowIndex;
789 foreach (DataRow childRow in childRows) {
790 if (childRow.RowState != DataRowState.Deleted) {
791 int defaultIdx = childRow.IndexFromVersion (DataRowVersion.Default);
792 foreach (DataColumn column in fkc.Columns)
793 column.DataContainer.CopyValue (defaultValuesRowIndex, defaultIdx);
798 case Rule.SetNull: // set the values in the child row to null.
799 for (int j = 0; j < childRows.Length; j++) {
800 DataRow child = childRows [j];
801 if (childRows[j].RowState != DataRowState.Deleted) {
802 // set only the key columns to DBNull
803 for (int k = 0; k < fkc.Columns.Length; k++)
804 child.SetNull (fkc.Columns[k]);
812 /// Ends the edit occurring on the row.
815 [EditorBrowsable (EditorBrowsableState.Advanced)]
817 public void EndEdit ()
819 if (_inChangingEvent)
820 throw new InRowChangingEventException ("Cannot call EndEdit inside an OnRowChanging event.");
822 if (RowState == DataRowState.Detached || !HasVersion (DataRowVersion.Proposed))
825 CheckReadOnlyStatus ();
827 _inChangingEvent = true;
829 _table.ChangingDataRow (this, DataRowAction.Change);
831 _inChangingEvent = false;
834 DataRowState oldState = RowState;
836 int oldRecord = Current;
841 //FIXME : ideally indexes shouldnt be maintained during dataload.But this needs to
842 //be implemented at multiple places.For now, just maintain the index.
843 //if (!Table._duringDataLoad) {
844 foreach (Index index in Table.Indexes)
845 index.Update (this, oldRecord, DataRowVersion.Current, oldState);
849 AssertConstraints ();
851 // restore previous state to let the cascade update to find the rows
855 CheckChildRows (DataRowAction.Change);
861 int proposed = Proposed >= 0 ? Proposed : Current;
863 //if (!Table._duringDataLoad) {
864 foreach (Index index in Table.Indexes)
865 index.Update (this, proposed, DataRowVersion.Current, RowState);
870 if (Original != oldRecord)
871 Table.RecordCache.DisposeRecord (oldRecord);
873 // Note : row state must not be changed before all the job on indexes finished,
874 // since the indexes works with recods rather than with rows and the decision
875 // which of row records to choose depends on row state.
876 if (_rowChanged == true) {
877 _table.ChangedDataRow (this, DataRowAction.Change);
883 /// Gets the child rows of this DataRow using the specified DataRelation.
885 public DataRow [] GetChildRows (DataRelation relation)
887 return GetChildRows (relation, DataRowVersion.Default);
891 /// Gets the child rows of a DataRow using the specified RelationName of a
894 public DataRow [] GetChildRows (string relationName)
896 return GetChildRows (Table.DataSet.Relations [relationName]);
900 /// Gets the child rows of a DataRow using the specified DataRelation, and
903 public DataRow[] GetChildRows (DataRelation relation, DataRowVersion version)
905 if (relation == null)
906 return Table.NewRowArray (0);
908 if (this.Table == null)
909 throw new RowNotInTableException ("This row has been removed from a table and does not have any data. BeginEdit() will allow creation of new data in this row.");
911 if (relation.DataSet != this.Table.DataSet)
912 throw new ArgumentException ();
914 if (_table != relation.ParentTable)
915 throw new InvalidConstraintException ("GetChildRow requires a row whose Table is " + relation.ParentTable + ", but the specified row's table is " + _table);
917 if (relation.ChildKeyConstraint != null)
918 return GetChildRows (relation.ChildKeyConstraint, version);
920 ArrayList rows = new ArrayList ();
921 DataColumn[] parentColumns = relation.ParentColumns;
922 DataColumn[] childColumns = relation.ChildColumns;
923 int numColumn = parentColumns.Length;
924 DataRow[] result = null;
926 int versionIndex = IndexFromVersion (version);
927 int tmpRecord = relation.ChildTable.RecordCache.NewRecord ();
930 for (int i = 0; i < numColumn; i++)
931 // according to MSDN: the DataType value for both columns must be identical.
932 childColumns [i].DataContainer.CopyValue (parentColumns [i].DataContainer, versionIndex, tmpRecord);
934 Index index = relation.ChildTable.FindIndex (childColumns);
937 int [] records = index.FindAll (tmpRecord);
938 result = relation.ChildTable.NewRowArray (records.Length);
939 for(int i = 0; i < records.Length; i++)
940 result [i] = relation.ChildTable.RecordCache [records [i]];
942 foreach (DataRow row in relation.ChildTable.Rows) {
943 bool allColumnsMatch = false;
944 if (row.HasVersion (DataRowVersion.Default)) {
945 allColumnsMatch = true;
946 int childIndex = row.IndexFromVersion (DataRowVersion.Default);
947 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) {
948 if (childColumns[columnCnt].DataContainer.CompareValues (childIndex, tmpRecord) != 0) {
949 allColumnsMatch = false;
957 result = relation.ChildTable.NewRowArray (rows.Count);
958 rows.CopyTo (result, 0);
962 relation.ChildTable.RecordCache.DisposeRecord (tmpRecord);
969 /// Gets the child rows of a DataRow using the specified RelationName of a
970 /// DataRelation, and DataRowVersion.
972 public DataRow [] GetChildRows (string relationName, DataRowVersion version)
974 return GetChildRows (Table.DataSet.Relations [relationName], version);
977 private DataRow [] GetChildRows (ForeignKeyConstraint fkc, DataRowVersion version)
979 ArrayList rows = new ArrayList ();
980 DataColumn [] parentColumns = fkc.RelatedColumns;
981 DataColumn [] childColumns = fkc.Columns;
982 int numColumn = parentColumns.Length;
984 Index index = fkc.Index;
986 int curIndex = IndexFromVersion (version);
987 int tmpRecord = fkc.Table.RecordCache.NewRecord ();
988 for (int i = 0; i < numColumn; i++)
989 // according to MSDN: the DataType value for both columns must be identical.
990 childColumns [i].DataContainer.CopyValue (parentColumns [i].DataContainer, curIndex, tmpRecord);
994 // get the child rows from the index
995 int[] childRecords = index.FindAll (tmpRecord);
996 for (int i = 0; i < childRecords.Length; i++)
997 rows.Add (childColumns [i].Table.RecordCache [childRecords [i]]);
998 } else { // if there is no index we search manualy.
999 foreach (DataRow row in fkc.Table.Rows) {
1000 bool allColumnsMatch = false;
1001 if (row.HasVersion (DataRowVersion.Default)) {
1002 allColumnsMatch = true;
1003 int childIndex = row.IndexFromVersion (DataRowVersion.Default);
1004 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) {
1005 if (childColumns [columnCnt].DataContainer.CompareValues (childIndex, tmpRecord) != 0) {
1006 allColumnsMatch = false;
1011 if (allColumnsMatch)
1016 fkc.Table.RecordCache.DisposeRecord(tmpRecord);
1019 DataRow[] result = fkc.Table.NewRowArray (rows.Count);
1020 rows.CopyTo (result, 0);
1025 /// Gets the error description of the specified DataColumn.
1027 public string GetColumnError (DataColumn column)
1030 throw new ArgumentNullException ("column");
1032 int index = _table.Columns.IndexOf (column);
1034 throw new ArgumentException (String.Format ("Column '{0}' does not belong to table {1}.", column.ColumnName, Table.TableName));
1036 return GetColumnError (index);
1040 /// Gets the error description for the column specified by index.
1042 public string GetColumnError (int columnIndex)
1044 if (columnIndex < 0 || columnIndex >= Table.Columns.Count)
1045 throw new IndexOutOfRangeException ();
1047 string retVal = null;
1048 if (columnIndex < ColumnErrors.Count)
1049 retVal = (String) ColumnErrors [columnIndex];
1050 return (retVal != null) ? retVal : String.Empty;
1054 /// Gets the error description for the column, specified by name.
1056 public string GetColumnError (string columnName)
1058 return GetColumnError (_table.Columns.IndexOf (columnName));
1062 /// Gets an array of columns that have errors.
1064 public DataColumn [] GetColumnsInError ()
1066 ArrayList dataColumns = new ArrayList ();
1068 int columnOrdinal = 0;
1069 foreach (String columnError in ColumnErrors) {
1070 if (columnError != null && columnError != String.Empty)
1071 dataColumns.Add (_table.Columns [columnOrdinal]);
1075 return (DataColumn [])(dataColumns.ToArray (typeof (DataColumn)));
1079 /// Gets the parent row of a DataRow using the specified DataRelation.
1081 public DataRow GetParentRow (DataRelation relation)
1083 return GetParentRow (relation, DataRowVersion.Default);
1087 /// Gets the parent row of a DataRow using the specified RelationName of a
1090 public DataRow GetParentRow (string relationName)
1092 return GetParentRow (relationName, DataRowVersion.Default);
1096 /// Gets the parent row of a DataRow using the specified DataRelation, and
1099 public DataRow GetParentRow (DataRelation relation, DataRowVersion version)
1101 DataRow[] rows = GetParentRows (relation, version);
1102 if (rows.Length == 0)
1108 /// Gets the parent row of a DataRow using the specified RelationName of a
1109 /// DataRelation, and DataRowVersion.
1111 public DataRow GetParentRow (string relationName, DataRowVersion version)
1113 return GetParentRow (Table.DataSet.Relations [relationName], version);
1117 /// Gets the parent rows of a DataRow using the specified DataRelation.
1119 public DataRow [] GetParentRows (DataRelation relation)
1121 return GetParentRows (relation, DataRowVersion.Default);
1125 /// Gets the parent rows of a DataRow using the specified RelationName of a
1128 public DataRow [] GetParentRows (string relationName)
1130 return GetParentRows (relationName, DataRowVersion.Default);
1134 /// Gets the parent rows of a DataRow using the specified DataRelation, and
1137 public DataRow [] GetParentRows (DataRelation relation, DataRowVersion version)
1139 // TODO: Caching for better preformance
1140 if (relation == null)
1141 return Table.NewRowArray (0);
1143 if (this.Table == null)
1144 throw new RowNotInTableException ("This row has been removed from a table and does not have any data. BeginEdit() will allow creation of new data in this row.");
1146 if (relation.DataSet != this.Table.DataSet)
1147 throw new ArgumentException ();
1149 if (_table != relation.ChildTable)
1150 throw new InvalidConstraintException ("GetParentRows requires a row whose Table is " + relation.ChildTable + ", but the specified row's table is " + _table);
1152 ArrayList rows = new ArrayList ();
1153 DataColumn[] parentColumns = relation.ParentColumns;
1154 DataColumn[] childColumns = relation.ChildColumns;
1155 int numColumn = parentColumns.Length;
1157 int curIndex = IndexFromVersion (version);
1158 int tmpRecord = relation.ParentTable.RecordCache.NewRecord ();
1159 for (int i = 0; i < numColumn; i++)
1160 // according to MSDN: the DataType value for both columns must be identical.
1161 parentColumns [i].DataContainer.CopyValue(childColumns [i].DataContainer, curIndex, tmpRecord);
1164 Index index = relation.ParentTable.FindIndex(parentColumns);
1165 if (index != null) { // get the parent rows from the index
1166 int [] parentRecords = index.FindAll (tmpRecord);
1167 for (int i = 0; i < parentRecords.Length; i++)
1168 rows.Add (parentColumns [i].Table.RecordCache [parentRecords [i]]);
1169 } else { // no index so we have to search manualy.
1170 foreach (DataRow row in relation.ParentTable.Rows) {
1171 bool allColumnsMatch = false;
1172 if (row.HasVersion (DataRowVersion.Default)) {
1173 allColumnsMatch = true;
1174 int parentIndex = row.IndexFromVersion (DataRowVersion.Default);
1175 for (int columnCnt = 0; columnCnt < numColumn; columnCnt++) {
1176 if (parentColumns [columnCnt].DataContainer.CompareValues (parentIndex, tmpRecord) != 0) {
1177 allColumnsMatch = false;
1182 if (allColumnsMatch)
1187 relation.ParentTable.RecordCache.DisposeRecord (tmpRecord);
1190 DataRow [] result = relation.ParentTable.NewRowArray (rows.Count);
1191 rows.CopyTo (result, 0);
1196 /// Gets the parent rows of a DataRow using the specified RelationName of a
1197 /// DataRelation, and DataRowVersion.
1199 public DataRow [] GetParentRows (string relationName, DataRowVersion version)
1201 return GetParentRows (Table.DataSet.Relations [relationName], version);
1205 /// Gets a value indicating whether a specified version exists.
1207 public bool HasVersion (DataRowVersion version)
1210 case DataRowVersion.Default:
1211 return (Proposed >= 0 || Current >= 0);
1212 case DataRowVersion.Proposed:
1213 return Proposed >= 0;
1214 case DataRowVersion.Current:
1215 return Current >= 0;
1216 case DataRowVersion.Original:
1217 return Original >= 0;
1219 return IndexFromVersion (version) >= 0;
1224 /// Gets a value indicating whether the specified DataColumn contains a null value.
1226 public bool IsNull (DataColumn column)
1228 return IsNull (column, DataRowVersion.Default);
1232 /// Gets a value indicating whether the column at the specified index contains a null
1235 public bool IsNull (int columnIndex)
1237 return IsNull (Table.Columns [columnIndex]);
1241 /// Gets a value indicating whether the named column contains a null value.
1243 public bool IsNull (string columnName)
1245 return IsNull (GetColumn (columnName));
1249 /// Gets a value indicating whether the specified DataColumn and DataRowVersion
1250 /// contains a null value.
1252 public bool IsNull (DataColumn column, DataRowVersion version)
1255 throw new ArgumentNullException ("column");
1257 // use the expresion if there is one
1258 if (column.Expression != String.Empty) {
1259 // FIXME: how does this handle 'version'?
1260 // TODO: Can we avoid the Eval each time by using the cached value?
1261 object o = column.CompiledExpression.Eval (this);
1262 return o == null && o == DBNull.Value;
1265 return column.DataContainer.IsNull (IndexFromVersion (version));
1269 /// Returns a value indicating whether all of the row columns specified contain a null value.
1271 internal bool IsNullColumns (DataColumn [] columns)
1274 for (i = 0; i < columns.Length; i++) {
1275 if (!IsNull (columns [i]))
1278 return i == columns.Length;
1282 /// Rejects all changes made to the row since AcceptChanges was last called.
1284 public void RejectChanges ()
1286 if (RowState == DataRowState.Detached)
1287 throw new RowNotInTableException ("This row has been removed from a table and does not have any data. BeginEdit() will allow creation of new data in this row.");
1288 // If original is null, then nothing has happened since AcceptChanges
1289 // was last called. We have no "original" to go back to.
1291 // Varadhan: Following if may be un-necessary
1292 /*if (_inChangingEvent) {
1293 _table.ChangedDataRow (this, DataRowAction.Rollback);
1297 Table.ChangingDataRow (this, DataRowAction.Rollback);
1298 //TODO : Need to Verify the constraints..
1300 case DataRowState.Added:
1303 case DataRowState.Modified:
1304 int current = Current;
1305 Table.RecordCache.DisposeRecord (Current);
1306 CheckChildRows (DataRowAction.Rollback);
1308 foreach (Index index in Table.Indexes)
1309 index.Update (this, current, DataRowVersion.Current, DataRowState.Modified);
1311 case DataRowState.Deleted:
1312 CheckChildRows (DataRowAction.Rollback);
1314 // Add row to index and validate if the constraints are satisfied
1318 Table.ChangedDataRow (this, DataRowAction.Rollback);
1322 /// Sets the error description for a column specified as a DataColumn.
1324 public void SetColumnError (DataColumn column, string error)
1326 SetColumnError (_table.Columns.IndexOf (column), error);
1330 /// Sets the error description for a column specified by index.
1332 public void SetColumnError (int columnIndex, string error)
1334 if (columnIndex < 0 || columnIndex >= Table.Columns.Count)
1335 throw new IndexOutOfRangeException ();
1337 while (columnIndex >= ColumnErrors.Count)
1338 ColumnErrors.Add (null);
1340 ColumnErrors [columnIndex] = error;
1344 /// Sets the error description for a column specified by name.
1346 public void SetColumnError (string columnName, string error)
1348 SetColumnError (_table.Columns.IndexOf (columnName), error);
1352 /// Sets the value of the specified DataColumn to a null value.
1354 protected void SetNull (DataColumn column)
1356 this [column] = DBNull.Value;
1360 /// Sets the parent row of a DataRow with specified new parent DataRow.
1362 public void SetParentRow (DataRow parentRow)
1364 SetParentRow (parentRow, null);
1368 /// Sets the parent row of a DataRow with specified new parent DataRow and
1371 public void SetParentRow (DataRow parentRow, DataRelation relation)
1373 if (_table == null || (parentRow != null && parentRow.Table == null))
1374 throw new RowNotInTableException ("This row has been removed from a table and does not have any data. BeginEdit() will allow creation of new data in this row.");
1376 if (parentRow != null && _table.DataSet != parentRow.Table.DataSet)
1377 throw new ArgumentException ();
1379 if (RowState == DataRowState.Detached && !HasVersion (DataRowVersion.Default))
1380 // the row should have default data to access, i.e. we can do this for the newly created row, but not for the row once deleted from the table
1381 throw new RowNotInTableException ("This row has been removed from a table and does not have any data. BeginEdit() will allow creation of new data in this row.");
1385 IEnumerable relations;
1386 if (relation != null) {
1387 if (parentRow != null && relation.ParentColumns [0].Table != parentRow.Table)
1388 throw new InvalidConstraintException (string.Format ("Parent belongs to table {0} but relation is for table {1}", parentRow.Table, relation.ParentColumns [0].Table));
1389 relations = new DataRelation [] { relation };
1391 relations = _table.ParentRelations;
1394 foreach (DataRelation rel in relations) {
1395 DataColumn [] childCols = rel.ChildColumns;
1396 DataColumn [] parentCols = rel.ParentColumns;
1398 for (int i = 0; i < parentCols.Length; i++) {
1399 if (parentRow == null) {
1400 childCols [i].DataContainer [Proposed] = DBNull.Value;
1401 } else if (parentCols [i].Table == parentRow.Table) {
1402 int defaultIdx = parentRow.IndexFromVersion (DataRowVersion.Default);
1403 childCols [i].DataContainer.CopyValue(parentCols [i].DataContainer, defaultIdx, Proposed);
1411 //Copy all values of this DataRow to the row parameter.
1412 internal void CopyValuesToRow (DataRow row)
1414 CopyValuesToRow(row, true);
1417 internal void CopyValuesToRow (DataRow row, bool doROCheck)
1420 throw new ArgumentNullException("row");
1422 throw new ArgumentException("'row' is the same as this object");
1424 // create target records if missing.
1425 if (HasVersion (DataRowVersion.Original)) {
1426 if (row.Original < 0)
1427 row.Original = row.Table.RecordCache.NewRecord ();
1428 else if (row.Original == row.Current) {
1429 row.Original = row.Table.RecordCache.NewRecord ();
1430 row.Table.RecordCache.CopyRecord (row.Table, row.Current, row.Original);
1433 if (row.Original > 0) {
1434 if (row.Original != row.Current)
1435 row.Table.RecordCache.DisposeRecord (row.Original);
1440 if (HasVersion (DataRowVersion.Current)) {
1441 if (Current == Original) {
1442 if (row.Current >= 0)
1443 row.Table.RecordCache.DisposeRecord (row.Current);
1444 row.Current = row.Original;
1446 if (row.Current < 0)
1447 row.Current = row.Table.RecordCache.NewRecord ();
1450 if (row.Current > 0) {
1451 row.Table.RecordCache.DisposeRecord (row.Current);
1456 if (HasVersion (DataRowVersion.Proposed)) {
1457 if (row.Proposed < 0)
1458 row.Proposed = row.Table.RecordCache.NewRecord ();
1460 if (row.Proposed > 0) {
1461 row.Table.RecordCache.DisposeRecord (row.Proposed);
1466 // copy source record values to target records
1467 foreach (DataColumn column in Table.Columns) {
1468 DataColumn targetColumn = row.Table.Columns [column.ColumnName];
1469 //if a column with the same name exists in both rows copy the values
1470 if (targetColumn != null) {
1471 if (HasVersion (DataRowVersion.Original)) {
1472 object val = column[Original];
1473 row.CheckValue (val, targetColumn, doROCheck);
1474 targetColumn [row.Original] = val;
1477 if (HasVersion (DataRowVersion.Current) && Current != Original) {
1478 object val = column[Current];
1479 row.CheckValue (val, targetColumn, doROCheck);
1480 targetColumn [row.Current] = val;
1483 if (HasVersion (DataRowVersion.Proposed)) {
1484 object val = column[row.Proposed];
1485 row.CheckValue (val, targetColumn, doROCheck);
1486 targetColumn [row.Proposed] = val;
1494 //Merge all values of this DataRow to the row parameter according to merge rules.
1495 internal void MergeValuesToRow (DataRow row, bool preserveChanges)
1498 throw new ArgumentNullException ("row");
1500 throw new ArgumentException ("'row' is the same as this object");
1502 // Original values are anyway copied
1503 if (HasVersion (DataRowVersion.Original)) {
1504 if (row.Original < 0)
1505 row.Original = row.Table.RecordCache.NewRecord ();
1506 else if (row.Original == row.Current && !(Original == Current && !preserveChanges)) {
1507 row.Original = row.Table.RecordCache.NewRecord ();
1508 row.Table.RecordCache.CopyRecord (row.Table, row.Current, row.Original);
1511 if (row.Original == row.Current) { // if target has same current, better create new original
1512 row.Original = row.Table.RecordCache.NewRecord ();
1513 row.Table.RecordCache.CopyRecord (row.Table, row.Current, row.Original);
1517 // if i have current, push all
1518 if (HasVersion (DataRowVersion.Current)) {
1519 if (! preserveChanges && row.Current < 0)
1520 row.Current = row.Table.RecordCache.NewRecord ();
1522 if (row.Current > 0 && ! preserveChanges) {
1523 row.Table.RecordCache.DisposeRecord (row.Current);
1528 // copy source record values to target records
1529 foreach (DataColumn column in Table.Columns) {
1530 DataColumn targetColumn = row.Table.Columns[column.ColumnName];
1531 //if a column with the same name exists in both rows copy the values
1532 if (targetColumn != null) {
1533 if (HasVersion (DataRowVersion.Original)) {
1534 object val = column [Original];
1535 row.CheckValue (val, targetColumn);
1536 targetColumn [row.Original] = val;
1539 if (HasVersion (DataRowVersion.Current) && !preserveChanges) {
1540 object val = column [Current];
1541 row.CheckValue (val, targetColumn);
1542 targetColumn [row.Current] = val;
1551 internal void CopyErrors (DataRow row)
1553 row.RowError = RowError;
1554 DataColumn[] errorColumns = GetColumnsInError();
1555 foreach (DataColumn col in errorColumns) {
1556 DataColumn targetColumn = row.Table.Columns [col.ColumnName];
1557 row.SetColumnError (targetColumn, GetColumnError (col));
1561 internal bool IsRowChanged (DataRowState rowState)
1563 if((RowState & rowState) != 0)
1566 //we need to find if child rows of this row changed.
1567 //if yes - we should return true
1569 // if the rowState is deleted we should get the original version of the row
1570 // else - we should get the current version of the row.
1571 DataRowVersion version = (rowState == DataRowState.Deleted) ? DataRowVersion.Original : DataRowVersion.Current;
1572 int count = Table.ChildRelations.Count;
1573 for (int i = 0; i < count; i++){
1574 DataRelation rel = Table.ChildRelations [i];
1575 DataRow [] childRows = GetChildRows (rel, version);
1576 for (int j = 0; j < childRows.Length; j++){
1577 if (childRows [j].IsRowChanged (rowState))
1585 internal void Validate ()
1587 Table.AddRowToIndexes (this);
1588 AssertConstraints ();
1591 void AssertConstraints ()
1593 if (Table == null || Table._duringDataLoad)
1596 if (Table.DataSet != null && !Table.DataSet.EnforceConstraints)
1598 for (int i = 0; i < Table.Columns.Count; ++i) {
1599 DataColumn column = Table.Columns [i];
1600 if (!column.AllowDBNull && IsNull (column))
1601 throw new NoNullAllowedException (_nullConstraintMessage);
1604 foreach (Constraint constraint in Table.Constraints) {
1606 constraint.AssertConstraint (this);
1607 } catch (Exception e) {
1608 Table.DeleteRowFromIndexes (this);
1614 internal void CheckNullConstraints ()
1616 if (_nullConstraintViolation) {
1617 if (HasVersion (DataRowVersion.Proposed)) {
1618 foreach (DataColumn column in Table.Columns) {
1619 if (IsNull (column) && !column.AllowDBNull)
1620 throw new NoNullAllowedException (_nullConstraintMessage);
1623 _nullConstraintViolation = false;
1627 internal void CheckReadOnlyStatus()
1629 int defaultIdx = IndexFromVersion (DataRowVersion.Default);
1630 foreach(DataColumn column in Table.Columns) {
1631 if ((column.DataContainer.CompareValues (defaultIdx,Proposed) != 0) && column.ReadOnly)
1632 throw new ReadOnlyException ();
1636 #endregion // Methods
1640 /// This method loads a given value into the existing row affecting versions,
1641 /// state based on the LoadOption. The matrix of changes for this method are as
1642 /// mentioned in the DataTable.Load (IDataReader, LoadOption) method.
1644 internal void Load (object [] values, LoadOption loadOption)
1648 if (loadOption == LoadOption.OverwriteChanges ||
1649 (loadOption == LoadOption.PreserveChanges && RowState == DataRowState.Unchanged)) {
1650 Table.ChangingDataRow (this, DataRowAction.ChangeCurrentAndOriginal);
1651 temp = Table.CreateRecord (values);
1652 Table.DeleteRowFromIndexes(this);
1653 if (HasVersion (DataRowVersion.Original) && Current != Original)
1654 Table.RecordCache.DisposeRecord (Original);
1657 if (HasVersion (DataRowVersion.Current))
1658 Table.RecordCache.DisposeRecord (Current);
1660 Table.AddRowToIndexes(this);
1661 Table.ChangedDataRow (this, DataRowAction.ChangeCurrentAndOriginal);
1665 if (loadOption == LoadOption.PreserveChanges) {
1666 Table.ChangingDataRow (this, DataRowAction.ChangeOriginal);
1667 temp = Table.CreateRecord (values);
1668 if (HasVersion (DataRowVersion.Original) && Current != Original)
1669 Table.RecordCache.DisposeRecord (Original);
1671 Table.ChangedDataRow (this, DataRowAction.ChangeOriginal);
1676 if (RowState != DataRowState.Deleted) {
1677 int rindex = HasVersion (DataRowVersion.Proposed) ? Proposed : Current;
1678 temp = Table.CreateRecord (values);
1679 if (RowState == DataRowState.Added || Table.CompareRecords (rindex, temp) != 0) {
1680 Table.ChangingDataRow (this, DataRowAction.Change);
1681 Table.DeleteRowFromIndexes(this);
1682 if (HasVersion (DataRowVersion.Proposed)) {
1683 Table.RecordCache.DisposeRecord (Proposed);
1687 if (Original != Current)
1688 Table.RecordCache.DisposeRecord (Current);
1690 Table.AddRowToIndexes(this);
1691 Table.ChangedDataRow (this, DataRowAction.Change);
1693 Table.ChangingDataRow (this, DataRowAction.Nothing);
1694 Table.RecordCache.DisposeRecord (temp);
1695 Table.ChangedDataRow (this, DataRowAction.Nothing);
1701 DataColumn GetColumn (string columnName)
1703 DataColumn column = _table.Columns [columnName];
1706 throw new ArgumentException ("The column '" + columnName + "' does not belong to the table " + _table.TableName);