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.
57 private DataTable _table;
59 internal int _original = -1;
60 internal int _current = -1;
61 internal int _proposed = -1;
63 private ArrayList _columnErrors;
64 private string rowError;
65 internal int xmlRowID = 0;
66 internal bool _nullConstraintViolation;
67 private string _nullConstraintMessage;
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 rowError = String.Empty;
91 // create mapped XmlDataElement
92 DataSet ds = _table.DataSet;
93 if (ds != null && ds._xmlDataDocument != null)
94 mappedElement = new XmlDataDocument.XmlDataElement (this, _table.Prefix, _table.TableName, _table.Namespace, ds._xmlDataDocument);
97 internal DataRow(DataTable table,int rowId)
103 #endregion // Constructors
107 private ArrayList ColumnErrors
110 if (_columnErrors == null) {
111 _columnErrors = new ArrayList();
113 return _columnErrors;
117 _columnErrors = value;
122 /// Gets a value indicating whether there are errors in a row.
124 public bool HasErrors {
126 if (RowError != string.Empty)
129 foreach(String columnError in ColumnErrors) {
130 if (columnError != null && columnError != string.Empty) {
139 /// Gets or sets the data stored in the column specified by name.
141 public object this[string columnName] {
142 get { return this[columnName, DataRowVersion.Default]; }
144 int columnIndex = _table.Columns.IndexOf (columnName);
145 if (columnIndex == -1)
146 throw new IndexOutOfRangeException ();
147 this[columnIndex] = value;
152 /// Gets or sets the data stored in specified DataColumn
154 public object this[DataColumn column] {
157 return this[column, DataRowVersion.Default];}
159 int columnIndex = _table.Columns.IndexOf (column);
160 if (columnIndex == -1)
161 throw new ArgumentException ("The column does not belong to this table.");
162 this[columnIndex] = value;
167 /// Gets or sets the data stored in column specified by index.
169 public object this[int columnIndex] {
170 get { return this[columnIndex, DataRowVersion.Default]; }
172 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
173 throw new IndexOutOfRangeException ();
174 if (RowState == DataRowState.Deleted)
175 throw new DeletedRowInaccessibleException ();
177 DataColumn column = _table.Columns[columnIndex];
178 _table.ChangingDataColumn (this, column, value);
180 if (value == null && column.DataType != typeof(string)) {
181 throw new ArgumentException("Cannot set column " + column.ColumnName + " to be null, Please use DBNull instead");
184 CheckValue (value, column);
185 bool orginalEditing = Proposed >= 0;
186 if (!orginalEditing) {
190 column[Proposed] = value;
191 _table.ChangedDataColumn (this, column, value);
192 if (!orginalEditing) {
199 /// Gets the specified version of data stored in the named column.
201 public object this[string columnName, DataRowVersion version] {
203 int columnIndex = _table.Columns.IndexOf (columnName);
204 if (columnIndex == -1)
205 throw new IndexOutOfRangeException ();
206 return this[columnIndex, version];
211 /// Gets the specified version of data stored in the specified DataColumn.
213 public object this[DataColumn column, DataRowVersion version] {
215 if (column.Table != Table)
216 throw new ArgumentException ("The column does not belong to this table.");
217 int columnIndex = column.Ordinal;
218 return this[columnIndex, version];
223 /// Set a value for the column into the offset specified by the version.<br>
224 /// If the value is auto increment or null, necessary auto increment value
225 /// or the default value will be used.
227 internal void SetValue (int column, object value, int version)
229 DataColumn dc = Table.Columns[column];
231 if (value == null && ! dc.AutoIncrement) // set default value / auto increment
232 value = dc.DefaultValue;
234 Table.ChangingDataColumn (this, dc, value);
235 CheckValue (value, dc);
236 if ( ! dc.AutoIncrement)
237 dc [version] = value;
238 else if (_proposed >= 0 && _proposed != version) // proposed holds the AI
239 dc [version] = dc [_proposed];
243 /// Gets the data stored in the column, specified by index and version of the data to
246 public object this[int columnIndex, DataRowVersion version] {
248 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
249 throw new IndexOutOfRangeException ();
251 DataColumn column = _table.Columns[columnIndex];
252 int recordIndex = IndexFromVersion(version);
254 if (column.Expression != String.Empty) {
255 object o = column.CompiledExpression.Eval (this);
256 if (o != null && o != DBNull.Value) {
257 o = Convert.ChangeType (o, column.DataType);
259 column[recordIndex] = o;
260 return column[recordIndex];
263 return column[recordIndex];
268 /// Gets or sets all of the values for this row through an array.
270 public object[] ItemArray {
272 // Accessing deleted rows
273 if (RowState == DataRowState.Deleted)
274 throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
277 if (RowState == DataRowState.Detached)
278 // Check if datarow is removed from the table.
280 throw new RowNotInTableException(
281 "This row has been removed from a table and does not have any data."
282 +" BeginEdit() will allow creation of new data in this row.");
288 object[] items = new object[_table.Columns.Count];
290 foreach(DataColumn column in _table.Columns)
291 items[column.Ordinal] = column[index];
295 if (value.Length > _table.Columns.Count)
296 throw new ArgumentException ();
298 if (RowState == DataRowState.Deleted)
299 throw new DeletedRowInaccessibleException ();
303 DataColumnChangeEventArgs e = new DataColumnChangeEventArgs();
304 foreach(DataColumn column in _table.Columns) {
305 int i = column.Ordinal;
306 object newVal = (i < value.Length) ? value[i] : null;
311 e.Initialize(this, column, newVal);
312 CheckValue (e.ProposedValue, column);
313 _table.RaiseOnColumnChanging(e);
314 column[Proposed] = e.ProposedValue;
315 _table.RaiseOnColumnChanged(e);
323 /// Gets the current state of the row in regards to its relationship to the
324 /// DataRowCollection.
326 public DataRowState RowState {
329 if ((Original == -1) && (Current == -1))
330 return DataRowState.Detached;
331 if (Original == Current)
332 return DataRowState.Unchanged;
334 return DataRowState.Added;
336 return DataRowState.Deleted;
337 return DataRowState.Modified;
342 /// Gets the DataTable for which this row has a schema.
344 public DataTable Table {
351 /// Gets and sets index of row. This is used from
354 internal int XmlRowID {
364 /// Gets and sets index of row.
375 internal int Original
382 //Table.RecordCache[_original] = null;
383 Table.RecordCache[value] = this;
396 //Table.RecordCache[_current] = null;
397 Table.RecordCache[value] = this;
403 internal int Proposed
410 //Table.RecordCache[_proposed] = null;
411 Table.RecordCache[value] = this;
421 //FIXME?: Couldn't find a way to set the RowState when adding the DataRow
422 //to a Datatable so I added this method. Delete if there is a better way.
423 internal void AttachRow() {
424 if (Proposed != -1) {
426 Table.RecordCache.DisposeRecord(Current);
433 //FIXME?: Couldn't find a way to set the RowState when removing the DataRow
434 //from a Datatable so I added this method. Delete if there is a better way.
435 internal void DetachRow() {
437 _table.RecordCache.DisposeRecord(Proposed);
438 if (Proposed == Current) {
441 if (Proposed == Original) {
448 _table.RecordCache.DisposeRecord(Current);
449 if (Current == Original) {
456 _table.RecordCache.DisposeRecord(Original);
461 _hasParentCollection = false;
464 internal void ImportRecord(int record)
466 if (HasVersion(DataRowVersion.Proposed)) {
467 Table.RecordCache.DisposeRecord(Proposed);
472 foreach(DataColumn column in Table.Columns.AutoIncrmentColumns) {
473 column.UpdateAutoIncrementValue(column.DataContainer.GetInt64(Proposed));
478 private void CheckValue (object v, DataColumn col)
480 if (_hasParentCollection && col.ReadOnly) {
481 throw new ReadOnlyException ();
484 if (v == null || v == DBNull.Value) {
485 if (col.AllowDBNull || col.AutoIncrement || col.DefaultValue != DBNull.Value) {
489 //Constraint violations during data load is raise in DataTable EndLoad
490 this._nullConstraintViolation = true;
491 if (this.Table._duringDataLoad) {
492 this.Table._nullConstraintViolationDuringDataLoad = true;
494 _nullConstraintMessage = "Column '" + col.ColumnName + "' does not allow nulls.";
500 /// Gets or sets the custom error description for a row.
502 public string RowError {
511 internal int IndexFromVersion(DataRowVersion version)
514 case DataRowVersion.Default:
522 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.");
524 throw new DeletedRowInaccessibleException("Deleted row information cannot be accessed through the row.");
526 case DataRowVersion.Proposed:
527 return AssertValidVersionIndex(version, Proposed);
528 case DataRowVersion.Current:
529 return AssertValidVersionIndex(version, Current);
530 case DataRowVersion.Original:
531 return AssertValidVersionIndex(version, Original);
533 throw new DataException ("Version must be Original, Current, or Proposed.");
537 private int AssertValidVersionIndex(DataRowVersion version, int index) {
541 throw new VersionNotFoundException(String.Format("There is no {0} data to accces.", version));
544 internal DataRowVersion VersionFromIndex(int index) {
546 throw new ArgumentException("Index must not be negative.");
548 // the order of ifs matters
549 if (index == Current)
550 return DataRowVersion.Current;
551 if (index == Original)
552 return DataRowVersion.Original;
553 if (index == Proposed)
554 return DataRowVersion.Proposed;
556 throw new ArgumentException(String.Format("The index {0} does not belong to this row.", index) );
559 internal XmlDataDocument.XmlDataElement DataElement {
560 get { return mappedElement; }
561 set { mappedElement = value; }
564 internal void SetOriginalValue (string columnName, object val)
566 DataColumn column = _table.Columns[columnName];
567 _table.ChangingDataColumn (this, column, val);
569 if (Original < 0 || Original == Current) {
570 Original = Table.RecordCache.NewRecord();
572 CheckValue (val, column);
573 column[Original] = val;
577 /// Commits all the changes made to this row since the last time AcceptChanges was
580 public void AcceptChanges ()
582 EndEdit(); // in case it hasn't been called
584 _table.ChangingDataRow (this, DataRowAction.Commit);
585 CheckChildRows(DataRowAction.Commit);
587 case DataRowState.Unchanged:
589 case DataRowState.Added:
590 case DataRowState.Modified:
591 int original = Original;
592 DataRowState oldState = RowState;
594 Table.RecordCache.DisposeRecord(Original);
597 foreach (Index index in Table.Indexes)
598 index.Update(this, original, DataRowVersion.Original, oldState);
600 case DataRowState.Deleted:
601 Table.DeleteRowFromIndexes(this);
602 _table.Rows.RemoveInternal (this);
605 case DataRowState.Detached:
606 throw new RowNotInTableException("Cannot perform this operation on a row not in the table.");
609 _table.ChangedDataRow (this, DataRowAction.Commit);
613 /// Begins an edit operation on a DataRow object.
615 public void BeginEdit ()
617 if (_inChangingEvent)
618 throw new InRowChangingEventException("Cannot call BeginEdit inside an OnRowChanging event.");
619 if (RowState == DataRowState.Deleted)
620 throw new DeletedRowInaccessibleException ();
622 if (!HasVersion (DataRowVersion.Proposed)) {
623 Proposed = Table.RecordCache.NewRecord();
624 int from = HasVersion(DataRowVersion.Current) ? Current : Table.DefaultValuesRowIndex;
625 for(int i = 0; i < Table.Columns.Count; i++){
626 DataColumn column = Table.Columns[i];
627 column.DataContainer.CopyValue(from,Proposed);
633 /// Cancels the current edit on the row.
635 public void CancelEdit ()
637 if (_inChangingEvent) {
638 throw new InRowChangingEventException("Cannot call CancelEdit inside an OnRowChanging event.");
641 if (HasVersion (DataRowVersion.Proposed)) {
642 int oldRecord = Proposed;
643 DataRowState oldState = RowState;
644 Table.RecordCache.DisposeRecord(Proposed);
647 foreach(Index index in Table.Indexes)
648 index.Update(this,oldRecord, DataRowVersion.Proposed, oldState);
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:
679 case DataRowState.Detached:
682 // check what to do with child rows
683 CheckChildRows(DataRowAction.Delete);
687 int current = Current;
688 DataRowState oldState = RowState;
689 if (Current != Original) {
690 _table.RecordCache.DisposeRecord(Current);
693 foreach(Index index in Table.Indexes)
694 index.Update(this, current, DataRowVersion.Current, oldState);
696 _table.DeletedDataRow(this, DataRowAction.Delete);
699 // check the child rows of this row before deleting the row.
700 private void CheckChildRows(DataRowAction action)
703 // in this method we find the row that this row is in a relation with them.
704 // in shortly we find all child rows of this row.
705 // then we function according to the DeleteRule of the foriegnkey.
708 // 1. find if this row is attached to dataset.
709 // 2. find if EnforceConstraints is true.
710 // 3. find if there are any constraint on the table that the row is in.
711 if (_table.DataSet != null && _table.DataSet.EnforceConstraints && _table.Constraints.Count > 0)
713 foreach (DataTable table in _table.DataSet.Tables)
715 // loop on all ForeignKeyConstrain of the table.
716 foreach (Constraint constraint in table.Constraints) {
717 if (constraint is ForeignKeyConstraint) {
718 ForeignKeyConstraint fk = (ForeignKeyConstraint) constraint;
719 if (fk.RelatedTable == _table) {
721 case DataRowAction.Delete:
722 CheckChildRows(fk, action, fk.DeleteRule);
724 case DataRowAction.Commit:
725 case DataRowAction.Rollback:
726 if (fk.AcceptRejectRule != AcceptRejectRule.None)
727 CheckChildRows(fk, action, Rule.Cascade);
730 CheckChildRows(fk, action, fk.UpdateRule);
740 private void CheckChildRows(ForeignKeyConstraint fkc, DataRowAction action, Rule rule)
742 DataRow[] childRows = GetChildRows(fkc, DataRowVersion.Current);
745 case Rule.Cascade: // delete or change all relted rows.
746 if (childRows != null)
748 for (int j = 0; j < childRows.Length; j++)
750 // if action is delete we delete all child rows
752 case DataRowAction.Delete: {
753 if (childRows[j].RowState != DataRowState.Deleted)
754 childRows[j].Delete();
758 // if action is change we change the values in the child row
759 case DataRowAction.Change: {
760 // change only the values in the key columns
761 // set the childcolumn value to the new parent row value
762 for (int k = 0; k < fkc.Columns.Length; k++)
763 if (!fkc.RelatedColumns [k].DataContainer [Original].Equals (fkc.RelatedColumns [k].DataContainer [Proposed]))
764 childRows[j][fkc.Columns[k]] = this[fkc.RelatedColumns[k], DataRowVersion.Proposed];
769 case DataRowAction.Rollback: {
770 if (childRows[j].RowState != DataRowState.Unchanged)
771 childRows[j].RejectChanges ();
778 case Rule.None: // throw an exception if there are any child rows.
779 if (childRows != null)
781 for (int j = 0; j < childRows.Length; j++)
783 if (childRows[j].RowState != DataRowState.Deleted)
785 string changeStr = "Cannot change this row because constraints are enforced on relation " + fkc.ConstraintName +", and changing this row will strand child rows.";
786 string delStr = "Cannot delete this row because constraints are enforced on relation " + fkc.ConstraintName +", and deleting this row will strand child rows.";
787 string message = action == DataRowAction.Delete ? delStr : changeStr;
788 throw new InvalidConstraintException(message);
793 case Rule.SetDefault: // set the values in the child rows to the defult value of the columns.
794 if (childRows != null && childRows.Length > 0) {
795 int defaultValuesRowIndex = childRows[0].Table.DefaultValuesRowIndex;
796 foreach(DataRow childRow in childRows) {
797 if (childRow.RowState != DataRowState.Deleted) {
798 int defaultIdx = childRow.IndexFromVersion(DataRowVersion.Default);
799 foreach(DataColumn column in fkc.Columns) {
800 column.DataContainer.CopyValue(defaultValuesRowIndex,defaultIdx);
806 case Rule.SetNull: // set the values in the child row to null.
807 if (childRows != null)
809 for (int j = 0; j < childRows.Length; j++)
811 DataRow child = childRows[j];
812 if (childRows[j].RowState != DataRowState.Deleted)
814 // set only the key columns to DBNull
815 for (int k = 0; k < fkc.Columns.Length; k++)
816 child.SetNull(fkc.Columns[k]);
826 /// Ends the edit occurring on the row.
828 public void EndEdit ()
830 if (_inChangingEvent)
831 throw new InRowChangingEventException("Cannot call EndEdit inside an OnRowChanging event.");
833 if (RowState == DataRowState.Detached)
836 if (HasVersion (DataRowVersion.Proposed))
838 CheckReadOnlyStatus();
840 _inChangingEvent = true;
843 _table.ChangingDataRow(this, DataRowAction.Change);
847 _inChangingEvent = false;
850 DataRowState oldState = RowState;
852 int oldRecord = Current;
856 if (!Table._duringDataLoad) {
857 foreach(Index index in Table.Indexes) {
858 index.Update(this,oldRecord, DataRowVersion.Current, oldState);
865 // restore previous state to let the cascade update to find the rows
869 CheckChildRows(DataRowAction.Change);
876 int proposed = Proposed >= 0 ? Proposed : Current;
878 if (!Table._duringDataLoad) {
879 foreach(Index index in Table.Indexes) {
880 index.Update(this,proposed, DataRowVersion.Current, RowState);
886 if (Original != oldRecord) {
887 Table.RecordCache.DisposeRecord(oldRecord);
890 // Note : row state must not be changed before all the job on indexes finished,
891 // since the indexes works with recods rather than with rows and the decision
892 // which of row records to choose depends on row state.
893 _table.ChangedDataRow(this, DataRowAction.Change);
898 /// Gets the child rows of this DataRow using the specified DataRelation.
900 public DataRow[] GetChildRows (DataRelation relation)
902 return GetChildRows (relation, DataRowVersion.Default);
906 /// Gets the child rows of a DataRow using the specified RelationName of a
909 public DataRow[] GetChildRows (string relationName)
911 return GetChildRows (Table.DataSet.Relations[relationName]);
915 /// Gets the child rows of a DataRow using the specified DataRelation, and
918 public DataRow[] GetChildRows (DataRelation relation, DataRowVersion version)
920 if (relation == null)
921 return Table.NewRowArray(0);
923 if (this.Table == null)
924 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.");
926 if (relation.DataSet != this.Table.DataSet)
927 throw new ArgumentException();
929 if (_table != relation.ParentTable)
930 throw new InvalidConstraintException ("GetChildRow requires a row whose Table is " + relation.ParentTable + ", but the specified row's table is " + _table);
932 if (relation.ChildKeyConstraint != null)
933 return GetChildRows (relation.ChildKeyConstraint, version);
935 ArrayList rows = new ArrayList();
936 DataColumn[] parentColumns = relation.ParentColumns;
937 DataColumn[] childColumns = relation.ChildColumns;
938 int numColumn = parentColumns.Length;
939 DataRow[] result = null;
941 int versionIndex = IndexFromVersion(version);
942 int tmpRecord = relation.ChildTable.RecordCache.NewRecord();
945 for (int i = 0; i < numColumn; i++) {
946 // according to MSDN: the DataType value for both columns must be identical.
947 childColumns[i].DataContainer.CopyValue(parentColumns[i].DataContainer, versionIndex, tmpRecord);
950 Index index = relation.ChildTable.FindIndex(childColumns);
953 int[] records = index.FindAll(tmpRecord);
954 result = relation.ChildTable.NewRowArray(records.Length);
955 for(int i=0; i < records.Length; i++) {
956 result[i] = relation.ChildTable.RecordCache[records[i]];
960 foreach (DataRow row in relation.ChildTable.Rows) {
961 bool allColumnsMatch = false;
962 if (row.HasVersion(DataRowVersion.Default)) {
963 allColumnsMatch = true;
964 int childIndex = row.IndexFromVersion(DataRowVersion.Default);
965 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) {
966 if (childColumns[columnCnt].DataContainer.CompareValues(childIndex, tmpRecord) != 0) {
967 allColumnsMatch = false;
972 if (allColumnsMatch) rows.Add(row);
974 result = relation.ChildTable.NewRowArray(rows.Count);
975 rows.CopyTo(result, 0);
979 relation.ChildTable.RecordCache.DisposeRecord(tmpRecord);
986 /// Gets the child rows of a DataRow using the specified RelationName of a
987 /// DataRelation, and DataRowVersion.
989 public DataRow[] GetChildRows (string relationName, DataRowVersion version)
991 return GetChildRows (Table.DataSet.Relations[relationName], version);
994 private DataRow[] GetChildRows (ForeignKeyConstraint fkc, DataRowVersion version)
996 ArrayList rows = new ArrayList();
997 DataColumn[] parentColumns = fkc.RelatedColumns;
998 DataColumn[] childColumns = fkc.Columns;
999 int numColumn = parentColumns.Length;
1001 Index index = fkc.Index;
1003 int curIndex = IndexFromVersion(version);
1004 int tmpRecord = fkc.Table.RecordCache.NewRecord();
1005 for (int i = 0; i < numColumn; i++) {
1006 // according to MSDN: the DataType value for both columns must be identical.
1007 childColumns[i].DataContainer.CopyValue(parentColumns[i].DataContainer, curIndex, tmpRecord);
1011 if (index != null) {
1012 // get the child rows from the index
1013 int[] childRecords = index.FindAll(tmpRecord);
1014 for (int i = 0; i < childRecords.Length; i++) {
1015 rows.Add (childColumns[i].Table.RecordCache[childRecords[i]]);
1018 else { // if there is no index we search manualy.
1019 foreach (DataRow row in fkc.Table.Rows) {
1020 bool allColumnsMatch = false;
1021 if (row.HasVersion(DataRowVersion.Default)) {
1022 allColumnsMatch = true;
1023 int childIndex = row.IndexFromVersion(DataRowVersion.Default);
1024 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) {
1025 if (childColumns[columnCnt].DataContainer.CompareValues(childIndex, tmpRecord) != 0) {
1026 allColumnsMatch = false;
1031 if (allColumnsMatch) {
1038 fkc.Table.RecordCache.DisposeRecord(tmpRecord);
1041 DataRow[] result = fkc.Table.NewRowArray(rows.Count);
1042 rows.CopyTo(result, 0);
1047 /// Gets the error description of the specified DataColumn.
1049 public string GetColumnError (DataColumn column)
1052 throw new ArgumentNullException("column");
1054 int index = _table.Columns.IndexOf(column);
1056 throw new ArgumentException(String.Format("Column '{0}' does not belong to table {1}.", column.ColumnName, Table.TableName));
1058 return GetColumnError (index);
1062 /// Gets the error description for the column specified by index.
1064 public string GetColumnError (int columnIndex)
1066 if (columnIndex < 0 || columnIndex >= Table.Columns.Count)
1067 throw new IndexOutOfRangeException ();
1069 string retVal = null;
1070 if (columnIndex < ColumnErrors.Count) {
1071 retVal = (String) ColumnErrors[columnIndex];
1073 return (retVal != null) ? retVal : String.Empty;
1077 /// Gets the error description for the column, specified by name.
1079 public string GetColumnError (string columnName)
1081 return GetColumnError (_table.Columns.IndexOf(columnName));
1085 /// Gets an array of columns that have errors.
1087 public DataColumn[] GetColumnsInError ()
1089 ArrayList dataColumns = new ArrayList ();
1091 int columnOrdinal = 0;
1092 foreach(String columnError in ColumnErrors) {
1093 if (columnError != null && columnError != String.Empty) {
1094 dataColumns.Add (_table.Columns[columnOrdinal]);
1099 return (DataColumn[])(dataColumns.ToArray (typeof(DataColumn)));
1103 /// Gets the parent row of a DataRow using the specified DataRelation.
1105 public DataRow GetParentRow (DataRelation relation)
1107 return GetParentRow (relation, DataRowVersion.Default);
1111 /// Gets the parent row of a DataRow using the specified RelationName of a
1114 public DataRow GetParentRow (string relationName)
1116 return GetParentRow (relationName, DataRowVersion.Default);
1120 /// Gets the parent row of a DataRow using the specified DataRelation, and
1123 public DataRow GetParentRow (DataRelation relation, DataRowVersion version)
1125 DataRow[] rows = GetParentRows(relation, version);
1126 if (rows.Length == 0) return null;
1131 /// Gets the parent row of a DataRow using the specified RelationName of a
1132 /// DataRelation, and DataRowVersion.
1134 public DataRow GetParentRow (string relationName, DataRowVersion version)
1136 return GetParentRow (Table.DataSet.Relations[relationName], version);
1140 /// Gets the parent rows of a DataRow using the specified DataRelation.
1142 public DataRow[] GetParentRows (DataRelation relation)
1144 return GetParentRows (relation, DataRowVersion.Default);
1148 /// Gets the parent rows of a DataRow using the specified RelationName of a
1151 public DataRow[] GetParentRows (string relationName)
1153 return GetParentRows (relationName, DataRowVersion.Default);
1157 /// Gets the parent rows of a DataRow using the specified DataRelation, and
1160 public DataRow[] GetParentRows (DataRelation relation, DataRowVersion version)
1162 // TODO: Caching for better preformance
1163 if (relation == null)
1164 return Table.NewRowArray(0);
1166 if (this.Table == null)
1167 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.");
1169 if (relation.DataSet != this.Table.DataSet)
1170 throw new ArgumentException();
1172 if (_table != relation.ChildTable)
1173 throw new InvalidConstraintException ("GetParentRows requires a row whose Table is " + relation.ChildTable + ", but the specified row's table is " + _table);
1175 ArrayList rows = new ArrayList();
1176 DataColumn[] parentColumns = relation.ParentColumns;
1177 DataColumn[] childColumns = relation.ChildColumns;
1178 int numColumn = parentColumns.Length;
1180 int curIndex = IndexFromVersion(version);
1181 int tmpRecord = relation.ParentTable.RecordCache.NewRecord();
1182 for (int i = 0; i < numColumn; i++) {
1183 // according to MSDN: the DataType value for both columns must be identical.
1184 parentColumns[i].DataContainer.CopyValue(childColumns[i].DataContainer, curIndex, tmpRecord);
1188 Index index = relation.ParentTable.FindIndex(parentColumns);
1189 if (index != null) { // get the parent rows from the index
1190 int[] parentRecords = index.FindAll(tmpRecord);
1191 for (int i = 0; i < parentRecords.Length; i++) {
1192 rows.Add (parentColumns[i].Table.RecordCache[parentRecords[i]]);
1195 else { // no index so we have to search manualy.
1196 foreach (DataRow row in relation.ParentTable.Rows) {
1197 bool allColumnsMatch = false;
1198 if (row.HasVersion(DataRowVersion.Default)) {
1199 allColumnsMatch = true;
1200 int parentIndex = row.IndexFromVersion(DataRowVersion.Default);
1201 for (int columnCnt = 0; columnCnt < numColumn; columnCnt++) {
1202 if (parentColumns[columnCnt].DataContainer.CompareValues(parentIndex, tmpRecord) != 0) {
1203 allColumnsMatch = false;
1208 if (allColumnsMatch) {
1215 relation.ParentTable.RecordCache.DisposeRecord(tmpRecord);
1218 DataRow[] result = relation.ParentTable.NewRowArray(rows.Count);
1219 rows.CopyTo(result, 0);
1224 /// Gets the parent rows of a DataRow using the specified RelationName of a
1225 /// DataRelation, and DataRowVersion.
1227 public DataRow[] GetParentRows (string relationName, DataRowVersion version)
1229 return GetParentRows (Table.DataSet.Relations[relationName], version);
1233 /// Gets a value indicating whether a specified version exists.
1235 public bool HasVersion (DataRowVersion version)
1238 case DataRowVersion.Default:
1239 return (Proposed >= 0 || Current >= 0);
1240 case DataRowVersion.Proposed:
1241 return Proposed >= 0;
1242 case DataRowVersion.Current:
1243 return Current >= 0;
1244 case DataRowVersion.Original:
1245 return Original >= 0;
1247 return IndexFromVersion(version) >= 0;
1252 /// Gets a value indicating whether the specified DataColumn contains a null value.
1254 public bool IsNull (DataColumn column)
1256 return IsNull(column, DataRowVersion.Default);
1260 /// Gets a value indicating whether the column at the specified index contains a null
1263 public bool IsNull (int columnIndex)
1265 return IsNull(Table.Columns[columnIndex]);
1269 /// Gets a value indicating whether the named column contains a null value.
1271 public bool IsNull (string columnName)
1273 return IsNull(Table.Columns[columnName]);
1277 /// Gets a value indicating whether the specified DataColumn and DataRowVersion
1278 /// contains a null value.
1280 public bool IsNull (DataColumn column, DataRowVersion version)
1282 object o = this[column,version];
1283 return column.DataContainer.IsNull(IndexFromVersion(version));
1287 /// Returns a value indicating whether all of the row columns specified contain a null value.
1289 internal bool IsNullColumns(DataColumn[] columns)
1291 bool allNull = true;
1292 for (int i = 0; i < columns.Length; i++)
1294 if (!IsNull(columns[i]))
1304 /// Rejects all changes made to the row since AcceptChanges was last called.
1306 public void RejectChanges ()
1308 if (RowState == DataRowState.Detached)
1309 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.");
1310 // If original is null, then nothing has happened since AcceptChanges
1311 // was last called. We have no "original" to go back to.
1313 _table.ChangedDataRow (this, DataRowAction.Rollback);
1316 //TODO : Need to Verify the constraints..
1318 case DataRowState.Added:
1319 _table.DeleteRowFromIndexes (this);
1320 _table.Rows.RemoveInternal (this);
1323 case DataRowState.Modified:
1324 Table.RecordCache.DisposeRecord (Current);
1325 CheckChildRows (DataRowAction.Rollback);
1326 Table.DeleteRowFromIndexes(this);
1329 case DataRowState.Deleted:
1330 CheckChildRows (DataRowAction.Rollback);
1331 Table.DeleteRowFromIndexes(this);
1338 /// Sets the error description for a column specified as a DataColumn.
1340 public void SetColumnError (DataColumn column, string error)
1342 SetColumnError (_table.Columns.IndexOf (column), error);
1346 /// Sets the error description for a column specified by index.
1348 public void SetColumnError (int columnIndex, string error)
1350 if (columnIndex < 0 || columnIndex >= Table.Columns.Count)
1351 throw new IndexOutOfRangeException ();
1353 while(ColumnErrors.Count < columnIndex) {
1354 ColumnErrors.Add(null);
1356 ColumnErrors.Add(error);
1360 /// Sets the error description for a column specified by name.
1362 public void SetColumnError (string columnName, string error)
1364 SetColumnError (_table.Columns.IndexOf (columnName), error);
1368 /// Sets the value of the specified DataColumn to a null value.
1370 protected void SetNull (DataColumn column)
1372 this[column] = DBNull.Value;
1376 /// Sets the parent row of a DataRow with specified new parent DataRow.
1378 public void SetParentRow (DataRow parentRow)
1380 SetParentRow(parentRow, null);
1384 /// Sets the parent row of a DataRow with specified new parent DataRow and
1387 public void SetParentRow (DataRow parentRow, DataRelation relation)
1389 if (_table == null || parentRow.Table == null)
1390 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.");
1392 if (parentRow != null && _table.DataSet != parentRow.Table.DataSet)
1393 throw new ArgumentException();
1395 if (RowState == DataRowState.Detached && !HasVersion(DataRowVersion.Default)) {
1396 // 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
1397 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.");
1402 IEnumerable relations;
1403 if (relation == null) {
1404 relations = _table.ParentRelations;
1407 relations = new DataRelation[] { relation };
1410 foreach (DataRelation rel in relations)
1412 DataColumn[] childCols = rel.ChildColumns;
1413 DataColumn[] parentCols = rel.ParentColumns;
1415 for (int i = 0; i < parentCols.Length; i++)
1417 if (parentRow == null) {
1418 childCols[i].DataContainer[Proposed] = DBNull.Value;
1421 int defaultIdx = parentRow.IndexFromVersion(DataRowVersion.Default);
1422 childCols[i].DataContainer.CopyValue(parentCols[i].DataContainer,defaultIdx,Proposed);
1431 //Copy all values of this DataRow to the row parameter.
1432 internal void CopyValuesToRow(DataRow row)
1435 throw new ArgumentNullException("row");
1437 throw new ArgumentException("'row' is the same as this object");
1439 // create target records if missing.
1440 if (HasVersion(DataRowVersion.Original)) {
1441 if (row.Original < 0)
1442 row.Original = row.Table.RecordCache.NewRecord();
1443 else if (row.Original == row.Current) {
1444 row.Original = row.Table.RecordCache.NewRecord();
1445 row.Table.RecordCache.CopyRecord (row.Table, row.Current, row.Original);
1448 if (row.Original > 0) {
1449 if (row.Original != row.Current)
1450 row.Table.RecordCache.DisposeRecord(row.Original);
1455 if (HasVersion(DataRowVersion.Current)) {
1456 if (Current == Original) {
1457 if (row.Current >= 0)
1458 row.Table.RecordCache.DisposeRecord(row.Current);
1459 row.Current = row.Original;
1461 if (row.Current < 0)
1462 row.Current = row.Table.RecordCache.NewRecord();
1465 if (row.Current > 0) {
1466 row.Table.RecordCache.DisposeRecord(row.Current);
1471 if (HasVersion(DataRowVersion.Proposed)) {
1472 if (row.Proposed < 0)
1473 row.Proposed = row.Table.RecordCache.NewRecord();
1475 if (row.Proposed > 0) {
1476 row.Table.RecordCache.DisposeRecord(row.Proposed);
1481 // copy source record values to target records
1482 foreach(DataColumn column in Table.Columns) {
1483 DataColumn targetColumn = row.Table.Columns[column.ColumnName];
1484 //if a column with the same name exists in both rows copy the values
1485 if(targetColumn != null) {
1486 if (HasVersion(DataRowVersion.Original)) {
1487 object val = column[Original];
1488 row.CheckValue(val, targetColumn);
1489 targetColumn[row.Original] = val;
1492 if (HasVersion(DataRowVersion.Current)
1493 && Current != Original) {
1494 object val = column[Current];
1495 row.CheckValue(val, targetColumn);
1496 targetColumn[row.Current] = val;
1499 if (HasVersion(DataRowVersion.Proposed)) {
1500 object val = column[row.Proposed];
1501 row.CheckValue(val, targetColumn);
1502 targetColumn[row.Proposed] = val;
1511 //Merge all values of this DataRow to the row parameter according to merge rules.
1512 internal void MergeValuesToRow(DataRow row, bool preserveChanges)
1515 throw new ArgumentNullException("row");
1517 throw new ArgumentException("'row' is the same as this object");
1519 // Original values are anyway copied
1520 if (HasVersion(DataRowVersion.Original)) {
1521 if (row.Original < 0)
1522 row.Original = row.Table.RecordCache.NewRecord();
1523 else if (row.Original == row.Current
1524 && !(Original == Current && ! preserveChanges)) {
1525 row.Original = row.Table.RecordCache.NewRecord();
1526 row.Table.RecordCache.CopyRecord (row.Table, row.Current, row.Original);
1529 if (row.Original == row.Current) { // if target has same current, better create new original
1530 row.Original = row.Table.RecordCache.NewRecord();
1531 row.Table.RecordCache.CopyRecord (row.Table, row.Current, row.Original);
1535 // if i have current, push all
1536 if (HasVersion(DataRowVersion.Current)) {
1537 if (! preserveChanges && row.Current < 0)
1538 row.Current = row.Table.RecordCache.NewRecord();
1540 if (row.Current > 0 && ! preserveChanges) {
1541 row.Table.RecordCache.DisposeRecord(row.Current);
1546 // copy source record values to target records
1547 foreach(DataColumn column in Table.Columns) {
1548 DataColumn targetColumn = row.Table.Columns[column.ColumnName];
1549 //if a column with the same name exists in both rows copy the values
1550 if(targetColumn != null) {
1551 if (HasVersion(DataRowVersion.Original)) {
1552 object val = column[Original];
1553 row.CheckValue(val, targetColumn);
1554 targetColumn[row.Original] = val;
1557 if (HasVersion(DataRowVersion.Current)
1558 && !preserveChanges) {
1559 object val = column[Current];
1560 row.CheckValue(val, targetColumn);
1561 targetColumn[row.Current] = val;
1570 internal void CopyErrors(DataRow row)
1572 row.RowError = RowError;
1573 DataColumn[] errorColumns = GetColumnsInError();
1574 foreach(DataColumn col in errorColumns) {
1575 DataColumn targetColumn = row.Table.Columns[col.ColumnName];
1576 row.SetColumnError(targetColumn,GetColumnError(col));
1580 internal bool IsRowChanged(DataRowState rowState) {
1581 if((RowState & rowState) != 0)
1584 //we need to find if child rows of this row changed.
1585 //if yes - we should return true
1587 // if the rowState is deleted we should get the original version of the row
1588 // else - we should get the current version of the row.
1589 DataRowVersion version = (rowState == DataRowState.Deleted) ? DataRowVersion.Original : DataRowVersion.Current;
1590 int count = Table.ChildRelations.Count;
1591 for (int i = 0; i < count; i++){
1592 DataRelation rel = Table.ChildRelations[i];
1593 DataRow[] childRows = GetChildRows(rel, version);
1594 for (int j = 0; j < childRows.Length; j++){
1595 if (childRows[j].IsRowChanged(rowState))
1603 internal bool HasParentCollection
1607 return _hasParentCollection;
1611 _hasParentCollection = value;
1615 internal void Validate() {
1616 Table.AddRowToIndexes(this);
1617 AssertConstraints();
1620 void AssertConstraints() {
1621 if (Table == null || Table._duringDataLoad)
1624 if (Table.DataSet != null && !Table.DataSet.EnforceConstraints)
1626 foreach(DataColumn column in Table.Columns) {
1627 if (!column.AllowDBNull && IsNull(column)) {
1628 throw new NoNullAllowedException(_nullConstraintMessage);
1632 foreach(Constraint constraint in Table.Constraints) {
1634 constraint.AssertConstraint(this);
1636 catch(Exception e) {
1637 Table.DeleteRowFromIndexes(this);
1643 internal void CheckNullConstraints()
1645 if (_nullConstraintViolation) {
1646 if (HasVersion(DataRowVersion.Proposed)) {
1647 foreach(DataColumn column in Table.Columns) {
1648 if (IsNull(column) && !column.AllowDBNull) {
1649 throw new NoNullAllowedException(_nullConstraintMessage);
1653 _nullConstraintViolation = false;
1657 internal void CheckReadOnlyStatus() {
1658 int defaultIdx = IndexFromVersion(DataRowVersion.Default);
1659 foreach(DataColumn column in Table.Columns) {
1660 if ((column.DataContainer.CompareValues(defaultIdx,Proposed) != 0) && column.ReadOnly) {
1661 throw new ReadOnlyException();
1666 #endregion // Methods
1670 /// This method loads a given value into the existing row affecting versions,
1671 /// state based on the LoadOption. The matrix of changes for this method are as
1672 /// mentioned in the DataTable.Load (IDataReader, LoadOption) method.
1674 internal void Load (object [] values, LoadOption loadOption)
1679 if (loadOption == LoadOption.OverwriteChanges
1680 || (loadOption == LoadOption.PreserveChanges
1681 && RowState == DataRowState.Unchanged)) {
1682 Table.ChangingDataRow (this, DataRowAction.ChangeCurrentAndOriginal);
1683 temp = Table.CreateRecord (values);
1684 Table.DeleteRowFromIndexes(this);
1685 if (HasVersion (DataRowVersion.Original) && Current != Original)
1686 Table.RecordCache.DisposeRecord (Original);
1689 if (HasVersion (DataRowVersion.Current))
1690 Table.RecordCache.DisposeRecord (Current);
1692 Table.AddRowToIndexes(this);
1693 Table.ChangedDataRow (this, DataRowAction.ChangeCurrentAndOriginal);
1697 if (loadOption == LoadOption.PreserveChanges) {
1698 Table.ChangingDataRow (this, DataRowAction.ChangeOriginal);
1699 temp = Table.CreateRecord (values);
1700 if (HasVersion (DataRowVersion.Original) && Current != Original)
1701 Table.RecordCache.DisposeRecord (Original);
1703 Table.ChangedDataRow (this, DataRowAction.ChangeOriginal);
1708 if (RowState != DataRowState.Deleted) {
1709 int rindex = HasVersion (DataRowVersion.Proposed) ? Proposed : Current;
1710 temp = Table.CreateRecord (values);
1711 if (RowState == DataRowState.Added
1712 || Table.CompareRecords (rindex, temp) != 0) {
1713 Table.ChangingDataRow (this, DataRowAction.Change);
1714 Table.DeleteRowFromIndexes(this);
1715 if (HasVersion (DataRowVersion.Proposed)) {
1716 Table.RecordCache.DisposeRecord (Proposed);
1720 if (Original != Current)
1721 Table.RecordCache.DisposeRecord (Current);
1723 Table.AddRowToIndexes(this);
1724 Table.ChangedDataRow (this, DataRowAction.Change);
1726 Table.ChangingDataRow (this, DataRowAction.Nothing);
1727 Table.RecordCache.DisposeRecord (temp);
1728 Table.ChangedDataRow (this, DataRowAction.Nothing);