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 namespace System.Data {
48 /// Represents a row of data in a DataTable.
55 private DataTable _table;
57 internal int _original = -1;
58 internal int _current = -1;
59 internal int _proposed = -1;
61 private ArrayList _columnErrors;
62 private string rowError;
63 private DataRowState rowState;
64 internal int xmlRowID = 0;
65 internal bool _nullConstraintViolation;
66 private string _nullConstraintMessage;
67 private bool editing = false;
68 private bool _hasParentCollection;
69 private bool _inChangingEvent;
72 private XmlDataDocument.XmlDataElement mappedElement;
73 internal bool _inExpressionEvaluation = false;
80 /// This member supports the .NET Framework infrastructure and is not intended to be
81 /// used directly from your code.
83 protected internal DataRow (DataRowBuilder builder)
85 _table = builder.Table;
86 // Get the row id from the builder.
87 _rowId = builder._rowId;
89 _proposed = _table.RecordCache.NewRecord();
90 // Initialise the data columns of the row with the dafault values, if any
91 // TODO : should proposed version be available immediately after record creation ?
92 foreach(DataColumn column in _table.Columns) {
93 column.DataContainer.CopyValue(_table.DefaultValuesRowIndex,_proposed);
96 rowError = String.Empty;
98 //on first creating a DataRow it is always detached.
99 rowState = DataRowState.Detached;
101 ArrayList aiColumns = _table.Columns.AutoIncrmentColumns;
102 foreach (DataColumn dc in aiColumns) {
103 this [dc] = dc.AutoIncrementValue();
106 // create mapped XmlDataElement
107 DataSet ds = _table.DataSet;
108 if (ds != null && ds._xmlDataDocument != null)
109 mappedElement = new XmlDataDocument.XmlDataElement (this, _table.Prefix, _table.TableName, _table.Namespace, ds._xmlDataDocument);
112 internal DataRow(DataTable table,int rowId)
118 #endregion // Constructors
122 private ArrayList ColumnErrors
125 if (_columnErrors == null) {
126 _columnErrors = new ArrayList();
128 return _columnErrors;
132 _columnErrors = value;
137 /// Gets a value indicating whether there are errors in a row.
139 public bool HasErrors {
141 if (RowError != string.Empty)
144 foreach(String columnError in ColumnErrors) {
145 if (columnError != null && columnError != string.Empty) {
154 /// Gets or sets the data stored in the column specified by name.
156 public object this[string columnName] {
157 get { return this[columnName, DataRowVersion.Default]; }
159 int columnIndex = _table.Columns.IndexOf (columnName);
160 if (columnIndex == -1)
161 throw new IndexOutOfRangeException ();
162 this[columnIndex] = value;
167 /// Gets or sets the data stored in specified DataColumn
169 public object this[DataColumn column] {
172 return this[column, DataRowVersion.Default];}
174 int columnIndex = _table.Columns.IndexOf (column);
175 if (columnIndex == -1)
176 throw new ArgumentException ("The column does not belong to this table.");
177 this[columnIndex] = value;
182 /// Gets or sets the data stored in column specified by index.
184 public object this[int columnIndex] {
185 get { return this[columnIndex, DataRowVersion.Default]; }
187 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
188 throw new IndexOutOfRangeException ();
189 if (rowState == DataRowState.Deleted)
190 throw new DeletedRowInaccessibleException ();
192 DataColumn column = _table.Columns[columnIndex];
193 _table.ChangingDataColumn (this, column, value);
195 if (value == null && column.DataType != typeof(string)) {
196 throw new ArgumentException("Cannot set column " + column.ColumnName + " to be null, Please use DBNull instead");
199 CheckValue (value, column);
201 bool orginalEditing = editing;
202 if (!orginalEditing) {
206 column[_proposed] = value;
207 _table.ChangedDataColumn (this, column, value);
208 if (!orginalEditing) {
215 /// Gets the specified version of data stored in the named column.
217 public object this[string columnName, DataRowVersion version] {
219 int columnIndex = _table.Columns.IndexOf (columnName);
220 if (columnIndex == -1)
221 throw new IndexOutOfRangeException ();
222 return this[columnIndex, version];
227 /// Gets the specified version of data stored in the specified DataColumn.
229 public object this[DataColumn column, DataRowVersion version] {
231 if (column.Table != Table)
232 throw new ArgumentException ("The column does not belong to this table.");
233 int columnIndex = column.Ordinal;
234 return this[columnIndex, version];
239 /// Sets the index into the container records for the original version. Apart
240 /// from that, it makes sure it pools the record used earlier if they are not
241 /// used by other versions.
243 internal int Original
245 get { return _original;}
247 if (_original == value)
251 && _current != _original
252 && _proposed != _original)
253 Table.RecordCache.DisposeRecord (_original);
259 /// Sets the index into the container records for the proposed version. Apart
260 /// from that, it makes sure it pools the record used earlier if they are not
261 /// used by other versions.
262 internal int Proposed
264 get { return _proposed;}
266 if (_proposed == value)
269 && _proposed != _current
270 && _proposed != _original)
271 Table.RecordCache.DisposeRecord (_proposed);
277 /// Sets the index into the container records for the current version. Apart
278 /// from that, it makes sure it pools the record used earlier if they are not
279 /// used by other versions.
282 get { return _current;}
284 if (_current == value)
287 && _current != _original
288 && _current != _proposed)
289 Table.RecordCache.DisposeRecord (_current);
295 /// Set a value for the column into the offset specified by the version.<br>
296 /// If the value is auto increment or null, necessary auto increment value
297 /// or the default value will be used.
299 internal void SetValue (int column, object value, int version)
301 DataColumn dc = Table.Columns[column];
303 if (value == null && ! dc.AutoIncrement) // set default value / auto increment
304 value = dc.DefaultValue;
306 Table.ChangingDataColumn (this, dc, value);
307 CheckValue (value, dc);
308 if ( ! dc.AutoIncrement)
309 dc [version] = value;
310 else if (_proposed >= 0 && _proposed != version) // proposed holds the AI
311 dc [version] = dc [_proposed];
315 /// Gets the data stored in the column, specified by index and version of the data to
318 public object this[int columnIndex, DataRowVersion version] {
320 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
321 throw new IndexOutOfRangeException ();
322 // Accessing deleted rows
323 if (!_inExpressionEvaluation && rowState == DataRowState.Deleted && version != DataRowVersion.Original)
324 throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
326 DataColumn column = _table.Columns[columnIndex];
327 if (column.Expression != String.Empty) {
328 object o = column.CompiledExpression.Eval (this);
329 return Convert.ChangeType (o, column.DataType);
332 if (rowState == DataRowState.Detached && version == DataRowVersion.Default && _proposed < 0)
333 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.");
335 int recordIndex = IndexFromVersion(version);
337 if (recordIndex >= 0) {
338 return column[recordIndex];
341 throw new VersionNotFoundException (Locale.GetText ("There is no " + version.ToString () + " data to access."));
346 /// Gets or sets all of the values for this row through an array.
348 public object[] ItemArray {
351 if (rowState == DataRowState.Detached)
352 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.");
353 // Accessing deleted rows
354 if (rowState == DataRowState.Deleted)
355 throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
357 object[] items = new object[_table.Columns.Count];
358 foreach(DataColumn column in _table.Columns) {
359 items[column.Ordinal] = column[_current];
364 if (value.Length > _table.Columns.Count)
365 throw new ArgumentException ();
367 if (rowState == DataRowState.Deleted)
368 throw new DeletedRowInaccessibleException ();
370 bool orginalEditing = editing;
371 if (!orginalEditing) {
374 object newVal = null;
375 DataColumnChangeEventArgs e = new DataColumnChangeEventArgs();
376 foreach(DataColumn column in _table.Columns) {
377 int i = column.Ordinal;
378 newVal = (i < value.Length) ? value[i] : null;
380 e.Initialize(this, column, newVal);
381 _table.RaiseOnColumnChanged(e);
382 CheckValue (e.ProposedValue, column);
383 column[_proposed] = e.ProposedValue;
385 if (!orginalEditing) {
391 internal bool IsEditing {
392 get { return editing; }
396 /// Gets the current state of the row in regards to its relationship to the
397 /// DataRowCollection.
399 public DataRowState RowState {
406 /// Gets the DataTable for which this row has a schema.
408 public DataTable Table {
415 /// Gets and sets index of row. This is used from
418 internal int XmlRowID {
428 /// Gets and sets index of row.
439 #endregion // Properties
443 //FIXME?: Couldn't find a way to set the RowState when adding the DataRow
444 //to a Datatable so I added this method. Delete if there is a better way.
445 internal void AttachRow() {
447 Table.RecordCache.DisposeRecord(_current);
449 _current = _proposed;
451 rowState = DataRowState.Added;
454 //FIXME?: Couldn't find a way to set the RowState when removing the DataRow
455 //from a Datatable so I added this method. Delete if there is a better way.
456 internal void DetachRow() {
457 if (_proposed >= 0) {
458 _table.RecordCache.DisposeRecord(_proposed);
462 _hasParentCollection = false;
463 rowState = DataRowState.Detached;
466 private void CheckValue (object v, DataColumn col)
468 if (_hasParentCollection && col.ReadOnly) {
469 throw new ReadOnlyException ();
472 if (v == null || v == DBNull.Value) {
473 if (col.AllowDBNull || col.AutoIncrement || col.DefaultValue != DBNull.Value) {
477 //Constraint violations during data load is raise in DataTable EndLoad
478 this._nullConstraintViolation = true;
479 if (this.Table._duringDataLoad) {
480 this.Table._nullConstraintViolationDuringDataLoad = true;
482 _nullConstraintMessage = "Column '" + col.ColumnName + "' does not allow nulls.";
487 internal void SetValuesFromDataRecord(IDataRecord record, int[] mapping)
489 // bool orginalEditing = editing;
490 // if (!orginalEditing) {
494 if (!HasVersion(DataRowVersion.Proposed)) {
495 _proposed = Table.RecordCache.NewRecord();
499 for(int i=0; i < Table.Columns.Count; i++) {
500 DataColumn column = Table.Columns[i];
501 if (mapping [i] < 0) { // no mapping
502 if (! column.AutoIncrement)
503 column.DataContainer [_proposed] = column.DefaultValue;
507 column.DataContainer.SetItemFromDataRecord(_proposed, record,mapping[i]);
508 if ( column.AutoIncrement ) {
509 column.UpdateAutoIncrementValue(column.DataContainer.GetInt64(_proposed));
514 Table.RecordCache.DisposeRecord(_proposed);
519 // if (!orginalEditing) {
525 /// Gets or sets the custom error description for a row.
527 public string RowError {
536 internal int IndexFromVersion(DataRowVersion version)
538 if (HasVersion(version))
542 case DataRowVersion.Default:
543 if (editing || rowState == DataRowState.Detached) {
544 recordIndex = _proposed;
547 recordIndex = _current;
550 case DataRowVersion.Proposed:
551 recordIndex = _proposed;
553 case DataRowVersion.Current:
554 recordIndex = _current;
556 case DataRowVersion.Original:
557 recordIndex = _original;
560 throw new ArgumentException ();
567 internal XmlDataDocument.XmlDataElement DataElement {
568 get { return mappedElement; }
569 set { mappedElement = value; }
572 internal void SetOriginalValue (string columnName, object val)
574 DataColumn column = _table.Columns[columnName];
575 _table.ChangingDataColumn (this, column, val);
577 if (_original < 0 || _original == _current) {
578 // This really creates a new record version if one does not exist
579 _original = Table.RecordCache.NewRecord();
581 CheckValue (val, column);
582 column[_original] = val;
583 rowState = DataRowState.Modified;
587 /// Commits all the changes made to this row since the last time AcceptChanges was
590 public void AcceptChanges ()
592 EndEdit(); // in case it hasn't been called
594 _table.ChangingDataRow (this, DataRowAction.Commit);
596 case DataRowState.Unchanged:
598 case DataRowState.Added:
599 case DataRowState.Modified:
600 rowState = DataRowState.Unchanged;
602 case DataRowState.Deleted:
603 _table.Rows.RemoveInternal (this);
606 case DataRowState.Detached:
607 throw new RowNotInTableException("Cannot perform this operation on a row not in the table.");
609 // Accept from detached
610 if (_original != _current)
613 _table.ChangedDataRow (this, DataRowAction.Commit);
617 /// Begins an edit operation on a DataRow object.
619 public void BeginEdit ()
621 if (_inChangingEvent)
622 throw new InRowChangingEventException("Cannot call BeginEdit inside an OnRowChanging event.");
623 if (rowState == DataRowState.Deleted)
624 throw new DeletedRowInaccessibleException ();
625 if (!HasVersion (DataRowVersion.Proposed)) {
626 _proposed = Table.RecordCache.NewRecord();
627 foreach(DataColumn column in Table.Columns) {
628 column.DataContainer.CopyValue(_current,_proposed);
631 // setting editing to true stops validations on the row
636 /// Cancels the current edit on the row.
638 public void CancelEdit ()
640 if (_inChangingEvent)
641 throw new InRowChangingEventException("Cannot call CancelEdit inside an OnRowChanging event.");
643 if (HasVersion (DataRowVersion.Proposed)) {
644 Table.RecordCache.DisposeRecord(_proposed);
646 if (rowState == DataRowState.Modified) {
647 rowState = DataRowState.Unchanged;
653 /// Clears the errors for the row, including the RowError and errors set with
656 public void ClearErrors ()
658 rowError = String.Empty;
659 ColumnErrors.Clear();
663 /// Deletes the DataRow.
665 public void Delete ()
667 _table.DeletingDataRow(this, DataRowAction.Delete);
669 case DataRowState.Added:
670 // check what to do with child rows
671 CheckChildRows(DataRowAction.Delete);
672 _table.DeleteRowFromIndexes (this);
673 Table.Rows.RemoveInternal (this);
675 // if row was in Added state we move it to Detached.
678 case DataRowState.Deleted:
681 // check what to do with child rows
682 CheckChildRows(DataRowAction.Delete);
683 _table.DeleteRowFromIndexes (this);
684 rowState = DataRowState.Deleted;
687 _table.DeletedDataRow(this, DataRowAction.Delete);
690 // check the child rows of this row before deleting the row.
691 private void CheckChildRows(DataRowAction action)
694 // in this method we find the row that this row is in a relation with them.
695 // in shortly we find all child rows of this row.
696 // then we function according to the DeleteRule of the foriegnkey.
699 // 1. find if this row is attached to dataset.
700 // 2. find if EnforceConstraints is true.
701 // 3. find if there are any constraint on the table that the row is in.
702 if (_table.DataSet != null && _table.DataSet.EnforceConstraints && _table.Constraints.Count > 0)
704 foreach (DataTable table in _table.DataSet.Tables)
706 // loop on all ForeignKeyConstrain of the table.
707 foreach (ForeignKeyConstraint fk in table.Constraints.ForeignKeyConstraints)
709 if (fk.RelatedTable == _table)
712 if (action == DataRowAction.Delete)
713 rule = fk.DeleteRule;
715 rule = fk.UpdateRule;
716 CheckChildRows(fk, action, rule);
723 private void CheckChildRows(ForeignKeyConstraint fkc, DataRowAction action, Rule rule)
725 DataRow[] childRows = GetChildRows(fkc, DataRowVersion.Default);
728 case Rule.Cascade: // delete or change all relted rows.
729 if (childRows != null)
731 for (int j = 0; j < childRows.Length; j++)
733 // if action is delete we delete all child rows
734 if (action == DataRowAction.Delete)
736 if (childRows[j].RowState != DataRowState.Deleted)
737 childRows[j].Delete();
739 // if action is change we change the values in the child row
740 else if (action == DataRowAction.Change)
742 // change only the values in the key columns
743 // set the childcolumn value to the new parent row value
744 for (int k = 0; k < fkc.Columns.Length; k++)
745 childRows[j][fkc.Columns[k]] = this[fkc.RelatedColumns[k], DataRowVersion.Proposed];
750 case Rule.None: // throw an exception if there are any child rows.
751 if (childRows != null)
753 for (int j = 0; j < childRows.Length; j++)
755 if (childRows[j].RowState != DataRowState.Deleted)
757 string changeStr = "Cannot change this row because constraints are enforced on relation " + fkc.ConstraintName +", and changing this row will strand child rows.";
758 string delStr = "Cannot delete this row because constraints are enforced on relation " + fkc.ConstraintName +", and deleting this row will strand child rows.";
759 string message = action == DataRowAction.Delete ? delStr : changeStr;
760 throw new InvalidConstraintException(message);
765 case Rule.SetDefault: // set the values in the child rows to the defult value of the columns.
766 if (childRows != null && childRows.Length > 0) {
767 int defaultValuesRowIndex = childRows[0].Table.DefaultValuesRowIndex;
768 foreach(DataRow childRow in childRows) {
769 if (childRow.RowState != DataRowState.Deleted) {
770 int defaultIdx = childRow.IndexFromVersion(DataRowVersion.Default);
771 foreach(DataColumn column in fkc.Columns) {
772 column.DataContainer.CopyValue(defaultValuesRowIndex,defaultIdx);
778 case Rule.SetNull: // set the values in the child row to null.
779 if (childRows != null)
781 for (int j = 0; j < childRows.Length; j++)
783 DataRow child = childRows[j];
784 if (childRows[j].RowState != DataRowState.Deleted)
786 // set only the key columns to DBNull
787 for (int k = 0; k < fkc.Columns.Length; k++)
788 child.SetNull(fkc.Columns[k]);
798 /// Ends the edit occurring on the row.
800 public void EndEdit ()
802 if (_inChangingEvent)
803 throw new InRowChangingEventException("Cannot call EndEdit inside an OnRowChanging event.");
804 if (rowState == DataRowState.Detached)
810 CheckReadOnlyStatus();
811 if (HasVersion (DataRowVersion.Proposed))
813 _inChangingEvent = true;
816 _table.ChangingDataRow(this, DataRowAction.Change);
820 _inChangingEvent = false;
822 if (rowState == DataRowState.Unchanged)
823 rowState = DataRowState.Modified;
825 //Calling next method validates UniqueConstraints
829 if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
830 _table.Rows.ValidateDataRowInternal(this);
835 Table.RecordCache.DisposeRecord(_proposed);
840 // Now we are going to check all child rows of current row.
841 // In the case the cascade is true the child rows will look up for
842 // parent row. since lookup in index is always on current,
843 // we have to move proposed version of current row to current
844 // in the case of check child row failure we are rolling
845 // current row state back.
846 int backup = _current;
847 _current = _proposed;
848 bool editing_backup = editing;
851 // check all child rows.
852 CheckChildRows(DataRowAction.Change);
854 if (_original != backup) {
855 Table.RecordCache.DisposeRecord(backup);
858 catch (Exception ex) {
859 // if check child rows failed - rollback to previous state
860 // i.e. restore proposed and current versions
861 _proposed = _current;
863 editing = editing_backup;
864 // since we failed - propagate an exception
867 _table.ChangedDataRow(this, DataRowAction.Change);
872 /// Gets the child rows of this DataRow using the specified DataRelation.
874 public DataRow[] GetChildRows (DataRelation relation)
876 return GetChildRows (relation, DataRowVersion.Default);
880 /// Gets the child rows of a DataRow using the specified RelationName of a
883 public DataRow[] GetChildRows (string relationName)
885 return GetChildRows (Table.DataSet.Relations[relationName]);
889 /// Gets the child rows of a DataRow using the specified DataRelation, and
892 public DataRow[] GetChildRows (DataRelation relation, DataRowVersion version)
894 if (relation == null)
895 return new DataRow[0];
897 //if (this.Table == null || RowState == DataRowState.Detached)
898 if (this.Table == null)
899 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.");
901 if (relation.DataSet != this.Table.DataSet)
902 throw new ArgumentException();
904 if (_table != relation.ParentTable)
905 throw new InvalidConstraintException ("GetChildRow requires a row whose Table is " + relation.ParentTable + ", but the specified row's table is " + _table);
907 if (relation.ChildKeyConstraint != null)
908 return GetChildRows (relation.ChildKeyConstraint, version);
910 ArrayList rows = new ArrayList();
911 DataColumn[] parentColumns = relation.ParentColumns;
912 DataColumn[] childColumns = relation.ChildColumns;
913 int numColumn = parentColumns.Length;
914 if (HasVersion(version))
916 object[] vals = new object[parentColumns.Length];
917 for (int i = 0; i < vals.Length; i++)
918 vals[i] = this[parentColumns[i], version];
920 foreach (DataRow row in relation.ChildTable.Rows)
922 bool allColumnsMatch = false;
923 if (row.HasVersion(DataRowVersion.Default))
925 allColumnsMatch = true;
926 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt)
928 if (!vals[columnCnt].Equals(
929 row[childColumns[columnCnt], DataRowVersion.Default]))
931 allColumnsMatch = false;
936 if (allColumnsMatch) rows.Add(row);
939 throw new VersionNotFoundException("There is no " + version + " data to accces.");
941 DataRow[] result = relation.ChildTable.NewRowArray(rows.Count);
942 rows.CopyTo(result, 0);
947 /// Gets the child rows of a DataRow using the specified RelationName of a
948 /// DataRelation, and DataRowVersion.
950 public DataRow[] GetChildRows (string relationName, DataRowVersion version)
952 return GetChildRows (Table.DataSet.Relations[relationName], version);
955 private DataRow[] GetChildRows (ForeignKeyConstraint fkc, DataRowVersion version)
957 ArrayList rows = new ArrayList();
958 DataColumn[] parentColumns = fkc.RelatedColumns;
959 DataColumn[] childColumns = fkc.Columns;
960 int numColumn = parentColumns.Length;
961 if (HasVersion(version)) {
962 Index index = fkc.Index;
964 // get the child rows from the index
965 Node[] childNodes = index.FindAllSimple (parentColumns, IndexFromVersion(version));
966 for (int i = 0; i < childNodes.Length; i++) {
967 rows.Add (childNodes[i].Row);
970 else { // if there is no index we search manualy.
971 int curIndex = IndexFromVersion(DataRowVersion.Default);
972 int tmpRecord = fkc.Table.RecordCache.NewRecord();
975 for (int i = 0; i < numColumn; i++) {
976 // according to MSDN: the DataType value for both columns must be identical.
977 childColumns[i].DataContainer.CopyValue(parentColumns[i].DataContainer, curIndex, tmpRecord);
980 foreach (DataRow row in fkc.Table.Rows) {
981 bool allColumnsMatch = false;
982 if (row.HasVersion(DataRowVersion.Default)) {
983 allColumnsMatch = true;
984 int childIndex = row.IndexFromVersion(DataRowVersion.Default);
985 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) {
986 if (childColumns[columnCnt].DataContainer.CompareValues(childIndex, tmpRecord) != 0) {
987 allColumnsMatch = false;
992 if (allColumnsMatch) {
998 fkc.Table.RecordCache.DisposeRecord(tmpRecord);
1002 throw new VersionNotFoundException("There is no " + version + " data to accces.");
1004 DataRow[] result = fkc.Table.NewRowArray(rows.Count);
1005 rows.CopyTo(result, 0);
1010 /// Gets the error description of the specified DataColumn.
1012 public string GetColumnError (DataColumn column)
1014 return GetColumnError (_table.Columns.IndexOf(column));
1018 /// Gets the error description for the column specified by index.
1020 public string GetColumnError (int columnIndex)
1022 if (columnIndex < 0 || columnIndex >= Table.Columns.Count)
1023 throw new IndexOutOfRangeException ();
1025 string retVal = null;
1026 if (columnIndex < ColumnErrors.Count) {
1027 retVal = (String) ColumnErrors[columnIndex];
1029 return (retVal != null) ? retVal : String.Empty;
1033 /// Gets the error description for the column, specified by name.
1035 public string GetColumnError (string columnName)
1037 return GetColumnError (_table.Columns.IndexOf(columnName));
1041 /// Gets an array of columns that have errors.
1043 public DataColumn[] GetColumnsInError ()
1045 ArrayList dataColumns = new ArrayList ();
1047 int columnOrdinal = 0;
1048 foreach(String columnError in ColumnErrors) {
1049 if (columnError != null && columnError != String.Empty) {
1050 dataColumns.Add (_table.Columns[columnOrdinal]);
1055 return (DataColumn[])(dataColumns.ToArray (typeof(DataColumn)));
1059 /// Gets the parent row of a DataRow using the specified DataRelation.
1061 public DataRow GetParentRow (DataRelation relation)
1063 return GetParentRow (relation, DataRowVersion.Default);
1067 /// Gets the parent row of a DataRow using the specified RelationName of a
1070 public DataRow GetParentRow (string relationName)
1072 return GetParentRow (relationName, DataRowVersion.Default);
1076 /// Gets the parent row of a DataRow using the specified DataRelation, and
1079 public DataRow GetParentRow (DataRelation relation, DataRowVersion version)
1081 DataRow[] rows = GetParentRows(relation, version);
1082 if (rows.Length == 0) return null;
1087 /// Gets the parent row of a DataRow using the specified RelationName of a
1088 /// DataRelation, and DataRowVersion.
1090 public DataRow GetParentRow (string relationName, DataRowVersion version)
1092 return GetParentRow (Table.DataSet.Relations[relationName], version);
1096 /// Gets the parent rows of a DataRow using the specified DataRelation.
1098 public DataRow[] GetParentRows (DataRelation relation)
1100 return GetParentRows (relation, DataRowVersion.Default);
1104 /// Gets the parent rows of a DataRow using the specified RelationName of a
1107 public DataRow[] GetParentRows (string relationName)
1109 return GetParentRows (relationName, DataRowVersion.Default);
1113 /// Gets the parent rows of a DataRow using the specified DataRelation, and
1116 public DataRow[] GetParentRows (DataRelation relation, DataRowVersion version)
1118 // TODO: Caching for better preformance
1119 if (relation == null)
1120 return new DataRow[0];
1122 if (this.Table == null)
1123 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.");
1125 if (relation.DataSet != this.Table.DataSet)
1126 throw new ArgumentException();
1128 if (_table != relation.ChildTable)
1129 throw new InvalidConstraintException ("GetParentRows requires a row whose Table is " + relation.ChildTable + ", but the specified row's table is " + _table);
1131 ArrayList rows = new ArrayList();
1132 DataColumn[] parentColumns = relation.ParentColumns;
1133 DataColumn[] childColumns = relation.ChildColumns;
1134 int numColumn = parentColumns.Length;
1135 if (HasVersion(version)) {
1136 Index indx = relation.ParentTable.GetIndexByColumns (parentColumns);
1138 (Table == null || Table.DataSet == null ||
1139 Table.DataSet.EnforceConstraints)) { // get the child rows from the index
1140 Node[] childNodes = indx.FindAllSimple(childColumns, IndexFromVersion(version));
1141 for (int i = 0; i < childNodes.Length; i++) {
1142 rows.Add (childNodes[i].Row);
1145 else { // no index so we have to search manualy.
1146 int curIndex = IndexFromVersion(DataRowVersion.Default);
1147 int tmpRecord = relation.ParentTable.RecordCache.NewRecord();
1149 for (int i = 0; i < numColumn; i++) {
1150 // according to MSDN: the DataType value for both columns must be identical.
1151 parentColumns[i].DataContainer.CopyValue(childColumns[i].DataContainer, curIndex, tmpRecord);
1154 foreach (DataRow row in relation.ParentTable.Rows) {
1155 bool allColumnsMatch = false;
1156 if (row.HasVersion(DataRowVersion.Default)) {
1157 allColumnsMatch = true;
1158 int parentIndex = row.IndexFromVersion(DataRowVersion.Default);
1159 for (int columnCnt = 0; columnCnt < numColumn; columnCnt++) {
1160 if (parentColumns[columnCnt].DataContainer.CompareValues(parentIndex, tmpRecord) != 0) {
1161 allColumnsMatch = false;
1166 if (allColumnsMatch) {
1172 relation.ParentTable.RecordCache.DisposeRecord(tmpRecord);
1176 throw new VersionNotFoundException("There is no " + version + " data to accces.");
1178 DataRow[] result = relation.ParentTable.NewRowArray(rows.Count);
1179 rows.CopyTo(result, 0);
1184 /// Gets the parent rows of a DataRow using the specified RelationName of a
1185 /// DataRelation, and DataRowVersion.
1187 public DataRow[] GetParentRows (string relationName, DataRowVersion version)
1189 return GetParentRows (Table.DataSet.Relations[relationName], version);
1193 /// Gets a value indicating whether a specified version exists.
1195 public bool HasVersion (DataRowVersion version)
1198 case DataRowVersion.Default:
1199 if (rowState == DataRowState.Deleted && !_inExpressionEvaluation)
1201 if (rowState == DataRowState.Detached)
1202 return _proposed >= 0;
1204 case DataRowVersion.Proposed:
1205 if (rowState == DataRowState.Deleted && !_inExpressionEvaluation)
1207 return _proposed >= 0;
1208 case DataRowVersion.Current:
1209 if ((rowState == DataRowState.Deleted && !_inExpressionEvaluation) || rowState == DataRowState.Detached)
1211 return _current >= 0;
1212 case DataRowVersion.Original:
1213 if (rowState == DataRowState.Detached)
1215 return _original >= 0;
1221 /// Gets a value indicating whether the specified DataColumn contains a null value.
1223 public bool IsNull (DataColumn column)
1225 return IsNull(column, DataRowVersion.Default);
1229 /// Gets a value indicating whether the column at the specified index contains a null
1232 public bool IsNull (int columnIndex)
1234 return IsNull(Table.Columns[columnIndex]);
1238 /// Gets a value indicating whether the named column contains a null value.
1240 public bool IsNull (string columnName)
1242 return IsNull(Table.Columns[columnName]);
1246 /// Gets a value indicating whether the specified DataColumn and DataRowVersion
1247 /// contains a null value.
1249 public bool IsNull (DataColumn column, DataRowVersion version)
1251 return column.DataContainer.IsNull(IndexFromVersion(version));
1255 /// Returns a value indicating whether all of the row columns specified contain a null value.
1257 internal bool IsNullColumns(DataColumn[] columns)
1259 bool allNull = true;
1260 for (int i = 0; i < columns.Length; i++)
1262 if (!IsNull(columns[i]))
1272 /// Rejects all changes made to the row since AcceptChanges was last called.
1274 public void RejectChanges ()
1276 if (RowState == DataRowState.Detached)
1277 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.");
1278 // If original is null, then nothing has happened since AcceptChanges
1279 // was last called. We have no "original" to go back to.
1280 if (HasVersion(DataRowVersion.Original)) {
1281 if (_current >= 0 ) {
1282 Table.RecordCache.DisposeRecord(_current);
1284 _current = _original;
1286 _table.ChangedDataRow (this, DataRowAction.Rollback);
1290 case DataRowState.Added:
1291 _table.DeleteRowFromIndexes (this);
1292 _table.Rows.RemoveInternal (this);
1294 case DataRowState.Modified:
1295 if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
1296 _table.Rows.ValidateDataRowInternal(this);
1297 rowState = DataRowState.Unchanged;
1299 case DataRowState.Deleted:
1300 rowState = DataRowState.Unchanged;
1301 if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
1302 _table.Rows.ValidateDataRowInternal(this);
1308 // If rows are just loaded via Xml the original values are null.
1309 // So in this case we have to remove all columns.
1310 // FIXME: I'm not realy sure, does this break something else, but
1313 if ((rowState & DataRowState.Added) > 0)
1315 _table.DeleteRowFromIndexes (this);
1316 _table.Rows.RemoveInternal (this);
1317 // if row was in Added state we move it to Detached.
1324 /// Sets the error description for a column specified as a DataColumn.
1326 public void SetColumnError (DataColumn column, string error)
1328 SetColumnError (_table.Columns.IndexOf (column), error);
1332 /// Sets the error description for a column specified by index.
1334 public void SetColumnError (int columnIndex, string error)
1336 if (columnIndex < 0 || columnIndex >= Table.Columns.Count)
1337 throw new IndexOutOfRangeException ();
1339 while(ColumnErrors.Count < columnIndex) {
1340 ColumnErrors.Add(null);
1342 ColumnErrors.Add(error);
1346 /// Sets the error description for a column specified by name.
1348 public void SetColumnError (string columnName, string error)
1350 SetColumnError (_table.Columns.IndexOf (columnName), error);
1354 /// Sets the value of the specified DataColumn to a null value.
1356 protected void SetNull (DataColumn column)
1358 this[column] = DBNull.Value;
1362 /// Sets the parent row of a DataRow with specified new parent DataRow.
1364 public void SetParentRow (DataRow parentRow)
1366 SetParentRow(parentRow, null);
1370 /// Sets the parent row of a DataRow with specified new parent DataRow and
1373 public void SetParentRow (DataRow parentRow, DataRelation relation)
1375 if (_table == null || parentRow.Table == null)
1376 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.");
1378 if (parentRow != null && _table.DataSet != parentRow.Table.DataSet)
1379 throw new ArgumentException();
1382 if (relation == null)
1384 foreach (DataRelation parentRel in _table.ParentRelations)
1386 DataColumn[] childCols = parentRel.ChildColumns;
1387 DataColumn[] parentCols = parentRel.ParentColumns;
1389 for (int i = 0; i < parentCols.Length; i++)
1391 if (parentRow == null)
1392 this[childCols[i].Ordinal] = DBNull.Value;
1394 this[childCols[i].Ordinal] = parentRow[parentCols[i]];
1401 DataColumn[] childCols = relation.ChildColumns;
1402 DataColumn[] parentCols = relation.ParentColumns;
1404 for (int i = 0; i < parentCols.Length; i++)
1406 if (parentRow == null)
1407 this[childCols[i].Ordinal] = DBNull.Value;
1409 this[childCols[i].Ordinal] = parentRow[parentCols[i]];
1415 //Copy all values of this DataaRow to the row parameter.
1416 internal void CopyValuesToRow(DataRow row)
1419 throw new ArgumentNullException("row");
1421 throw new ArgumentException("'row' is the same as this object");
1423 foreach(DataColumn column in Table.Columns) {
1424 DataColumn targetColumn = row.Table.Columns[column.ColumnName];
1425 //if a column with the same name exists in both rows copy the values
1426 if(targetColumn != null) {
1427 int index = targetColumn.Ordinal;
1428 if (HasVersion(DataRowVersion.Original)) {
1429 if (row._original < 0) {
1430 row._original = row.Table.RecordCache.NewRecord();
1432 object val = column[_original];
1433 row.CheckValue(val, targetColumn);
1434 targetColumn[row._original] = val;
1436 if (HasVersion(DataRowVersion.Current)) {
1437 if (row._current < 0) {
1438 row._current = row.Table.RecordCache.NewRecord();
1440 object val = column[_current];
1441 row.CheckValue(val, targetColumn);
1442 targetColumn[row._current] = val;
1444 if (HasVersion(DataRowVersion.Proposed)) {
1445 if (row._proposed < 0) {
1446 row._proposed = row.Table.RecordCache.NewRecord();
1448 object val = column[row._proposed];
1449 row.CheckValue(val, targetColumn);
1450 targetColumn[row._proposed] = val;
1453 //Saving the current value as the column value
1454 object defaultVal = column [IndexFromVersion (DataRowVersion.Default)];
1455 row [index] = defaultVal;
1461 // Copy row state - rowState and errors
1462 internal void CopyState(DataRow row)
1464 row.rowState = RowState;
1465 row.RowError = RowError;
1466 row.ColumnErrors = (ArrayList)ColumnErrors.Clone();
1469 internal bool IsRowChanged(DataRowState rowState) {
1470 if((RowState & rowState) != 0)
1473 //we need to find if child rows of this row changed.
1474 //if yes - we should return true
1476 // if the rowState is deleted we should get the original version of the row
1477 // else - we should get the current version of the row.
1478 DataRowVersion version = (rowState == DataRowState.Deleted) ? DataRowVersion.Original : DataRowVersion.Current;
1479 int count = Table.ChildRelations.Count;
1480 for (int i = 0; i < count; i++){
1481 DataRelation rel = Table.ChildRelations[i];
1482 DataRow[] childRows = GetChildRows(rel, version);
1483 for (int j = 0; j < childRows.Length; j++){
1484 if (childRows[j].IsRowChanged(rowState))
1492 internal bool HasParentCollection
1496 return _hasParentCollection;
1500 _hasParentCollection = value;
1504 internal void CheckNullConstraints()
1506 if (_nullConstraintViolation) {
1507 if (HasVersion(DataRowVersion.Proposed)) {
1508 foreach(DataColumn column in Table.Columns) {
1509 if (IsNull(column) && !column.AllowDBNull) {
1510 throw new NoNullAllowedException(_nullConstraintMessage);
1514 _nullConstraintViolation = false;
1518 internal void CheckReadOnlyStatus()
1520 if (HasVersion(DataRowVersion.Proposed)) {
1521 int defaultIdx = IndexFromVersion(DataRowVersion.Default);
1522 foreach(DataColumn column in Table.Columns) {
1523 if ((column.DataContainer.CompareValues(defaultIdx,_proposed) != 0) && column.ReadOnly) {
1524 throw new ReadOnlyException();
1530 #endregion // Methods
1534 /// This method loads a given value into the existing row affecting versions,
1535 /// state based on the LoadOption. The matrix of changes for this method are as
1536 /// mentioned in the DataTable.Load (IDataReader, LoadOption) method.
1538 [MonoTODO ("Raise necessary Events")]
1539 internal void Load (object [] values, LoadOption loadOption, bool is_new)
1541 DataRowAction action = DataRowAction.Change;
1543 int temp = Table.RecordCache.NewRecord ();
1544 for (int i = 0 ; i < Table.Columns.Count; i++)
1545 SetValue (i, values [i], temp);
1547 if (is_new) { // new row
1548 if (editing || RowState == DataRowState.Detached)
1555 if (loadOption == LoadOption.OverwriteChanges
1556 || (loadOption == LoadOption.PreserveChanges
1557 && rowState == DataRowState.Unchanged)) {
1563 rowState = DataRowState.Unchanged;
1564 action = DataRowAction.ChangeCurrentAndOriginal;
1568 if (loadOption == LoadOption.PreserveChanges) {
1569 if (rowState != DataRowState.Deleted) {
1571 rowState = DataRowState.Modified;
1572 action = DataRowAction.ChangeOriginal;
1577 bool not_used = true;
1579 if (rowState != DataRowState.Deleted) {
1580 int index = editing ? _proposed : _current;
1581 if (! RecordCache.CompareRecords (Table, index, temp)) {
1587 if (rowState == DataRowState.Unchanged)
1588 rowState = DataRowState.Modified;
1593 Table.RecordCache.DisposeRecord (temp);