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>
11 // (C) Ximian, Inc 2002
12 // (C) Daniel Morgan 2002, 2003
13 // Copyright (C) 2002 Tim Coleman
17 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
19 // Permission is hereby granted, free of charge, to any person obtaining
20 // a copy of this software and associated documentation files (the
21 // "Software"), to deal in the Software without restriction, including
22 // without limitation the rights to use, copy, modify, merge, publish,
23 // distribute, sublicense, and/or sell copies of the Software, and to
24 // permit persons to whom the Software is furnished to do so, subject to
25 // the following conditions:
27 // The above copyright notice and this permission notice shall be
28 // included in all copies or substantial portions of the Software.
30 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
31 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
32 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
33 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
34 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
35 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
36 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
40 using System.Collections;
41 using System.Globalization;
44 namespace System.Data {
46 /// Represents a row of data in a DataTable.
53 private DataTable _table;
55 internal int _original = -1;
56 internal int _current = -1;
57 internal int _proposed = -1;
59 private ArrayList _columnErrors;
60 private string rowError;
61 private DataRowState rowState;
62 internal int xmlRowID = 0;
63 internal bool _nullConstraintViolation;
64 private string _nullConstraintMessage;
65 private bool editing = false;
66 private bool _hasParentCollection;
67 private bool _inChangingEvent;
70 private XmlDataDocument.XmlDataElement mappedElement;
71 internal bool _inExpressionEvaluation = false;
78 /// This member supports the .NET Framework infrastructure and is not intended to be
79 /// used directly from your code.
81 protected internal DataRow (DataRowBuilder builder)
83 _table = builder.Table;
84 // Get the row id from the builder.
85 _rowId = builder._rowId;
87 _proposed = _table.RecordCache.NewRecord();
88 // Initialise the data columns of the row with the dafault values, if any
89 // TODO : should proposed version be available immediately after record creation ?
90 foreach(DataColumn column in _table.Columns) {
91 column.DataContainer.CopyValue(_table.DefaultValuesRowIndex,_proposed);
94 rowError = String.Empty;
96 //on first creating a DataRow it is always detached.
97 rowState = DataRowState.Detached;
99 ArrayList aiColumns = _table.Columns.AutoIncrmentColumns;
100 foreach (DataColumn dc in aiColumns) {
101 this [dc] = dc.AutoIncrementValue();
104 // create mapped XmlDataElement
105 DataSet ds = _table.DataSet;
106 if (ds != null && ds._xmlDataDocument != null)
107 mappedElement = new XmlDataDocument.XmlDataElement (this, _table.Prefix, _table.TableName, _table.Namespace, ds._xmlDataDocument);
110 internal DataRow(DataTable table,int rowId)
116 #endregion // Constructors
120 private ArrayList ColumnErrors
123 if (_columnErrors == null) {
124 _columnErrors = new ArrayList();
126 return _columnErrors;
130 _columnErrors = value;
135 /// Gets a value indicating whether there are errors in a row.
137 public bool HasErrors {
139 if (RowError != string.Empty)
142 foreach(String columnError in ColumnErrors) {
143 if (columnError != null && columnError != string.Empty) {
152 /// Gets or sets the data stored in the column specified by name.
154 public object this[string columnName] {
155 get { return this[columnName, DataRowVersion.Default]; }
157 int columnIndex = _table.Columns.IndexOf (columnName);
158 if (columnIndex == -1)
159 throw new IndexOutOfRangeException ();
160 this[columnIndex] = value;
165 /// Gets or sets the data stored in specified DataColumn
167 public object this[DataColumn column] {
170 return this[column, DataRowVersion.Default];}
172 int columnIndex = _table.Columns.IndexOf (column);
173 if (columnIndex == -1)
174 throw new ArgumentException ("The column does not belong to this table.");
175 this[columnIndex] = value;
180 /// Gets or sets the data stored in column specified by index.
182 public object this[int columnIndex] {
183 get { return this[columnIndex, DataRowVersion.Default]; }
185 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
186 throw new IndexOutOfRangeException ();
187 if (rowState == DataRowState.Deleted)
188 throw new DeletedRowInaccessibleException ();
190 DataColumn column = _table.Columns[columnIndex];
191 _table.ChangingDataColumn (this, column, value);
193 if (value == null && column.DataType != typeof(string)) {
194 throw new ArgumentException("Cannot set column " + column.ColumnName + " to be null, Please use DBNull instead");
197 CheckValue (value, column);
199 bool orginalEditing = editing;
200 if (!orginalEditing) {
204 column[_proposed] = value;
205 _table.ChangedDataColumn (this, column, value);
206 if (!orginalEditing) {
213 /// Gets the specified version of data stored in the named column.
215 public object this[string columnName, DataRowVersion version] {
217 int columnIndex = _table.Columns.IndexOf (columnName);
218 if (columnIndex == -1)
219 throw new IndexOutOfRangeException ();
220 return this[columnIndex, version];
225 /// Gets the specified version of data stored in the specified DataColumn.
227 public object this[DataColumn column, DataRowVersion version] {
229 if (column.Table != Table)
230 throw new ArgumentException ("The column does not belong to this table.");
231 int columnIndex = column.Ordinal;
232 return this[columnIndex, version];
237 /// Gets the data stored in the column, specified by index and version of the data to
240 public object this[int columnIndex, DataRowVersion version] {
242 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
243 throw new IndexOutOfRangeException ();
244 // Accessing deleted rows
245 if (!_inExpressionEvaluation && rowState == DataRowState.Deleted && version != DataRowVersion.Original)
246 throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
248 DataColumn column = _table.Columns[columnIndex];
249 if (column.Expression != String.Empty) {
250 object o = column.CompiledExpression.Eval (this);
251 return Convert.ChangeType (o, column.DataType);
254 if (rowState == DataRowState.Detached && version == DataRowVersion.Default && _proposed < 0)
255 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.");
257 int recordIndex = IndexFromVersion(version);
259 if (recordIndex >= 0) {
260 return column[recordIndex];
263 throw new VersionNotFoundException (Locale.GetText ("There is no " + version.ToString () + " data to access."));
268 /// Gets or sets all of the values for this row through an array.
270 public object[] ItemArray {
273 if (rowState == DataRowState.Detached)
274 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.");
275 // Accessing deleted rows
276 if (rowState == DataRowState.Deleted)
277 throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
279 object[] items = new object[_table.Columns.Count];
280 foreach(DataColumn column in _table.Columns) {
281 items[column.Ordinal] = column[_current];
286 if (value.Length > _table.Columns.Count)
287 throw new ArgumentException ();
289 if (rowState == DataRowState.Deleted)
290 throw new DeletedRowInaccessibleException ();
292 bool orginalEditing = editing;
293 if (!orginalEditing) {
296 object newVal = null;
297 DataColumnChangeEventArgs e = new DataColumnChangeEventArgs();
298 foreach(DataColumn column in _table.Columns) {
299 int i = column.Ordinal;
300 newVal = (i < value.Length) ? value[i] : null;
302 e.Initialize(this, column, newVal);
303 _table.RaiseOnColumnChanged(e);
304 CheckValue (e.ProposedValue, column);
305 column[_proposed] = e.ProposedValue;
307 if (!orginalEditing) {
313 internal bool IsEditing {
314 get { return editing; }
318 /// Gets the current state of the row in regards to its relationship to the
319 /// DataRowCollection.
321 public DataRowState RowState {
328 /// Gets the DataTable for which this row has a schema.
330 public DataTable Table {
337 /// Gets and sets index of row. This is used from
340 internal int XmlRowID {
350 /// Gets and sets index of row.
365 //FIXME?: Couldn't find a way to set the RowState when adding the DataRow
366 //to a Datatable so I added this method. Delete if there is a better way.
367 internal void AttachRow() {
369 Table.RecordCache.DisposeRecord(_current);
371 _current = _proposed;
373 rowState = DataRowState.Added;
376 //FIXME?: Couldn't find a way to set the RowState when removing the DataRow
377 //from a Datatable so I added this method. Delete if there is a better way.
378 internal void DetachRow() {
379 if (_proposed >= 0) {
380 _table.RecordCache.DisposeRecord(_proposed);
384 _hasParentCollection = false;
385 rowState = DataRowState.Detached;
388 private void CheckValue (object v, DataColumn col)
390 if (_hasParentCollection && col.ReadOnly) {
391 throw new ReadOnlyException ();
394 if (v == null || v == DBNull.Value) {
395 if (col.AllowDBNull || col.AutoIncrement || col.DefaultValue != DBNull.Value) {
399 //Constraint violations during data load is raise in DataTable EndLoad
400 this._nullConstraintViolation = true;
401 if (this.Table._duringDataLoad) {
402 this.Table._nullConstraintViolationDuringDataLoad = true;
404 _nullConstraintMessage = "Column '" + col.ColumnName + "' does not allow nulls.";
409 internal void SetValuesFromDataRecord(IDataRecord record, int[] mapping)
411 if ( mapping.Length > Table.Columns.Count)
412 throw new ArgumentException ();
414 // bool orginalEditing = editing;
415 // if (!orginalEditing) {
419 if (!HasVersion(DataRowVersion.Proposed)) {
420 _proposed = Table.RecordCache.NewRecord();
424 for(int i=0; i < mapping.Length; i++) {
425 DataColumn column = Table.Columns[i];
426 column.DataContainer.SetItemFromDataRecord(_proposed, record,mapping[i]);
427 if ( column.AutoIncrement ) {
428 column.UpdateAutoIncrementValue(column.DataContainer.GetInt64(_proposed));
433 Table.RecordCache.DisposeRecord(_proposed);
438 // if (!orginalEditing) {
444 /// Gets or sets the custom error description for a row.
446 public string RowError {
455 internal int IndexFromVersion(DataRowVersion version)
457 if (HasVersion(version))
461 case DataRowVersion.Default:
462 if (editing || rowState == DataRowState.Detached) {
463 recordIndex = _proposed;
466 recordIndex = _current;
469 case DataRowVersion.Proposed:
470 recordIndex = _proposed;
472 case DataRowVersion.Current:
473 recordIndex = _current;
475 case DataRowVersion.Original:
476 recordIndex = _original;
479 throw new ArgumentException ();
486 internal XmlDataDocument.XmlDataElement DataElement {
487 get { return mappedElement; }
488 set { mappedElement = value; }
491 internal void SetOriginalValue (string columnName, object val)
493 DataColumn column = _table.Columns[columnName];
494 _table.ChangingDataColumn (this, column, val);
496 if (_original < 0 || _original == _current) {
497 // This really creates a new record version if one does not exist
498 _original = Table.RecordCache.NewRecord();
500 CheckValue (val, column);
501 column[_original] = val;
502 rowState = DataRowState.Modified;
506 /// Commits all the changes made to this row since the last time AcceptChanges was
509 public void AcceptChanges ()
511 EndEdit(); // in case it hasn't been called
513 case DataRowState.Unchanged:
515 case DataRowState.Added:
516 case DataRowState.Modified:
517 rowState = DataRowState.Unchanged;
519 case DataRowState.Deleted:
520 _table.Rows.RemoveInternal (this);
523 case DataRowState.Detached:
524 throw new RowNotInTableException("Cannot perform this operation on a row not in the table.");
526 // Accept from detached
527 if (_original >= 0) {
528 Table.RecordCache.DisposeRecord(_original);
530 _original = _current;
534 /// Begins an edit operation on a DataRow object.
536 public void BeginEdit ()
538 if (_inChangingEvent)
539 throw new InRowChangingEventException("Cannot call BeginEdit inside an OnRowChanging event.");
540 if (rowState == DataRowState.Deleted)
541 throw new DeletedRowInaccessibleException ();
542 if (!HasVersion (DataRowVersion.Proposed)) {
543 _proposed = Table.RecordCache.NewRecord();
544 foreach(DataColumn column in Table.Columns) {
545 column.DataContainer.CopyValue(_current,_proposed);
548 // setting editing to true stops validations on the row
553 /// Cancels the current edit on the row.
555 public void CancelEdit ()
557 if (_inChangingEvent)
558 throw new InRowChangingEventException("Cannot call CancelEdit inside an OnRowChanging event.");
560 if (HasVersion (DataRowVersion.Proposed)) {
561 Table.RecordCache.DisposeRecord(_proposed);
563 if (rowState == DataRowState.Modified) {
564 rowState = DataRowState.Unchanged;
570 /// Clears the errors for the row, including the RowError and errors set with
573 public void ClearErrors ()
575 rowError = String.Empty;
576 ColumnErrors.Clear();
580 /// Deletes the DataRow.
582 public void Delete ()
584 _table.DeletingDataRow(this, DataRowAction.Delete);
586 case DataRowState.Added:
587 // check what to do with child rows
588 CheckChildRows(DataRowAction.Delete);
589 _table.DeleteRowFromIndexes (this);
590 Table.Rows.RemoveInternal (this);
592 // if row was in Added state we move it to Detached.
595 case DataRowState.Deleted:
598 // check what to do with child rows
599 CheckChildRows(DataRowAction.Delete);
600 _table.DeleteRowFromIndexes (this);
601 rowState = DataRowState.Deleted;
604 _table.DeletedDataRow(this, DataRowAction.Delete);
607 // check the child rows of this row before deleting the row.
608 private void CheckChildRows(DataRowAction action)
611 // in this method we find the row that this row is in a relation with them.
612 // in shortly we find all child rows of this row.
613 // then we function according to the DeleteRule of the foriegnkey.
616 // 1. find if this row is attached to dataset.
617 // 2. find if EnforceConstraints is true.
618 // 3. find if there are any constraint on the table that the row is in.
619 if (_table.DataSet != null && _table.DataSet.EnforceConstraints && _table.Constraints.Count > 0)
621 foreach (DataTable table in _table.DataSet.Tables)
623 // loop on all ForeignKeyConstrain of the table.
624 foreach (ForeignKeyConstraint fk in table.Constraints.ForeignKeyConstraints)
626 if (fk.RelatedTable == _table)
629 if (action == DataRowAction.Delete)
630 rule = fk.DeleteRule;
632 rule = fk.UpdateRule;
633 CheckChildRows(fk, action, rule);
640 private void CheckChildRows(ForeignKeyConstraint fkc, DataRowAction action, Rule rule)
642 DataRow[] childRows = GetChildRows(fkc, DataRowVersion.Default);
645 case Rule.Cascade: // delete or change all relted rows.
646 if (childRows != null)
648 for (int j = 0; j < childRows.Length; j++)
650 // if action is delete we delete all child rows
651 if (action == DataRowAction.Delete)
653 if (childRows[j].RowState != DataRowState.Deleted)
654 childRows[j].Delete();
656 // if action is change we change the values in the child row
657 else if (action == DataRowAction.Change)
659 // change only the values in the key columns
660 // set the childcolumn value to the new parent row value
661 for (int k = 0; k < fkc.Columns.Length; k++)
662 childRows[j][fkc.Columns[k]] = this[fkc.RelatedColumns[k], DataRowVersion.Proposed];
667 case Rule.None: // throw an exception if there are any child rows.
668 if (childRows != null)
670 for (int j = 0; j < childRows.Length; j++)
672 if (childRows[j].RowState != DataRowState.Deleted)
674 string changeStr = "Cannot change this row because constraints are enforced on relation " + fkc.ConstraintName +", and changing this row will strand child rows.";
675 string delStr = "Cannot delete this row because constraints are enforced on relation " + fkc.ConstraintName +", and deleting this row will strand child rows.";
676 string message = action == DataRowAction.Delete ? delStr : changeStr;
677 throw new InvalidConstraintException(message);
682 case Rule.SetDefault: // set the values in the child rows to the defult value of the columns.
683 if (childRows != null && childRows.Length > 0) {
684 int defaultValuesRowIndex = childRows[0].Table.DefaultValuesRowIndex;
685 foreach(DataRow childRow in childRows) {
686 if (childRow.RowState != DataRowState.Deleted) {
687 int defaultIdx = childRow.IndexFromVersion(DataRowVersion.Default);
688 foreach(DataColumn column in fkc.Columns) {
689 column.DataContainer.CopyValue(defaultValuesRowIndex,defaultIdx);
695 case Rule.SetNull: // set the values in the child row to null.
696 if (childRows != null)
698 for (int j = 0; j < childRows.Length; j++)
700 DataRow child = childRows[j];
701 if (childRows[j].RowState != DataRowState.Deleted)
703 // set only the key columns to DBNull
704 for (int k = 0; k < fkc.Columns.Length; k++)
705 child.SetNull(fkc.Columns[k]);
715 /// Ends the edit occurring on the row.
717 public void EndEdit ()
719 if (_inChangingEvent)
720 throw new InRowChangingEventException("Cannot call EndEdit inside an OnRowChanging event.");
721 if (rowState == DataRowState.Detached)
727 CheckReadOnlyStatus();
728 if (HasVersion (DataRowVersion.Proposed))
730 _inChangingEvent = true;
733 _table.ChangingDataRow(this, DataRowAction.Change);
737 _inChangingEvent = false;
739 if (rowState == DataRowState.Unchanged)
740 rowState = DataRowState.Modified;
742 //Calling next method validates UniqueConstraints
746 if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
747 _table.Rows.ValidateDataRowInternal(this);
752 Table.RecordCache.DisposeRecord(_proposed);
757 // Now we are going to check all child rows of current row.
758 // In the case the cascade is true the child rows will look up for
759 // parent row. since lookup in index is always on current,
760 // we have to move proposed version of current row to current
761 // in the case of check child row failure we are rolling
762 // current row state back.
763 int backup = _current;
764 _current = _proposed;
765 bool editing_backup = editing;
768 // check all child rows.
769 CheckChildRows(DataRowAction.Change);
771 if (_original != backup) {
772 Table.RecordCache.DisposeRecord(backup);
775 catch (Exception ex) {
776 // if check child rows failed - rollback to previous state
777 // i.e. restore proposed and current versions
778 _proposed = _current;
780 editing = editing_backup;
781 // since we failed - propagate an exception
784 _table.ChangedDataRow(this, DataRowAction.Change);
789 /// Gets the child rows of this DataRow using the specified DataRelation.
791 public DataRow[] GetChildRows (DataRelation relation)
793 return GetChildRows (relation, DataRowVersion.Default);
797 /// Gets the child rows of a DataRow using the specified RelationName of a
800 public DataRow[] GetChildRows (string relationName)
802 return GetChildRows (Table.DataSet.Relations[relationName]);
806 /// Gets the child rows of a DataRow using the specified DataRelation, and
809 public DataRow[] GetChildRows (DataRelation relation, DataRowVersion version)
811 if (relation == null)
812 return new DataRow[0];
814 //if (this.Table == null || RowState == DataRowState.Detached)
815 if (this.Table == null)
816 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.");
818 if (relation.DataSet != this.Table.DataSet)
819 throw new ArgumentException();
821 if (_table != relation.ParentTable)
822 throw new InvalidConstraintException ("GetChildRow requires a row whose Table is " + relation.ParentTable + ", but the specified row's table is " + _table);
824 if (relation.ChildKeyConstraint != null)
825 return GetChildRows (relation.ChildKeyConstraint, version);
827 ArrayList rows = new ArrayList();
828 DataColumn[] parentColumns = relation.ParentColumns;
829 DataColumn[] childColumns = relation.ChildColumns;
830 int numColumn = parentColumns.Length;
831 if (HasVersion(version))
833 object[] vals = new object[parentColumns.Length];
834 for (int i = 0; i < vals.Length; i++)
835 vals[i] = this[parentColumns[i], version];
837 foreach (DataRow row in relation.ChildTable.Rows)
839 bool allColumnsMatch = false;
840 if (row.HasVersion(DataRowVersion.Default))
842 allColumnsMatch = true;
843 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt)
845 if (!vals[columnCnt].Equals(
846 row[childColumns[columnCnt], DataRowVersion.Default]))
848 allColumnsMatch = false;
853 if (allColumnsMatch) rows.Add(row);
856 throw new VersionNotFoundException("There is no " + version + " data to accces.");
858 DataRow[] result = relation.ChildTable.NewRowArray(rows.Count);
859 rows.CopyTo(result, 0);
864 /// Gets the child rows of a DataRow using the specified RelationName of a
865 /// DataRelation, and DataRowVersion.
867 public DataRow[] GetChildRows (string relationName, DataRowVersion version)
869 return GetChildRows (Table.DataSet.Relations[relationName], version);
872 private DataRow[] GetChildRows (ForeignKeyConstraint fkc, DataRowVersion version)
874 ArrayList rows = new ArrayList();
875 DataColumn[] parentColumns = fkc.RelatedColumns;
876 DataColumn[] childColumns = fkc.Columns;
877 int numColumn = parentColumns.Length;
878 if (HasVersion(version)) {
879 Index index = fkc.Index;
881 // get the child rows from the index
882 Node[] childNodes = index.FindAllSimple (parentColumns, IndexFromVersion(version));
883 for (int i = 0; i < childNodes.Length; i++) {
884 rows.Add (childNodes[i].Row);
887 else { // if there is no index we search manualy.
888 int curIndex = IndexFromVersion(DataRowVersion.Default);
889 int tmpRecord = fkc.Table.RecordCache.NewRecord();
892 for (int i = 0; i < numColumn; i++) {
893 // according to MSDN: the DataType value for both columns must be identical.
894 childColumns[i].DataContainer.CopyValue(parentColumns[i].DataContainer, curIndex, tmpRecord);
897 foreach (DataRow row in fkc.Table.Rows) {
898 bool allColumnsMatch = false;
899 if (row.HasVersion(DataRowVersion.Default)) {
900 allColumnsMatch = true;
901 int childIndex = row.IndexFromVersion(DataRowVersion.Default);
902 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) {
903 if (childColumns[columnCnt].DataContainer.CompareValues(childIndex, tmpRecord) != 0) {
904 allColumnsMatch = false;
909 if (allColumnsMatch) {
915 fkc.Table.RecordCache.DisposeRecord(tmpRecord);
919 throw new VersionNotFoundException("There is no " + version + " data to accces.");
921 DataRow[] result = fkc.Table.NewRowArray(rows.Count);
922 rows.CopyTo(result, 0);
927 /// Gets the error description of the specified DataColumn.
929 public string GetColumnError (DataColumn column)
931 return GetColumnError (_table.Columns.IndexOf(column));
935 /// Gets the error description for the column specified by index.
937 public string GetColumnError (int columnIndex)
939 if (columnIndex < 0 || columnIndex >= Table.Columns.Count)
940 throw new IndexOutOfRangeException ();
942 string retVal = null;
943 if (columnIndex < ColumnErrors.Count) {
944 retVal = (String) ColumnErrors[columnIndex];
946 return (retVal != null) ? retVal : String.Empty;
950 /// Gets the error description for the column, specified by name.
952 public string GetColumnError (string columnName)
954 return GetColumnError (_table.Columns.IndexOf(columnName));
958 /// Gets an array of columns that have errors.
960 public DataColumn[] GetColumnsInError ()
962 ArrayList dataColumns = new ArrayList ();
964 int columnOrdinal = 0;
965 foreach(String columnError in ColumnErrors) {
966 if (columnError != null && columnError != String.Empty) {
967 dataColumns.Add (_table.Columns[columnOrdinal]);
972 return (DataColumn[])(dataColumns.ToArray (typeof(DataColumn)));
976 /// Gets the parent row of a DataRow using the specified DataRelation.
978 public DataRow GetParentRow (DataRelation relation)
980 return GetParentRow (relation, DataRowVersion.Default);
984 /// Gets the parent row of a DataRow using the specified RelationName of a
987 public DataRow GetParentRow (string relationName)
989 return GetParentRow (relationName, DataRowVersion.Default);
993 /// Gets the parent row of a DataRow using the specified DataRelation, and
996 public DataRow GetParentRow (DataRelation relation, DataRowVersion version)
998 DataRow[] rows = GetParentRows(relation, version);
999 if (rows.Length == 0) return null;
1004 /// Gets the parent row of a DataRow using the specified RelationName of a
1005 /// DataRelation, and DataRowVersion.
1007 public DataRow GetParentRow (string relationName, DataRowVersion version)
1009 return GetParentRow (Table.DataSet.Relations[relationName], version);
1013 /// Gets the parent rows of a DataRow using the specified DataRelation.
1015 public DataRow[] GetParentRows (DataRelation relation)
1017 return GetParentRows (relation, DataRowVersion.Default);
1021 /// Gets the parent rows of a DataRow using the specified RelationName of a
1024 public DataRow[] GetParentRows (string relationName)
1026 return GetParentRows (relationName, DataRowVersion.Default);
1030 /// Gets the parent rows of a DataRow using the specified DataRelation, and
1033 public DataRow[] GetParentRows (DataRelation relation, DataRowVersion version)
1035 // TODO: Caching for better preformance
1036 if (relation == null)
1037 return new DataRow[0];
1039 if (this.Table == null)
1040 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.");
1042 if (relation.DataSet != this.Table.DataSet)
1043 throw new ArgumentException();
1045 if (_table != relation.ChildTable)
1046 throw new InvalidConstraintException ("GetParentRows requires a row whose Table is " + relation.ChildTable + ", but the specified row's table is " + _table);
1048 ArrayList rows = new ArrayList();
1049 DataColumn[] parentColumns = relation.ParentColumns;
1050 DataColumn[] childColumns = relation.ChildColumns;
1051 int numColumn = parentColumns.Length;
1052 if (HasVersion(version)) {
1053 Index indx = relation.ParentTable.GetIndexByColumns (parentColumns);
1055 (Table == null || Table.DataSet == null ||
1056 Table.DataSet.EnforceConstraints)) { // get the child rows from the index
1057 Node[] childNodes = indx.FindAllSimple(childColumns, IndexFromVersion(version));
1058 for (int i = 0; i < childNodes.Length; i++) {
1059 rows.Add (childNodes[i].Row);
1062 else { // no index so we have to search manualy.
1063 int curIndex = IndexFromVersion(DataRowVersion.Default);
1064 int tmpRecord = relation.ParentTable.RecordCache.NewRecord();
1066 for (int i = 0; i < numColumn; i++) {
1067 // according to MSDN: the DataType value for both columns must be identical.
1068 parentColumns[i].DataContainer.CopyValue(childColumns[i].DataContainer, curIndex, tmpRecord);
1071 foreach (DataRow row in relation.ParentTable.Rows) {
1072 bool allColumnsMatch = false;
1073 if (row.HasVersion(DataRowVersion.Default)) {
1074 allColumnsMatch = true;
1075 int parentIndex = row.IndexFromVersion(DataRowVersion.Default);
1076 for (int columnCnt = 0; columnCnt < numColumn; columnCnt++) {
1077 if (parentColumns[columnCnt].DataContainer.CompareValues(parentIndex, tmpRecord) != 0) {
1078 allColumnsMatch = false;
1083 if (allColumnsMatch) {
1089 relation.ParentTable.RecordCache.DisposeRecord(tmpRecord);
1093 throw new VersionNotFoundException("There is no " + version + " data to accces.");
1095 DataRow[] result = relation.ParentTable.NewRowArray(rows.Count);
1096 rows.CopyTo(result, 0);
1101 /// Gets the parent rows of a DataRow using the specified RelationName of a
1102 /// DataRelation, and DataRowVersion.
1104 public DataRow[] GetParentRows (string relationName, DataRowVersion version)
1106 return GetParentRows (Table.DataSet.Relations[relationName], version);
1110 /// Gets a value indicating whether a specified version exists.
1112 public bool HasVersion (DataRowVersion version)
1115 case DataRowVersion.Default:
1116 if (rowState == DataRowState.Deleted && !_inExpressionEvaluation)
1118 if (rowState == DataRowState.Detached)
1119 return _proposed >= 0;
1121 case DataRowVersion.Proposed:
1122 if (rowState == DataRowState.Deleted && !_inExpressionEvaluation)
1124 return _proposed >= 0;
1125 case DataRowVersion.Current:
1126 if ((rowState == DataRowState.Deleted && !_inExpressionEvaluation) || rowState == DataRowState.Detached)
1128 return _current >= 0;
1129 case DataRowVersion.Original:
1130 if (rowState == DataRowState.Detached)
1132 return _original >= 0;
1138 /// Gets a value indicating whether the specified DataColumn contains a null value.
1140 public bool IsNull (DataColumn column)
1142 return IsNull(column, DataRowVersion.Default);
1146 /// Gets a value indicating whether the column at the specified index contains a null
1149 public bool IsNull (int columnIndex)
1151 return IsNull(Table.Columns[columnIndex]);
1155 /// Gets a value indicating whether the named column contains a null value.
1157 public bool IsNull (string columnName)
1159 return IsNull(Table.Columns[columnName]);
1163 /// Gets a value indicating whether the specified DataColumn and DataRowVersion
1164 /// contains a null value.
1166 public bool IsNull (DataColumn column, DataRowVersion version)
1168 return column.DataContainer.IsNull(IndexFromVersion(version));
1172 /// Returns a value indicating whether all of the row columns specified contain a null value.
1174 internal bool IsNullColumns(DataColumn[] columns)
1176 bool allNull = true;
1177 for (int i = 0; i < columns.Length; i++)
1179 if (!IsNull(columns[i]))
1189 /// Rejects all changes made to the row since AcceptChanges was last called.
1191 public void RejectChanges ()
1193 if (RowState == DataRowState.Detached)
1194 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.");
1195 // If original is null, then nothing has happened since AcceptChanges
1196 // was last called. We have no "original" to go back to.
1197 if (HasVersion(DataRowVersion.Original)) {
1198 if (_current >= 0 ) {
1199 Table.RecordCache.DisposeRecord(_current);
1201 _current = _original;
1203 _table.ChangedDataRow (this, DataRowAction.Rollback);
1207 case DataRowState.Added:
1208 _table.DeleteRowFromIndexes (this);
1209 _table.Rows.RemoveInternal (this);
1211 case DataRowState.Modified:
1212 if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
1213 _table.Rows.ValidateDataRowInternal(this);
1214 rowState = DataRowState.Unchanged;
1216 case DataRowState.Deleted:
1217 rowState = DataRowState.Unchanged;
1218 if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
1219 _table.Rows.ValidateDataRowInternal(this);
1225 // If rows are just loaded via Xml the original values are null.
1226 // So in this case we have to remove all columns.
1227 // FIXME: I'm not realy sure, does this break something else, but
1230 if ((rowState & DataRowState.Added) > 0)
1232 _table.DeleteRowFromIndexes (this);
1233 _table.Rows.RemoveInternal (this);
1234 // if row was in Added state we move it to Detached.
1241 /// Sets the error description for a column specified as a DataColumn.
1243 public void SetColumnError (DataColumn column, string error)
1245 SetColumnError (_table.Columns.IndexOf (column), error);
1249 /// Sets the error description for a column specified by index.
1251 public void SetColumnError (int columnIndex, string error)
1253 if (columnIndex < 0 || columnIndex >= Table.Columns.Count)
1254 throw new IndexOutOfRangeException ();
1256 while(ColumnErrors.Count < columnIndex) {
1257 ColumnErrors.Add(null);
1259 ColumnErrors.Add(error);
1263 /// Sets the error description for a column specified by name.
1265 public void SetColumnError (string columnName, string error)
1267 SetColumnError (_table.Columns.IndexOf (columnName), error);
1271 /// Sets the value of the specified DataColumn to a null value.
1273 protected void SetNull (DataColumn column)
1275 this[column] = DBNull.Value;
1279 /// Sets the parent row of a DataRow with specified new parent DataRow.
1281 public void SetParentRow (DataRow parentRow)
1283 SetParentRow(parentRow, null);
1287 /// Sets the parent row of a DataRow with specified new parent DataRow and
1290 public void SetParentRow (DataRow parentRow, DataRelation relation)
1292 if (_table == null || parentRow.Table == null)
1293 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.");
1295 if (parentRow != null && _table.DataSet != parentRow.Table.DataSet)
1296 throw new ArgumentException();
1299 if (relation == null)
1301 foreach (DataRelation parentRel in _table.ParentRelations)
1303 DataColumn[] childCols = parentRel.ChildColumns;
1304 DataColumn[] parentCols = parentRel.ParentColumns;
1306 for (int i = 0; i < parentCols.Length; i++)
1308 if (parentRow == null)
1309 this[childCols[i].Ordinal] = DBNull.Value;
1311 this[childCols[i].Ordinal] = parentRow[parentCols[i]];
1318 DataColumn[] childCols = relation.ChildColumns;
1319 DataColumn[] parentCols = relation.ParentColumns;
1321 for (int i = 0; i < parentCols.Length; i++)
1323 if (parentRow == null)
1324 this[childCols[i].Ordinal] = DBNull.Value;
1326 this[childCols[i].Ordinal] = parentRow[parentCols[i]];
1332 //Copy all values of this DataaRow to the row parameter.
1333 internal void CopyValuesToRow(DataRow row)
1336 throw new ArgumentNullException("row");
1338 throw new ArgumentException("'row' is the same as this object");
1340 foreach(DataColumn column in Table.Columns) {
1341 DataColumn targetColumn = row.Table.Columns[column.ColumnName];
1342 //if a column with the same name exists in both rows copy the values
1343 if(targetColumn != null) {
1344 int index = targetColumn.Ordinal;
1345 if (HasVersion(DataRowVersion.Original)) {
1346 if (row._original < 0) {
1347 row._original = row.Table.RecordCache.NewRecord();
1349 object val = column[_original];
1350 row.CheckValue(val, targetColumn);
1351 targetColumn[row._original] = val;
1353 if (HasVersion(DataRowVersion.Current)) {
1354 if (row._current < 0) {
1355 row._current = row.Table.RecordCache.NewRecord();
1357 object val = column[_current];
1358 row.CheckValue(val, targetColumn);
1359 targetColumn[row._current] = val;
1361 if (HasVersion(DataRowVersion.Proposed)) {
1362 if (row._proposed < 0) {
1363 row._proposed = row.Table.RecordCache.NewRecord();
1365 object val = column[row._proposed];
1366 row.CheckValue(val, targetColumn);
1367 targetColumn[row._proposed] = val;
1370 //Saving the current value as the column value
1371 object defaultVal = column [IndexFromVersion (DataRowVersion.Default)];
1372 row [index] = defaultVal;
1378 // Copy row state - rowState and errors
1379 internal void CopyState(DataRow row)
1381 row.rowState = RowState;
1382 row.RowError = RowError;
1383 row.ColumnErrors = (ArrayList)ColumnErrors.Clone();
1386 internal bool IsRowChanged(DataRowState rowState) {
1387 if((RowState & rowState) != 0)
1390 //we need to find if child rows of this row changed.
1391 //if yes - we should return true
1393 // if the rowState is deleted we should get the original version of the row
1394 // else - we should get the current version of the row.
1395 DataRowVersion version = (rowState == DataRowState.Deleted) ? DataRowVersion.Original : DataRowVersion.Current;
1396 int count = Table.ChildRelations.Count;
1397 for (int i = 0; i < count; i++){
1398 DataRelation rel = Table.ChildRelations[i];
1399 DataRow[] childRows = GetChildRows(rel, version);
1400 for (int j = 0; j < childRows.Length; j++){
1401 if (childRows[j].IsRowChanged(rowState))
1409 internal bool HasParentCollection
1413 return _hasParentCollection;
1417 _hasParentCollection = value;
1421 internal void CheckNullConstraints()
1423 if (_nullConstraintViolation) {
1424 if (HasVersion(DataRowVersion.Proposed)) {
1425 foreach(DataColumn column in Table.Columns) {
1426 if (IsNull(column) && !column.AllowDBNull) {
1427 throw new NoNullAllowedException(_nullConstraintMessage);
1431 _nullConstraintViolation = false;
1435 internal void CheckReadOnlyStatus()
1437 if (HasVersion(DataRowVersion.Proposed)) {
1438 int defaultIdx = IndexFromVersion(DataRowVersion.Default);
1439 foreach(DataColumn column in Table.Columns) {
1440 if ((column.DataContainer.CompareValues(defaultIdx,_proposed) != 0) && column.ReadOnly) {
1441 throw new ReadOnlyException();
1447 #endregion // Methods