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>
12 // (C) Ximian, Inc 2002
13 // (C) Daniel Morgan 2002, 2003
14 // Copyright (C) 2002 Tim Coleman
18 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
20 // Permission is hereby granted, free of charge, to any person obtaining
21 // a copy of this software and associated documentation files (the
22 // "Software"), to deal in the Software without restriction, including
23 // without limitation the rights to use, copy, modify, merge, publish,
24 // distribute, sublicense, and/or sell copies of the Software, and to
25 // permit persons to whom the Software is furnished to do so, subject to
26 // the following conditions:
28 // The above copyright notice and this permission notice shall be
29 // included in all copies or substantial portions of the Software.
31 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
32 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
33 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
34 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
35 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
36 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
37 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
41 using System.Data.Common;
42 using System.Collections;
43 using System.Globalization;
46 using System.ComponentModel;
49 namespace System.Data {
51 /// Represents a row of data in a DataTable.
56 public class DataRow {
59 private DataTable _table;
61 internal int _original = -1;
62 internal int _current = -1;
63 internal int _proposed = -1;
65 private ArrayList _columnErrors;
66 private string rowError;
67 internal int xmlRowID = 0;
68 internal bool _nullConstraintViolation;
69 private string _nullConstraintMessage;
70 private bool _inChangingEvent;
72 internal bool _rowChanged = false;
74 private XmlDataDocument.XmlDataElement mappedElement;
75 internal bool _inExpressionEvaluation = false;
82 /// This member supports the .NET Framework infrastructure and is not intended to be
83 /// used directly from your code.
85 protected internal DataRow (DataRowBuilder builder)
87 _table = builder.Table;
88 // Get the row id from the builder.
89 _rowId = builder._rowId;
91 rowError = String.Empty;
94 internal DataRow (DataTable table, int rowId)
100 #endregion // Constructors
104 private ArrayList ColumnErrors {
106 if (_columnErrors == null)
107 _columnErrors = new ArrayList ();
108 return _columnErrors;
111 set { _columnErrors = value; }
115 /// Gets a value indicating whether there are errors in a row.
117 public bool HasErrors {
119 if (RowError != string.Empty)
122 foreach (String columnError in ColumnErrors) {
123 if (columnError != null && columnError != string.Empty)
131 /// Gets or sets the data stored in the column specified by name.
133 public object this [string columnName] {
134 get { return this [columnName, DataRowVersion.Default]; }
136 DataColumn column = _table.Columns [columnName];
138 throw new ArgumentException ("The column '" + columnName +
139 "' does not belong to the table : " + _table.TableName);
140 this [column.Ordinal] = value;
145 /// Gets or sets the data stored in specified DataColumn
147 public object this [DataColumn column] {
148 get { return this [column, DataRowVersion.Default]; }
151 throw new ArgumentNullException ("column");
152 int columnIndex = _table.Columns.IndexOf (column);
153 if (columnIndex == -1)
154 throw new ArgumentException (string.Format (CultureInfo.InvariantCulture,
155 "The column '{0}' does not belong to the table : {1}.",
156 column.ColumnName, _table.TableName));
157 this [columnIndex] = value;
162 /// Gets or sets the data stored in column specified by index.
164 public object this [int columnIndex] {
165 get { return this [columnIndex, DataRowVersion.Default]; }
167 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
168 throw new IndexOutOfRangeException ();
169 if (RowState == DataRowState.Deleted)
170 throw new DeletedRowInaccessibleException ();
172 DataColumn column = _table.Columns [columnIndex];
173 _table.ChangingDataColumn (this, column, value);
176 if (value == null && column.DataType.IsValueType)
178 if (value == null && column.DataType != typeof (string))
180 throw new ArgumentException ("Canot set column '"
181 + column.ColumnName + "' to be null."
182 + " Please use DBNull instead.");
186 CheckValue (value, column);
187 bool already_editing = Proposed >= 0;
188 if (!already_editing)
191 column [Proposed] = value;
192 _table.ChangedDataColumn (this, column, value);
194 if (!already_editing)
200 /// Gets the specified version of data stored in the named column.
202 public object this [string columnName, DataRowVersion version] {
204 DataColumn column = _table.Columns [columnName];
206 throw new ArgumentException ("The column '" + columnName +
207 "' does not belong to the table : " + _table.TableName);
208 return this [column.Ordinal, version];
213 /// Gets the specified version of data stored in the specified DataColumn.
215 public object this [DataColumn column, DataRowVersion version] {
218 throw new ArgumentNullException ("column");
219 if (column.Table != Table)
220 throw new ArgumentException (string.Format (CultureInfo.InvariantCulture,
221 "The column '{0}' does not belong to the table : {1}.",
222 column.ColumnName, _table.TableName));
223 return this [column.Ordinal, version];
228 /// Set a value for the column into the offset specified by the version.<br>
229 /// If the value is auto increment or null, necessary auto increment value
230 /// or the default value will be used.
232 internal void SetValue (int column, object value, int version)
234 DataColumn dc = Table.Columns [column];
236 if (value == null && !dc.AutoIncrement) // set default value / auto increment
237 value = dc.DefaultValue;
239 Table.ChangingDataColumn (this, dc, value);
240 CheckValue (value, dc);
241 if (!dc.AutoIncrement)
242 dc [version] = value;
243 else if (_proposed >= 0 && _proposed != version) // proposed holds the AI
244 dc [version] = dc [_proposed];
248 /// Gets the data stored in the column, specified by index and version of the data to
251 public object this [int columnIndex, DataRowVersion version] {
253 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
254 throw new IndexOutOfRangeException ();
256 DataColumn column = _table.Columns [columnIndex];
257 int recordIndex = IndexFromVersion (version);
259 if (column.Expression != String.Empty && _table.Rows.IndexOf (this) != -1) {
260 // FIXME: how does this handle 'version'?
261 // TODO: Can we avoid the Eval each time by using the cached value?
262 object o = column.CompiledExpression.Eval (this);
263 if (o != null && o != DBNull.Value)
264 o = Convert.ChangeType (o, column.DataType);
265 column [recordIndex] = o;
266 return column [recordIndex];
269 return column [recordIndex];
274 /// Gets or sets all of the values for this row through an array.
276 public object [] ItemArray {
278 // Accessing deleted rows
279 if (RowState == DataRowState.Deleted)
280 throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
283 if (RowState == DataRowState.Detached) {
284 // Check if datarow is removed from the table.
286 throw new RowNotInTableException (
287 "This row has been removed from a table and does not have any data."
288 + " BeginEdit() will allow creation of new data in this row.");
292 object[] items = new object [_table.Columns.Count];
294 foreach(DataColumn column in _table.Columns)
295 items [column.Ordinal] = column [index];
299 if (value.Length > _table.Columns.Count)
300 throw new ArgumentException ();
302 if (RowState == DataRowState.Deleted)
303 throw new DeletedRowInaccessibleException ();
307 DataColumnChangeEventArgs e = new DataColumnChangeEventArgs ();
308 foreach (DataColumn column in _table.Columns) {
309 int i = column.Ordinal;
310 object newVal = i < value.Length ? value [i] : null;
315 e.Initialize (this, column, newVal);
316 CheckValue (e.ProposedValue, column);
317 _table.RaiseOnColumnChanging (e);
318 column [Proposed] = e.ProposedValue;
319 _table.RaiseOnColumnChanged (e);
327 /// Gets the current state of the row in regards to its relationship to the
328 /// DataRowCollection.
330 public DataRowState RowState {
333 if (Original == -1 && Current == -1)
334 return DataRowState.Detached;
335 if (Original == Current)
336 return DataRowState.Unchanged;
338 return DataRowState.Added;
340 return DataRowState.Deleted;
341 return DataRowState.Modified;
345 if (DataRowState.Detached == value) {
349 if (DataRowState.Unchanged == value)
351 if (DataRowState.Added == value)
353 if (DataRowState.Deleted == value)
360 public void SetAdded ()
362 if (RowState != DataRowState.Unchanged)
363 throw new InvalidOperationException ("SetAdded and SetModified can only be called on DataRows with Unchanged DataRowState.");
367 public void SetModified ()
369 if (RowState != DataRowState.Unchanged)
370 throw new InvalidOperationException ("SetAdded and SetModified can only be called on DataRows with Unchanged DataRowState.");
371 Current = _table.RecordCache.NewRecord ();
372 _table.RecordCache.CopyRecord (_table, Original, Current);
377 /// Gets the DataTable for which this row has a schema.
379 public DataTable Table {
380 get { return _table; }
382 internal set { _table = value; }
387 /// Gets and sets index of row. This is used from
390 internal int XmlRowID {
391 get { return xmlRowID; }
392 set { xmlRowID = value; }
396 /// Gets and sets index of row.
399 get { return _rowId; }
400 set { _rowId = value; }
403 internal int Original {
404 get { return _original; }
407 //Table.RecordCache[_original] = null;
408 Table.RecordCache [value] = this;
414 internal int Current {
415 get { return _current; }
418 //Table.RecordCache[_current] = null;
419 Table.RecordCache [value] = this;
425 internal int Proposed {
426 get { return _proposed; }
429 //Table.RecordCache[_proposed] = null;
430 Table.RecordCache [value] = this;
439 // Called by DataRowCollection.Add/InsertAt
440 internal void AttachAt (int row_id, DataRowAction action)
443 if (Proposed != -1) {
445 Table.RecordCache.DisposeRecord (Current);
450 if ((action & (DataRowAction.ChangeCurrentAndOriginal | DataRowAction.ChangeOriginal)) != 0)
457 Table.DeleteRowFromIndexes (this);
458 _table.Rows.RemoveInternal (this);
460 if (Proposed >= 0 && Proposed != Current && Proposed != Original)
461 _table.RecordCache.DisposeRecord (Proposed);
464 if (Current >= 0 && Current != Original)
465 _table.RecordCache.DisposeRecord (Current);
469 _table.RecordCache.DisposeRecord (Original);
475 internal void ImportRecord (int record)
477 if (HasVersion (DataRowVersion.Proposed))
478 Table.RecordCache.DisposeRecord (Proposed);
482 foreach (DataColumn column in Table.Columns.AutoIncrmentColumns)
483 column.UpdateAutoIncrementValue (column.DataContainer.GetInt64 (Proposed));
485 foreach (DataColumn col in Table.Columns)
486 CheckValue (this [col], col, false);
489 void CheckValue (object v, DataColumn col)
491 CheckValue (v, col, true);
494 private void CheckValue (object v, DataColumn col, bool doROCheck)
496 if (doROCheck && _rowId != -1 && col.ReadOnly)
497 throw new ReadOnlyException ();
499 if (v == null || v == DBNull.Value) {
500 if (col.AllowDBNull || col.AutoIncrement || col.DefaultValue != DBNull.Value)
503 //Constraint violations during data load is raise in DataTable EndLoad
504 this._nullConstraintViolation = true;
505 if (this.Table._duringDataLoad || (Table.DataSet != null && !Table.DataSet.EnforceConstraints))
506 this.Table._nullConstraintViolationDuringDataLoad = true;
507 _nullConstraintMessage = "Column '" + col.ColumnName + "' does not allow nulls.";
512 /// Gets or sets the custom error description for a row.
514 public string RowError {
515 get { return rowError; }
516 set { rowError = value; }
519 internal int IndexFromVersion (DataRowVersion version)
522 case DataRowVersion.Default:
530 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.");
532 throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
534 case DataRowVersion.Proposed:
535 return AssertValidVersionIndex (version, Proposed);
536 case DataRowVersion.Current:
537 return AssertValidVersionIndex (version, Current);
538 case DataRowVersion.Original:
539 return AssertValidVersionIndex (version, Original);
541 throw new DataException ("Version must be Original, Current, or Proposed.");
545 private int AssertValidVersionIndex (DataRowVersion version, int index)
550 throw new VersionNotFoundException (String.Format ("There is no {0} data to accces.", version));
553 internal DataRowVersion VersionFromIndex (int index)
556 throw new ArgumentException ("Index must not be negative.");
558 // the order of ifs matters
559 if (index == Current)
560 return DataRowVersion.Current;
561 if (index == Original)
562 return DataRowVersion.Original;
563 if (index == Proposed)
564 return DataRowVersion.Proposed;
566 throw new ArgumentException (String.Format ("The index {0} does not belong to this row.", index));
569 internal XmlDataDocument.XmlDataElement DataElement {
571 if (mappedElement != null || _table.DataSet == null || _table.DataSet._xmlDataDocument == null)
572 return mappedElement;
574 // create mapped XmlDataElement
575 mappedElement = new XmlDataDocument.XmlDataElement (
576 this, _table.Prefix, XmlHelper.Encode (_table.TableName),
577 _table.Namespace, _table.DataSet._xmlDataDocument);
578 return mappedElement;
580 set { mappedElement = value; }
583 internal void SetOriginalValue (string columnName, object val)
585 DataColumn column = _table.Columns [columnName];
586 _table.ChangingDataColumn (this, column, val);
588 if (Original < 0 || Original == Current)
589 Original = Table.RecordCache.NewRecord ();
591 CheckValue (val, column);
592 column [Original] = val;
596 /// Commits all the changes made to this row since the last time AcceptChanges was
599 public void AcceptChanges ()
601 EndEdit (); // in case it hasn't been called
603 _table.ChangingDataRow (this, DataRowAction.Commit);
604 CheckChildRows (DataRowAction.Commit);
606 case DataRowState.Unchanged:
608 case DataRowState.Added:
609 case DataRowState.Modified:
611 Table.RecordCache.DisposeRecord (Original);
614 case DataRowState.Deleted:
617 case DataRowState.Detached:
618 throw new RowNotInTableException("Cannot perform this operation on a row not in the table.");
621 _table.ChangedDataRow (this, DataRowAction.Commit);
625 /// Begins an edit operation on a DataRow object.
628 [EditorBrowsable (EditorBrowsableState.Advanced)]
630 public void BeginEdit ()
632 if (_inChangingEvent)
633 throw new InRowChangingEventException ("Cannot call BeginEdit inside an OnRowChanging event.");
634 if (RowState == DataRowState.Deleted)
635 throw new DeletedRowInaccessibleException ();
637 if (!HasVersion (DataRowVersion.Proposed)) {
638 Proposed = Table.RecordCache.NewRecord ();
639 int from = HasVersion (DataRowVersion.Current) ? Current : Table.DefaultValuesRowIndex;
640 for (int i = 0; i < Table.Columns.Count; i++){
641 DataColumn column = Table.Columns [i];
642 column.DataContainer.CopyValue (from, Proposed);
648 /// Cancels the current edit on the row.
651 [EditorBrowsable (EditorBrowsableState.Advanced)]
653 public void CancelEdit ()
655 if (_inChangingEvent)
656 throw new InRowChangingEventException ("Cannot call CancelEdit inside an OnRowChanging event.");
658 if (HasVersion (DataRowVersion.Proposed)) {
659 int oldRecord = Proposed;
660 DataRowState oldState = RowState;
661 Table.RecordCache.DisposeRecord(Proposed);
664 foreach (Index index in Table.Indexes)
665 index.Update (this, oldRecord, DataRowVersion.Proposed, oldState);
670 /// Clears the errors for the row, including the RowError and errors set with
673 public void ClearErrors ()
675 rowError = String.Empty;
676 ColumnErrors.Clear();
680 /// Deletes the DataRow.
682 public void Delete ()
684 _table.DeletingDataRow (this, DataRowAction.Delete);
686 case DataRowState.Added:
687 CheckChildRows (DataRowAction.Delete);
690 case DataRowState.Deleted:
691 case DataRowState.Detached:
694 // check what to do with child rows
695 CheckChildRows (DataRowAction.Delete);
699 int current = Current;
700 DataRowState oldState = RowState;
701 if (Current != Original)
702 _table.RecordCache.DisposeRecord (Current);
704 foreach (Index index in Table.Indexes)
705 index.Update (this, current, DataRowVersion.Current, oldState);
707 _table.DeletedDataRow (this, DataRowAction.Delete);
710 // check the child rows of this row before deleting the row.
711 private void CheckChildRows (DataRowAction action)
713 DataSet ds = _table.DataSet;
715 if (ds == null || !ds.EnforceConstraints)
718 // if the table we're attached-to doesn't have an constraints, no foreign keys are pointing to us ...
719 if (_table.Constraints.Count == 0)
722 foreach (DataTable table in ds.Tables) {
723 // loop on all ForeignKeyConstrain of the table.
724 foreach (Constraint constraint in table.Constraints) {
725 ForeignKeyConstraint fk = constraint as ForeignKeyConstraint;
726 if (fk == null || fk.RelatedTable != _table)
730 case DataRowAction.Delete:
731 CheckChildRows (fk, action, fk.DeleteRule);
733 case DataRowAction.Commit:
734 case DataRowAction.Rollback:
735 if (fk.AcceptRejectRule != AcceptRejectRule.None)
736 CheckChildRows (fk, action, Rule.Cascade);
739 CheckChildRows (fk, action, fk.UpdateRule);
746 private void CheckChildRows (ForeignKeyConstraint fkc, DataRowAction action, Rule rule)
748 DataRow [] childRows = GetChildRows (fkc, DataRowVersion.Current);
749 if (childRows == null)
753 case Rule.Cascade: // delete or change all relted rows.
755 case DataRowAction.Delete:
756 for (int j = 0; j < childRows.Length; j++) {
757 if (childRows [j].RowState != DataRowState.Deleted)
758 childRows [j].Delete ();
761 case DataRowAction.Change:
762 for (int j = 0; j < childRows.Length; j++) {
763 // if action is change we change the values in the child row
764 // change only the values in the key columns
765 // set the childcolumn value to the new parent row value
766 for (int k = 0; k < fkc.Columns.Length; k++)
767 if (!fkc.RelatedColumns [k].DataContainer [Current].Equals (fkc.RelatedColumns [k].DataContainer [Proposed]))
768 childRows [j][fkc.Columns [k]] = this [fkc.RelatedColumns [k], DataRowVersion.Proposed];
771 case DataRowAction.Rollback:
772 for (int j = 0; j < childRows.Length; j++) {
773 if (childRows [j].RowState != DataRowState.Unchanged)
774 childRows [j].RejectChanges ();
779 case Rule.None: // throw an exception if there are any child rows.
780 for (int j = 0; j < childRows.Length; j++) {
781 if (childRows[j].RowState != DataRowState.Deleted) {
782 string changeStr = "Cannot change this row because constraints are enforced on relation " + fkc.ConstraintName +", and changing this row will strand child rows.";
783 string delStr = "Cannot delete this row because constraints are enforced on relation " + fkc.ConstraintName +", and deleting this row will strand child rows.";
784 string message = action == DataRowAction.Delete ? delStr : changeStr;
785 throw new InvalidConstraintException (message);
789 case Rule.SetDefault: // set the values in the child rows to the default value of the columns.
790 if (childRows.Length > 0) {
791 int defaultValuesRowIndex = childRows [0].Table.DefaultValuesRowIndex;
792 foreach (DataRow childRow in childRows) {
793 if (childRow.RowState != DataRowState.Deleted) {
794 int defaultIdx = childRow.IndexFromVersion (DataRowVersion.Default);
795 foreach (DataColumn column in fkc.Columns)
796 column.DataContainer.CopyValue (defaultValuesRowIndex, defaultIdx);
801 case Rule.SetNull: // set the values in the child row to null.
802 for (int j = 0; j < childRows.Length; j++) {
803 DataRow child = childRows [j];
804 if (childRows[j].RowState != DataRowState.Deleted) {
805 // set only the key columns to DBNull
806 for (int k = 0; k < fkc.Columns.Length; k++)
807 child.SetNull (fkc.Columns[k]);
815 /// Ends the edit occurring on the row.
818 [EditorBrowsable (EditorBrowsableState.Advanced)]
820 public void EndEdit ()
822 if (_inChangingEvent)
823 throw new InRowChangingEventException ("Cannot call EndEdit inside an OnRowChanging event.");
825 if (RowState == DataRowState.Detached || !HasVersion (DataRowVersion.Proposed))
828 CheckReadOnlyStatus ();
830 _inChangingEvent = true;
832 _table.ChangingDataRow (this, DataRowAction.Change);
834 _inChangingEvent = false;
837 DataRowState oldState = RowState;
839 int oldRecord = Current;
843 //FIXME : ideally indexes shouldnt be maintained during dataload.But this needs to
844 //be implemented at multiple places.For now, just maintain the index.
845 //if (!Table._duringDataLoad) {
846 foreach (Index index in Table.Indexes)
847 index.Update (this, oldRecord, DataRowVersion.Current, oldState);
851 AssertConstraints ();
853 // restore previous state to let the cascade update to find the rows
857 CheckChildRows (DataRowAction.Change);
863 int proposed = Proposed >= 0 ? Proposed : Current;
865 //if (!Table._duringDataLoad) {
866 foreach (Index index in Table.Indexes)
867 index.Update (this, proposed, DataRowVersion.Current, RowState);
872 if (Original != oldRecord)
873 Table.RecordCache.DisposeRecord (oldRecord);
875 // Note : row state must not be changed before all the job on indexes finished,
876 // since the indexes works with recods rather than with rows and the decision
877 // which of row records to choose depends on row state.
878 if (_rowChanged == true) {
879 _table.ChangedDataRow (this, DataRowAction.Change);
885 /// Gets the child rows of this DataRow using the specified DataRelation.
887 public DataRow [] GetChildRows (DataRelation relation)
889 return GetChildRows (relation, DataRowVersion.Default);
893 /// Gets the child rows of a DataRow using the specified RelationName of a
896 public DataRow [] GetChildRows (string relationName)
898 return GetChildRows (Table.DataSet.Relations [relationName]);
902 /// Gets the child rows of a DataRow using the specified DataRelation, and
905 public DataRow[] GetChildRows (DataRelation relation, DataRowVersion version)
907 if (relation == null)
908 return Table.NewRowArray (0);
910 if (this.Table == null)
911 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.");
913 if (relation.DataSet != this.Table.DataSet)
914 throw new ArgumentException ();
916 if (_table != relation.ParentTable)
917 throw new InvalidConstraintException ("GetChildRow requires a row whose Table is " + relation.ParentTable + ", but the specified row's table is " + _table);
919 if (relation.ChildKeyConstraint != null)
920 return GetChildRows (relation.ChildKeyConstraint, version);
922 ArrayList rows = new ArrayList ();
923 DataColumn[] parentColumns = relation.ParentColumns;
924 DataColumn[] childColumns = relation.ChildColumns;
925 int numColumn = parentColumns.Length;
926 DataRow[] result = null;
928 int versionIndex = IndexFromVersion (version);
929 int tmpRecord = relation.ChildTable.RecordCache.NewRecord ();
932 for (int i = 0; i < numColumn; i++)
933 // according to MSDN: the DataType value for both columns must be identical.
934 childColumns [i].DataContainer.CopyValue (parentColumns [i].DataContainer, versionIndex, tmpRecord);
936 Index index = relation.ChildTable.FindIndex (childColumns);
939 int [] records = index.FindAll (tmpRecord);
940 result = relation.ChildTable.NewRowArray (records.Length);
941 for(int i = 0; i < records.Length; i++)
942 result [i] = relation.ChildTable.RecordCache [records [i]];
944 foreach (DataRow row in relation.ChildTable.Rows) {
945 bool allColumnsMatch = false;
946 if (row.HasVersion (DataRowVersion.Default)) {
947 allColumnsMatch = true;
948 int childIndex = row.IndexFromVersion (DataRowVersion.Default);
949 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) {
950 if (childColumns[columnCnt].DataContainer.CompareValues (childIndex, tmpRecord) != 0) {
951 allColumnsMatch = false;
959 result = relation.ChildTable.NewRowArray (rows.Count);
960 rows.CopyTo (result, 0);
964 relation.ChildTable.RecordCache.DisposeRecord (tmpRecord);
971 /// Gets the child rows of a DataRow using the specified RelationName of a
972 /// DataRelation, and DataRowVersion.
974 public DataRow [] GetChildRows (string relationName, DataRowVersion version)
976 return GetChildRows (Table.DataSet.Relations [relationName], version);
979 private DataRow [] GetChildRows (ForeignKeyConstraint fkc, DataRowVersion version)
981 ArrayList rows = new ArrayList ();
982 DataColumn [] parentColumns = fkc.RelatedColumns;
983 DataColumn [] childColumns = fkc.Columns;
984 int numColumn = parentColumns.Length;
986 Index index = fkc.Index;
988 int curIndex = IndexFromVersion (version);
989 int tmpRecord = fkc.Table.RecordCache.NewRecord ();
990 for (int i = 0; i < numColumn; i++)
991 // according to MSDN: the DataType value for both columns must be identical.
992 childColumns [i].DataContainer.CopyValue (parentColumns [i].DataContainer, curIndex, tmpRecord);
996 // get the child rows from the index
997 int[] childRecords = index.FindAll (tmpRecord);
998 for (int i = 0; i < childRecords.Length; i++)
999 rows.Add (childColumns [i].Table.RecordCache [childRecords [i]]);
1000 } else { // if there is no index we search manualy.
1001 foreach (DataRow row in fkc.Table.Rows) {
1002 bool allColumnsMatch = false;
1003 if (row.HasVersion (DataRowVersion.Default)) {
1004 allColumnsMatch = true;
1005 int childIndex = row.IndexFromVersion (DataRowVersion.Default);
1006 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) {
1007 if (childColumns [columnCnt].DataContainer.CompareValues (childIndex, tmpRecord) != 0) {
1008 allColumnsMatch = false;
1013 if (allColumnsMatch)
1018 fkc.Table.RecordCache.DisposeRecord(tmpRecord);
1021 DataRow[] result = fkc.Table.NewRowArray (rows.Count);
1022 rows.CopyTo (result, 0);
1027 /// Gets the error description of the specified DataColumn.
1029 public string GetColumnError (DataColumn column)
1032 throw new ArgumentNullException ("column");
1034 int index = _table.Columns.IndexOf (column);
1036 throw new ArgumentException (String.Format ("Column '{0}' does not belong to table {1}.", column.ColumnName, Table.TableName));
1038 return GetColumnError (index);
1042 /// Gets the error description for the column specified by index.
1044 public string GetColumnError (int columnIndex)
1046 if (columnIndex < 0 || columnIndex >= Table.Columns.Count)
1047 throw new IndexOutOfRangeException ();
1049 string retVal = null;
1050 if (columnIndex < ColumnErrors.Count)
1051 retVal = (String) ColumnErrors [columnIndex];
1052 return (retVal != null) ? retVal : String.Empty;
1056 /// Gets the error description for the column, specified by name.
1058 public string GetColumnError (string columnName)
1060 return GetColumnError (_table.Columns.IndexOf (columnName));
1064 /// Gets an array of columns that have errors.
1066 public DataColumn [] GetColumnsInError ()
1068 ArrayList dataColumns = new ArrayList ();
1070 int columnOrdinal = 0;
1071 foreach (String columnError in ColumnErrors) {
1072 if (columnError != null && columnError != String.Empty)
1073 dataColumns.Add (_table.Columns [columnOrdinal]);
1077 return (DataColumn [])(dataColumns.ToArray (typeof (DataColumn)));
1081 /// Gets the parent row of a DataRow using the specified DataRelation.
1083 public DataRow GetParentRow (DataRelation relation)
1085 return GetParentRow (relation, DataRowVersion.Default);
1089 /// Gets the parent row of a DataRow using the specified RelationName of a
1092 public DataRow GetParentRow (string relationName)
1094 return GetParentRow (relationName, DataRowVersion.Default);
1098 /// Gets the parent row of a DataRow using the specified DataRelation, and
1101 public DataRow GetParentRow (DataRelation relation, DataRowVersion version)
1103 DataRow[] rows = GetParentRows (relation, version);
1104 if (rows.Length == 0)
1110 /// Gets the parent row of a DataRow using the specified RelationName of a
1111 /// DataRelation, and DataRowVersion.
1113 public DataRow GetParentRow (string relationName, DataRowVersion version)
1115 return GetParentRow (Table.DataSet.Relations [relationName], version);
1119 /// Gets the parent rows of a DataRow using the specified DataRelation.
1121 public DataRow [] GetParentRows (DataRelation relation)
1123 return GetParentRows (relation, DataRowVersion.Default);
1127 /// Gets the parent rows of a DataRow using the specified RelationName of a
1130 public DataRow [] GetParentRows (string relationName)
1132 return GetParentRows (relationName, DataRowVersion.Default);
1136 /// Gets the parent rows of a DataRow using the specified DataRelation, and
1139 public DataRow [] GetParentRows (DataRelation relation, DataRowVersion version)
1141 // TODO: Caching for better preformance
1142 if (relation == null)
1143 return Table.NewRowArray (0);
1145 if (this.Table == null)
1146 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.");
1148 if (relation.DataSet != this.Table.DataSet)
1149 throw new ArgumentException ();
1151 if (_table != relation.ChildTable)
1152 throw new InvalidConstraintException ("GetParentRows requires a row whose Table is " + relation.ChildTable + ", but the specified row's table is " + _table);
1154 ArrayList rows = new ArrayList ();
1155 DataColumn[] parentColumns = relation.ParentColumns;
1156 DataColumn[] childColumns = relation.ChildColumns;
1157 int numColumn = parentColumns.Length;
1159 int curIndex = IndexFromVersion (version);
1160 int tmpRecord = relation.ParentTable.RecordCache.NewRecord ();
1161 for (int i = 0; i < numColumn; i++)
1162 // according to MSDN: the DataType value for both columns must be identical.
1163 parentColumns [i].DataContainer.CopyValue(childColumns [i].DataContainer, curIndex, tmpRecord);
1166 Index index = relation.ParentTable.FindIndex(parentColumns);
1167 if (index != null) { // get the parent rows from the index
1168 int [] parentRecords = index.FindAll (tmpRecord);
1169 for (int i = 0; i < parentRecords.Length; i++)
1170 rows.Add (parentColumns [i].Table.RecordCache [parentRecords [i]]);
1171 } else { // no index so we have to search manualy.
1172 foreach (DataRow row in relation.ParentTable.Rows) {
1173 bool allColumnsMatch = false;
1174 if (row.HasVersion (DataRowVersion.Default)) {
1175 allColumnsMatch = true;
1176 int parentIndex = row.IndexFromVersion (DataRowVersion.Default);
1177 for (int columnCnt = 0; columnCnt < numColumn; columnCnt++) {
1178 if (parentColumns [columnCnt].DataContainer.CompareValues (parentIndex, tmpRecord) != 0) {
1179 allColumnsMatch = false;
1184 if (allColumnsMatch)
1189 relation.ParentTable.RecordCache.DisposeRecord (tmpRecord);
1192 DataRow [] result = relation.ParentTable.NewRowArray (rows.Count);
1193 rows.CopyTo (result, 0);
1198 /// Gets the parent rows of a DataRow using the specified RelationName of a
1199 /// DataRelation, and DataRowVersion.
1201 public DataRow [] GetParentRows (string relationName, DataRowVersion version)
1203 return GetParentRows (Table.DataSet.Relations [relationName], version);
1207 /// Gets a value indicating whether a specified version exists.
1209 public bool HasVersion (DataRowVersion version)
1212 case DataRowVersion.Default:
1213 return (Proposed >= 0 || Current >= 0);
1214 case DataRowVersion.Proposed:
1215 return Proposed >= 0;
1216 case DataRowVersion.Current:
1217 return Current >= 0;
1218 case DataRowVersion.Original:
1219 return Original >= 0;
1221 return IndexFromVersion (version) >= 0;
1226 /// Gets a value indicating whether the specified DataColumn contains a null value.
1228 public bool IsNull (DataColumn column)
1230 return IsNull (column, DataRowVersion.Default);
1234 /// Gets a value indicating whether the column at the specified index contains a null
1237 public bool IsNull (int columnIndex)
1239 return IsNull (Table.Columns [columnIndex]);
1243 /// Gets a value indicating whether the named column contains a null value.
1245 public bool IsNull (string columnName)
1247 return IsNull (Table.Columns [columnName]);
1251 /// Gets a value indicating whether the specified DataColumn and DataRowVersion
1252 /// contains a null value.
1254 public bool IsNull (DataColumn column, DataRowVersion version)
1256 object o = this [column, version];
1257 return column.DataContainer.IsNull (IndexFromVersion (version));
1261 /// Returns a value indicating whether all of the row columns specified contain a null value.
1263 internal bool IsNullColumns (DataColumn [] columns)
1266 for (i = 0; i < columns.Length; i++) {
1267 if (!IsNull (columns [i]))
1270 return i == columns.Length;
1274 /// Rejects all changes made to the row since AcceptChanges was last called.
1276 public void RejectChanges ()
1278 if (RowState == DataRowState.Detached)
1279 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.");
1280 // If original is null, then nothing has happened since AcceptChanges
1281 // was last called. We have no "original" to go back to.
1283 _table.ChangedDataRow (this, DataRowAction.Rollback);
1286 //TODO : Need to Verify the constraints..
1288 case DataRowState.Added:
1291 case DataRowState.Modified:
1292 int current = Current;
1293 Table.RecordCache.DisposeRecord (Current);
1294 CheckChildRows (DataRowAction.Rollback);
1296 foreach (Index index in Table.Indexes)
1297 index.Update (this, current, DataRowVersion.Current, DataRowState.Modified);
1299 case DataRowState.Deleted:
1300 CheckChildRows (DataRowAction.Rollback);
1302 // Add row to index and validate if the constraints are satisfied
1309 /// Sets the error description for a column specified as a DataColumn.
1311 public void SetColumnError (DataColumn column, string error)
1313 SetColumnError (_table.Columns.IndexOf (column), error);
1317 /// Sets the error description for a column specified by index.
1319 public void SetColumnError (int columnIndex, string error)
1321 if (columnIndex < 0 || columnIndex >= Table.Columns.Count)
1322 throw new IndexOutOfRangeException ();
1324 while (columnIndex >= ColumnErrors.Count)
1325 ColumnErrors.Add (null);
1327 ColumnErrors [columnIndex] = error;
1331 /// Sets the error description for a column specified by name.
1333 public void SetColumnError (string columnName, string error)
1335 SetColumnError (_table.Columns.IndexOf (columnName), error);
1339 /// Sets the value of the specified DataColumn to a null value.
1341 protected void SetNull (DataColumn column)
1343 this [column] = DBNull.Value;
1347 /// Sets the parent row of a DataRow with specified new parent DataRow.
1349 public void SetParentRow (DataRow parentRow)
1351 SetParentRow (parentRow, null);
1355 /// Sets the parent row of a DataRow with specified new parent DataRow and
1358 public void SetParentRow (DataRow parentRow, DataRelation relation)
1360 if (_table == null || parentRow.Table == null)
1361 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.");
1363 if (parentRow != null && _table.DataSet != parentRow.Table.DataSet)
1364 throw new ArgumentException ();
1366 if (RowState == DataRowState.Detached && !HasVersion (DataRowVersion.Default))
1367 // 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
1368 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.");
1372 IEnumerable relations;
1373 if (relation == null)
1374 relations = _table.ParentRelations;
1376 relations = new DataRelation [] { relation };
1378 foreach (DataRelation rel in relations) {
1379 DataColumn [] childCols = rel.ChildColumns;
1380 DataColumn [] parentCols = rel.ParentColumns;
1382 for (int i = 0; i < parentCols.Length; i++) {
1383 if (parentRow == null) {
1384 childCols [i].DataContainer [Proposed] = DBNull.Value;
1386 int defaultIdx = parentRow.IndexFromVersion (DataRowVersion.Default);
1387 childCols [i].DataContainer.CopyValue(parentCols [i].DataContainer, defaultIdx, Proposed);
1395 //Copy all values of this DataRow to the row parameter.
1396 internal void CopyValuesToRow (DataRow row)
1399 throw new ArgumentNullException("row");
1401 throw new ArgumentException("'row' is the same as this object");
1403 // create target records if missing.
1404 if (HasVersion (DataRowVersion.Original)) {
1405 if (row.Original < 0)
1406 row.Original = row.Table.RecordCache.NewRecord ();
1407 else if (row.Original == row.Current) {
1408 row.Original = row.Table.RecordCache.NewRecord ();
1409 row.Table.RecordCache.CopyRecord (row.Table, row.Current, row.Original);
1412 if (row.Original > 0) {
1413 if (row.Original != row.Current)
1414 row.Table.RecordCache.DisposeRecord (row.Original);
1419 if (HasVersion (DataRowVersion.Current)) {
1420 if (Current == Original) {
1421 if (row.Current >= 0)
1422 row.Table.RecordCache.DisposeRecord (row.Current);
1423 row.Current = row.Original;
1425 if (row.Current < 0)
1426 row.Current = row.Table.RecordCache.NewRecord ();
1429 if (row.Current > 0) {
1430 row.Table.RecordCache.DisposeRecord (row.Current);
1435 if (HasVersion (DataRowVersion.Proposed)) {
1436 if (row.Proposed < 0)
1437 row.Proposed = row.Table.RecordCache.NewRecord ();
1439 if (row.Proposed > 0) {
1440 row.Table.RecordCache.DisposeRecord (row.Proposed);
1445 // copy source record values to target records
1446 foreach (DataColumn column in Table.Columns) {
1447 DataColumn targetColumn = row.Table.Columns [column.ColumnName];
1448 //if a column with the same name exists in both rows copy the values
1449 if (targetColumn != null) {
1450 if (HasVersion (DataRowVersion.Original)) {
1451 object val = column[Original];
1452 row.CheckValue (val, targetColumn);
1453 targetColumn [row.Original] = val;
1456 if (HasVersion (DataRowVersion.Current) && Current != Original) {
1457 object val = column[Current];
1458 row.CheckValue (val, targetColumn);
1459 targetColumn [row.Current] = val;
1462 if (HasVersion (DataRowVersion.Proposed)) {
1463 object val = column[row.Proposed];
1464 row.CheckValue (val, targetColumn);
1465 targetColumn [row.Proposed] = val;
1473 //Merge all values of this DataRow to the row parameter according to merge rules.
1474 internal void MergeValuesToRow (DataRow row, bool preserveChanges)
1477 throw new ArgumentNullException ("row");
1479 throw new ArgumentException ("'row' is the same as this object");
1481 // Original values are anyway copied
1482 if (HasVersion (DataRowVersion.Original)) {
1483 if (row.Original < 0)
1484 row.Original = row.Table.RecordCache.NewRecord ();
1485 else if (row.Original == row.Current && !(Original == Current && !preserveChanges)) {
1486 row.Original = row.Table.RecordCache.NewRecord ();
1487 row.Table.RecordCache.CopyRecord (row.Table, row.Current, row.Original);
1490 if (row.Original == row.Current) { // if target has same current, better create new original
1491 row.Original = row.Table.RecordCache.NewRecord ();
1492 row.Table.RecordCache.CopyRecord (row.Table, row.Current, row.Original);
1496 // if i have current, push all
1497 if (HasVersion (DataRowVersion.Current)) {
1498 if (! preserveChanges && row.Current < 0)
1499 row.Current = row.Table.RecordCache.NewRecord ();
1501 if (row.Current > 0 && ! preserveChanges) {
1502 row.Table.RecordCache.DisposeRecord (row.Current);
1507 // copy source record values to target records
1508 foreach (DataColumn column in Table.Columns) {
1509 DataColumn targetColumn = row.Table.Columns[column.ColumnName];
1510 //if a column with the same name exists in both rows copy the values
1511 if (targetColumn != null) {
1512 if (HasVersion (DataRowVersion.Original)) {
1513 object val = column [Original];
1514 row.CheckValue (val, targetColumn);
1515 targetColumn [row.Original] = val;
1518 if (HasVersion (DataRowVersion.Current) && !preserveChanges) {
1519 object val = column [Current];
1520 row.CheckValue (val, targetColumn);
1521 targetColumn [row.Current] = val;
1530 internal void CopyErrors (DataRow row)
1532 row.RowError = RowError;
1533 DataColumn[] errorColumns = GetColumnsInError();
1534 foreach (DataColumn col in errorColumns) {
1535 DataColumn targetColumn = row.Table.Columns [col.ColumnName];
1536 row.SetColumnError (targetColumn, GetColumnError (col));
1540 internal bool IsRowChanged (DataRowState rowState)
1542 if((RowState & rowState) != 0)
1545 //we need to find if child rows of this row changed.
1546 //if yes - we should return true
1548 // if the rowState is deleted we should get the original version of the row
1549 // else - we should get the current version of the row.
1550 DataRowVersion version = (rowState == DataRowState.Deleted) ? DataRowVersion.Original : DataRowVersion.Current;
1551 int count = Table.ChildRelations.Count;
1552 for (int i = 0; i < count; i++){
1553 DataRelation rel = Table.ChildRelations [i];
1554 DataRow [] childRows = GetChildRows (rel, version);
1555 for (int j = 0; j < childRows.Length; j++){
1556 if (childRows [j].IsRowChanged (rowState))
1564 internal void Validate ()
1566 Table.AddRowToIndexes (this);
1567 AssertConstraints ();
1570 void AssertConstraints ()
1572 if (Table == null || Table._duringDataLoad)
1575 if (Table.DataSet != null && !Table.DataSet.EnforceConstraints)
1577 for (int i = 0; i < Table.Columns.Count; ++i) {
1578 DataColumn column = Table.Columns [i];
1579 if (!column.AllowDBNull && IsNull (column))
1580 throw new NoNullAllowedException (_nullConstraintMessage);
1583 foreach (Constraint constraint in Table.Constraints) {
1585 constraint.AssertConstraint (this);
1586 } catch (Exception e) {
1587 Table.DeleteRowFromIndexes (this);
1593 internal void CheckNullConstraints ()
1595 if (_nullConstraintViolation) {
1596 if (HasVersion (DataRowVersion.Proposed)) {
1597 foreach (DataColumn column in Table.Columns) {
1598 if (IsNull (column) && !column.AllowDBNull)
1599 throw new NoNullAllowedException (_nullConstraintMessage);
1602 _nullConstraintViolation = false;
1606 internal void CheckReadOnlyStatus()
1608 int defaultIdx = IndexFromVersion (DataRowVersion.Default);
1609 foreach(DataColumn column in Table.Columns) {
1610 if ((column.DataContainer.CompareValues (defaultIdx,Proposed) != 0) && column.ReadOnly)
1611 throw new ReadOnlyException ();
1615 #endregion // Methods
1619 /// This method loads a given value into the existing row affecting versions,
1620 /// state based on the LoadOption. The matrix of changes for this method are as
1621 /// mentioned in the DataTable.Load (IDataReader, LoadOption) method.
1623 internal void Load (object [] values, LoadOption loadOption)
1627 if (loadOption == LoadOption.OverwriteChanges ||
1628 (loadOption == LoadOption.PreserveChanges && RowState == DataRowState.Unchanged)) {
1629 Table.ChangingDataRow (this, DataRowAction.ChangeCurrentAndOriginal);
1630 temp = Table.CreateRecord (values);
1631 Table.DeleteRowFromIndexes(this);
1632 if (HasVersion (DataRowVersion.Original) && Current != Original)
1633 Table.RecordCache.DisposeRecord (Original);
1636 if (HasVersion (DataRowVersion.Current))
1637 Table.RecordCache.DisposeRecord (Current);
1639 Table.AddRowToIndexes(this);
1640 Table.ChangedDataRow (this, DataRowAction.ChangeCurrentAndOriginal);
1644 if (loadOption == LoadOption.PreserveChanges) {
1645 Table.ChangingDataRow (this, DataRowAction.ChangeOriginal);
1646 temp = Table.CreateRecord (values);
1647 if (HasVersion (DataRowVersion.Original) && Current != Original)
1648 Table.RecordCache.DisposeRecord (Original);
1650 Table.ChangedDataRow (this, DataRowAction.ChangeOriginal);
1655 if (RowState != DataRowState.Deleted) {
1656 int rindex = HasVersion (DataRowVersion.Proposed) ? Proposed : Current;
1657 temp = Table.CreateRecord (values);
1658 if (RowState == DataRowState.Added || Table.CompareRecords (rindex, temp) != 0) {
1659 Table.ChangingDataRow (this, DataRowAction.Change);
1660 Table.DeleteRowFromIndexes(this);
1661 if (HasVersion (DataRowVersion.Proposed)) {
1662 Table.RecordCache.DisposeRecord (Proposed);
1666 if (Original != Current)
1667 Table.RecordCache.DisposeRecord (Current);
1669 Table.AddRowToIndexes(this);
1670 Table.ChangedDataRow (this, DataRowAction.Change);
1672 Table.ChangingDataRow (this, DataRowAction.Nothing);
1673 Table.RecordCache.DisposeRecord (temp);
1674 Table.ChangedDataRow (this, DataRowAction.Nothing);