2 // System.Data.DataRow.cs
5 // Rodrigo Moya <rodrigo@ximian.com>
6 // Daniel Morgan <danmorg@sc.rr.com>
7 // Tim Coleman <tim@timcoleman.com>
8 // Ville Palo <vi64pa@koti.soon.fi>
9 // Alan Tam Siu Lung <Tam@SiuLung.com>
10 // Sureshkumar T <tsureshkumar@novell.com>
12 // (C) Ximian, Inc 2002
13 // (C) Daniel Morgan 2002, 2003
14 // Copyright (C) 2002 Tim Coleman
18 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
20 // Permission is hereby granted, free of charge, to any person obtaining
21 // a copy of this software and associated documentation files (the
22 // "Software"), to deal in the Software without restriction, including
23 // without limitation the rights to use, copy, modify, merge, publish,
24 // distribute, sublicense, and/or sell copies of the Software, and to
25 // permit persons to whom the Software is furnished to do so, subject to
26 // the following conditions:
28 // The above copyright notice and this permission notice shall be
29 // included in all copies or substantial portions of the Software.
31 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
32 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
33 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
34 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
35 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
36 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
37 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
41 using System.Data.Common;
42 using System.Collections;
43 using System.Globalization;
46 using System.Data.Common;
48 namespace System.Data {
50 /// 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);
186 bool orginalEditing = Proposed >= 0;
187 if (!orginalEditing) {
191 column[Proposed] = value;
192 _table.ChangedDataColumn (this, column, value);
193 if (!orginalEditing) {
200 /// Gets the specified version of data stored in the named column.
202 public object this[string columnName, DataRowVersion version] {
204 int columnIndex = _table.Columns.IndexOf (columnName);
205 if (columnIndex == -1)
206 throw new IndexOutOfRangeException ();
207 return this[columnIndex, version];
212 /// Gets the specified version of data stored in the specified DataColumn.
214 public object this[DataColumn column, DataRowVersion version] {
216 if (column.Table != Table)
217 throw new ArgumentException ("The column does not belong to this table.");
218 int columnIndex = column.Ordinal;
219 return this[columnIndex, version];
224 /// Set a value for the column into the offset specified by the version.<br>
225 /// If the value is auto increment or null, necessary auto increment value
226 /// or the default value will be used.
228 internal void SetValue (int column, object value, int version)
230 DataColumn dc = Table.Columns[column];
232 if (value == null && ! dc.AutoIncrement) // set default value / auto increment
233 value = dc.DefaultValue;
235 Table.ChangingDataColumn (this, dc, value);
236 CheckValue (value, dc);
237 if ( ! dc.AutoIncrement)
238 dc [version] = value;
239 else if (_proposed >= 0 && _proposed != version) // proposed holds the AI
240 dc [version] = dc [_proposed];
244 /// Gets the data stored in the column, specified by index and version of the data to
247 public object this[int columnIndex, DataRowVersion version] {
249 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
250 throw new IndexOutOfRangeException ();
251 // Accessing deleted rows
252 if (!_inExpressionEvaluation && RowState == DataRowState.Deleted && version != DataRowVersion.Original)
253 throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
255 DataColumn column = _table.Columns[columnIndex];
256 int recordIndex = IndexFromVersion(version);
258 if (column.Expression != String.Empty) {
259 object o = column.CompiledExpression.Eval (this);
260 if (o != null && o != DBNull.Value) {
261 o = Convert.ChangeType (o, column.DataType);
263 column[recordIndex] = o;
264 return column[recordIndex];
267 if (RowState == DataRowState.Detached && version == DataRowVersion.Default && Proposed < 0)
268 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.");
270 return column[recordIndex];
275 /// Gets or sets all of the values for this row through an array.
277 public object[] ItemArray {
280 if (RowState == DataRowState.Detached)
281 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.");
282 // Accessing deleted rows
283 if (RowState == DataRowState.Deleted)
284 throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
286 object[] items = new object[_table.Columns.Count];
287 foreach(DataColumn column in _table.Columns) {
288 items[column.Ordinal] = column[Current];
293 if (value.Length > _table.Columns.Count)
294 throw new ArgumentException ();
296 if (RowState == DataRowState.Deleted)
297 throw new DeletedRowInaccessibleException ();
301 DataColumnChangeEventArgs e = new DataColumnChangeEventArgs();
302 foreach(DataColumn column in _table.Columns) {
303 int i = column.Ordinal;
304 object newVal = (i < value.Length) ? value[i] : null;
309 e.Initialize(this, column, newVal);
310 CheckValue (e.ProposedValue, column);
311 _table.RaiseOnColumnChanging(e);
312 column[Proposed] = e.ProposedValue;
313 _table.RaiseOnColumnChanged(e);
321 /// Gets the current state of the row in regards to its relationship to the
322 /// DataRowCollection.
324 public DataRowState RowState {
327 if ((Original == -1) && (Current == -1))
329 return DataRowState.Detached;
331 if (Original == Current)
333 return DataRowState.Unchanged;
337 return DataRowState.Added;
341 return DataRowState.Deleted;
343 return DataRowState.Modified;
348 /// Gets the DataTable for which this row has a schema.
350 public DataTable Table {
357 /// Gets and sets index of row. This is used from
360 internal int XmlRowID {
370 /// Gets and sets index of row.
381 internal int Original
388 //Table.RecordCache[_original] = null;
389 Table.RecordCache[value] = this;
402 //Table.RecordCache[_current] = null;
403 Table.RecordCache[value] = this;
409 internal int Proposed
416 //Table.RecordCache[_proposed] = null;
417 Table.RecordCache[value] = this;
427 //FIXME?: Couldn't find a way to set the RowState when adding the DataRow
428 //to a Datatable so I added this method. Delete if there is a better way.
429 internal void AttachRow() {
430 if (Proposed != -1) {
432 Table.RecordCache.DisposeRecord(Current);
439 //FIXME?: Couldn't find a way to set the RowState when removing the DataRow
440 //from a Datatable so I added this method. Delete if there is a better way.
441 internal void DetachRow() {
443 _table.RecordCache.DisposeRecord(Proposed);
444 if (Proposed == Current) {
447 if (Proposed == Original) {
454 _table.RecordCache.DisposeRecord(Current);
455 if (Current == Original) {
462 _table.RecordCache.DisposeRecord(Original);
467 _hasParentCollection = false;
470 internal void ImportRecord(int record)
472 if (HasVersion(DataRowVersion.Proposed)) {
473 Table.RecordCache.DisposeRecord(Proposed);
478 foreach(DataColumn column in Table.Columns.AutoIncrmentColumns) {
479 column.UpdateAutoIncrementValue(column.DataContainer.GetInt64(Proposed));
484 private void CheckValue (object v, DataColumn col)
486 if (_hasParentCollection && col.ReadOnly) {
487 throw new ReadOnlyException ();
490 if (v == null || v == DBNull.Value) {
491 if (col.AllowDBNull || col.AutoIncrement || col.DefaultValue != DBNull.Value) {
495 //Constraint violations during data load is raise in DataTable EndLoad
496 this._nullConstraintViolation = true;
497 if (this.Table._duringDataLoad) {
498 this.Table._nullConstraintViolationDuringDataLoad = true;
500 _nullConstraintMessage = "Column '" + col.ColumnName + "' does not allow nulls.";
506 /// Gets or sets the custom error description for a row.
508 public string RowError {
517 internal int IndexFromVersion(DataRowVersion version)
520 case DataRowVersion.Default:
528 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.");
530 throw new DeletedRowInaccessibleException("Deleted row information cannot be accessed through the row.");
532 case DataRowVersion.Proposed:
533 return AssertValidVersionIndex(version, Proposed);
534 case DataRowVersion.Current:
535 return AssertValidVersionIndex(version, Current);
536 case DataRowVersion.Original:
537 return AssertValidVersionIndex(version, Original);
539 throw new DataException ("Version must be Original, Current, or Proposed.");
543 private int AssertValidVersionIndex(DataRowVersion version, int index) {
547 throw new VersionNotFoundException(String.Format("There is no {0} data to accces.", version));
550 internal DataRowVersion VersionFromIndex(int index) {
552 throw new ArgumentException("Index must not be negative.");
554 // the order of ifs matters
555 if (index == Current)
556 return DataRowVersion.Current;
557 if (index == Original)
558 return DataRowVersion.Original;
559 if (index == Proposed)
560 return DataRowVersion.Proposed;
562 throw new ArgumentException(String.Format("The index {0} does not belong to this row.", index) );
565 internal XmlDataDocument.XmlDataElement DataElement {
566 get { return mappedElement; }
567 set { mappedElement = value; }
570 internal void SetOriginalValue (string columnName, object val)
572 DataColumn column = _table.Columns[columnName];
573 _table.ChangingDataColumn (this, column, val);
575 if (Original < 0 || Original == Current) {
576 Original = Table.RecordCache.NewRecord();
578 CheckValue (val, column);
579 column[Original] = val;
583 /// Commits all the changes made to this row since the last time AcceptChanges was
586 public void AcceptChanges ()
588 EndEdit(); // in case it hasn't been called
590 _table.ChangingDataRow (this, DataRowAction.Commit);
591 CheckChildRows(DataRowAction.Commit);
593 case DataRowState.Unchanged:
595 case DataRowState.Added:
596 case DataRowState.Modified:
598 Table.RecordCache.DisposeRecord(Original);
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.");
610 _table.ChangedDataRow (this, DataRowAction.Commit);
614 /// Begins an edit operation on a DataRow object.
616 public void BeginEdit ()
618 if (_inChangingEvent)
619 throw new InRowChangingEventException("Cannot call BeginEdit inside an OnRowChanging event.");
620 if (RowState == DataRowState.Deleted)
621 throw new DeletedRowInaccessibleException ();
623 if (!HasVersion (DataRowVersion.Proposed)) {
624 Proposed = Table.RecordCache.NewRecord();
625 int from = HasVersion(DataRowVersion.Current) ? Current : Table.DefaultValuesRowIndex;
626 for(int i = 0; i < Table.Columns.Count; i++){
627 DataColumn column = Table.Columns[i];
628 column.DataContainer.CopyValue(from,Proposed);
634 /// Cancels the current edit on the row.
636 public void CancelEdit ()
638 if (_inChangingEvent) {
639 throw new InRowChangingEventException("Cannot call CancelEdit inside an OnRowChanging event.");
642 if (HasVersion (DataRowVersion.Proposed)) {
643 Table.RecordCache.DisposeRecord(Proposed);
649 /// Clears the errors for the row, including the RowError and errors set with
652 public void ClearErrors ()
654 rowError = String.Empty;
655 ColumnErrors.Clear();
659 /// Deletes the DataRow.
661 public void Delete ()
663 _table.DeletingDataRow(this, DataRowAction.Delete);
665 case DataRowState.Added:
666 // check what to do with child rows
667 CheckChildRows(DataRowAction.Delete);
668 _table.DeleteRowFromIndexes (this);
669 Table.Rows.RemoveInternal (this);
671 // if row was in Added state we move it to Detached.
674 case DataRowState.Deleted:
677 // check what to do with child rows
678 CheckChildRows(DataRowAction.Delete);
679 _table.DeleteRowFromIndexes (this);
683 if (Current != Original) {
684 _table.RecordCache.DisposeRecord(Current);
688 _table.DeletedDataRow(this, DataRowAction.Delete);
691 // check the child rows of this row before deleting the row.
692 private void CheckChildRows(DataRowAction action)
695 // in this method we find the row that this row is in a relation with them.
696 // in shortly we find all child rows of this row.
697 // then we function according to the DeleteRule of the foriegnkey.
700 // 1. find if this row is attached to dataset.
701 // 2. find if EnforceConstraints is true.
702 // 3. find if there are any constraint on the table that the row is in.
703 if (_table.DataSet != null && _table.DataSet.EnforceConstraints && _table.Constraints.Count > 0)
705 foreach (DataTable table in _table.DataSet.Tables)
707 // loop on all ForeignKeyConstrain of the table.
708 foreach (Constraint constraint in table.Constraints) {
709 if (constraint is ForeignKeyConstraint) {
710 ForeignKeyConstraint fk = (ForeignKeyConstraint) constraint;
711 if (fk.RelatedTable == _table) {
713 case DataRowAction.Delete:
714 CheckChildRows(fk, action, fk.DeleteRule);
716 case DataRowAction.Commit:
717 case DataRowAction.Rollback:
718 if (fk.AcceptRejectRule != AcceptRejectRule.None)
719 CheckChildRows(fk, action, Rule.Cascade);
722 CheckChildRows(fk, action, fk.UpdateRule);
732 private void CheckChildRows(ForeignKeyConstraint fkc, DataRowAction action, Rule rule)
734 DataRow[] childRows = GetChildRows(fkc, DataRowVersion.Current);
737 case Rule.Cascade: // delete or change all relted rows.
738 if (childRows != null)
740 for (int j = 0; j < childRows.Length; j++)
742 // if action is delete we delete all child rows
744 case DataRowAction.Delete: {
745 if (childRows[j].RowState != DataRowState.Deleted)
746 childRows[j].Delete();
750 // if action is change we change the values in the child row
751 case DataRowAction.Change: {
752 // change only the values in the key columns
753 // set the childcolumn value to the new parent row value
754 for (int k = 0; k < fkc.Columns.Length; k++)
755 childRows[j][fkc.Columns[k]] = this[fkc.RelatedColumns[k], DataRowVersion.Proposed];
760 case DataRowAction.Rollback: {
761 if (childRows[j].RowState != DataRowState.Unchanged)
762 childRows[j].RejectChanges ();
770 case Rule.None: // throw an exception if there are any child rows.
771 if (childRows != null)
773 for (int j = 0; j < childRows.Length; j++)
775 if (childRows[j].RowState != DataRowState.Deleted)
777 string changeStr = "Cannot change this row because constraints are enforced on relation " + fkc.ConstraintName +", and changing this row will strand child rows.";
778 string delStr = "Cannot delete this row because constraints are enforced on relation " + fkc.ConstraintName +", and deleting this row will strand child rows.";
779 string message = action == DataRowAction.Delete ? delStr : changeStr;
780 throw new InvalidConstraintException(message);
785 case Rule.SetDefault: // set the values in the child rows to the defult value of the columns.
786 if (childRows != null && childRows.Length > 0) {
787 int defaultValuesRowIndex = childRows[0].Table.DefaultValuesRowIndex;
788 foreach(DataRow childRow in childRows) {
789 if (childRow.RowState != DataRowState.Deleted) {
790 int defaultIdx = childRow.IndexFromVersion(DataRowVersion.Default);
791 foreach(DataColumn column in fkc.Columns) {
792 column.DataContainer.CopyValue(defaultValuesRowIndex,defaultIdx);
798 case Rule.SetNull: // set the values in the child row to null.
799 if (childRows != null)
801 for (int j = 0; j < childRows.Length; j++)
803 DataRow child = childRows[j];
804 if (childRows[j].RowState != DataRowState.Deleted)
806 // set only the key columns to DBNull
807 for (int k = 0; k < fkc.Columns.Length; k++)
808 child.SetNull(fkc.Columns[k]);
818 /// Ends the edit occurring on the row.
820 public void EndEdit ()
822 if (_inChangingEvent)
823 throw new InRowChangingEventException("Cannot call EndEdit inside an OnRowChanging event.");
825 if (RowState == DataRowState.Detached)
828 if (HasVersion (DataRowVersion.Proposed))
830 CheckReadOnlyStatus();
832 _inChangingEvent = true;
835 _table.ChangingDataRow(this, DataRowAction.Change);
839 _inChangingEvent = false;
842 //Calling next method validates UniqueConstraints
844 bool rowValidated = false;
847 if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad) {
848 _table.Rows.ValidateDataRowInternal(this);
854 Table.RecordCache.DisposeRecord(Proposed);
859 CheckChildRows(DataRowAction.Change);
860 if (Original != Current) {
861 Table.RecordCache.DisposeRecord(Current);
868 // keep indexes updated even if there was no need to validate row
869 foreach(Index index in Table.Indexes) {
870 index.Update(this,Current); //FIXME: why Current ?!
874 // Note : row state must not be changed before all the job on indexes finished,
875 // since the indexes works with recods rather than with rows and the decision
876 // which of row records to choose depends on row state.
877 _table.ChangedDataRow(this, DataRowAction.Change);
882 /// Gets the child rows of this DataRow using the specified DataRelation.
884 public DataRow[] GetChildRows (DataRelation relation)
886 return GetChildRows (relation, DataRowVersion.Default);
890 /// Gets the child rows of a DataRow using the specified RelationName of a
893 public DataRow[] GetChildRows (string relationName)
895 return GetChildRows (Table.DataSet.Relations[relationName]);
899 /// Gets the child rows of a DataRow using the specified DataRelation, and
902 public DataRow[] GetChildRows (DataRelation relation, DataRowVersion version)
904 if (relation == null)
905 return Table.NewRowArray(0);
907 if (this.Table == null)
908 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.");
910 if (relation.DataSet != this.Table.DataSet)
911 throw new ArgumentException();
913 if (_table != relation.ParentTable)
914 throw new InvalidConstraintException ("GetChildRow requires a row whose Table is " + relation.ParentTable + ", but the specified row's table is " + _table);
916 if (relation.ChildKeyConstraint != null)
917 return GetChildRows (relation.ChildKeyConstraint, version);
919 ArrayList rows = new ArrayList();
920 DataColumn[] parentColumns = relation.ParentColumns;
921 DataColumn[] childColumns = relation.ChildColumns;
922 int numColumn = parentColumns.Length;
923 DataRow[] result = null;
925 int versionIndex = IndexFromVersion(version);
926 int tmpRecord = relation.ChildTable.RecordCache.NewRecord();
929 for (int i = 0; i < numColumn; i++) {
930 // according to MSDN: the DataType value for both columns must be identical.
931 childColumns[i].DataContainer.CopyValue(parentColumns[i].DataContainer, versionIndex, tmpRecord);
934 Index index = relation.ChildTable.FindIndex(childColumns);
937 int[] records = index.FindAll(tmpRecord);
938 result = relation.ChildTable.NewRowArray(records.Length);
939 for(int i=0; i < records.Length; i++) {
940 result[i] = relation.ChildTable.RecordCache[records[i]];
944 foreach (DataRow row in relation.ChildTable.Rows) {
945 bool allColumnsMatch = false;
946 if (row.HasVersion(DataRowVersion.Default)) {
947 allColumnsMatch = true;
948 int childIndex = row.IndexFromVersion(DataRowVersion.Default);
949 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) {
950 if (childColumns[columnCnt].DataContainer.CompareValues(childIndex, tmpRecord) != 0) {
951 allColumnsMatch = false;
956 if (allColumnsMatch) rows.Add(row);
958 result = relation.ChildTable.NewRowArray(rows.Count);
959 rows.CopyTo(result, 0);
963 relation.ChildTable.RecordCache.DisposeRecord(tmpRecord);
970 /// Gets the child rows of a DataRow using the specified RelationName of a
971 /// DataRelation, and DataRowVersion.
973 public DataRow[] GetChildRows (string relationName, DataRowVersion version)
975 return GetChildRows (Table.DataSet.Relations[relationName], version);
978 private DataRow[] GetChildRows (ForeignKeyConstraint fkc, DataRowVersion version)
980 ArrayList rows = new ArrayList();
981 DataColumn[] parentColumns = fkc.RelatedColumns;
982 DataColumn[] childColumns = fkc.Columns;
983 int numColumn = parentColumns.Length;
985 Index index = fkc.Index;
987 int curIndex = IndexFromVersion(version);
988 int tmpRecord = fkc.Table.RecordCache.NewRecord();
989 for (int i = 0; i < numColumn; i++) {
990 // according to MSDN: the DataType value for both columns must be identical.
991 childColumns[i].DataContainer.CopyValue(parentColumns[i].DataContainer, curIndex, tmpRecord);
996 // get the child rows from the index
997 int[] childRecords = index.FindAll(tmpRecord);
998 for (int i = 0; i < childRecords.Length; i++) {
999 rows.Add (childColumns[i].Table.RecordCache[childRecords[i]]);
1002 else { // if there is no index we search manualy.
1003 foreach (DataRow row in fkc.Table.Rows) {
1004 bool allColumnsMatch = false;
1005 if (row.HasVersion(DataRowVersion.Default)) {
1006 allColumnsMatch = true;
1007 int childIndex = row.IndexFromVersion(DataRowVersion.Default);
1008 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) {
1009 if (childColumns[columnCnt].DataContainer.CompareValues(childIndex, tmpRecord) != 0) {
1010 allColumnsMatch = false;
1015 if (allColumnsMatch) {
1022 fkc.Table.RecordCache.DisposeRecord(tmpRecord);
1025 DataRow[] result = fkc.Table.NewRowArray(rows.Count);
1026 rows.CopyTo(result, 0);
1031 /// Gets the error description of the specified DataColumn.
1033 public string GetColumnError (DataColumn column)
1036 throw new ArgumentNullException("column");
1038 int index = _table.Columns.IndexOf(column);
1040 throw new ArgumentException(String.Format("Column '{0}' does not belong to table {1}.", column.ColumnName, Table.TableName));
1042 return GetColumnError (index);
1046 /// Gets the error description for the column specified by index.
1048 public string GetColumnError (int columnIndex)
1050 if (columnIndex < 0 || columnIndex >= Table.Columns.Count)
1051 throw new IndexOutOfRangeException ();
1053 string retVal = null;
1054 if (columnIndex < ColumnErrors.Count) {
1055 retVal = (String) ColumnErrors[columnIndex];
1057 return (retVal != null) ? retVal : String.Empty;
1061 /// Gets the error description for the column, specified by name.
1063 public string GetColumnError (string columnName)
1065 return GetColumnError (_table.Columns.IndexOf(columnName));
1069 /// Gets an array of columns that have errors.
1071 public DataColumn[] GetColumnsInError ()
1073 ArrayList dataColumns = new ArrayList ();
1075 int columnOrdinal = 0;
1076 foreach(String columnError in ColumnErrors) {
1077 if (columnError != null && columnError != String.Empty) {
1078 dataColumns.Add (_table.Columns[columnOrdinal]);
1083 return (DataColumn[])(dataColumns.ToArray (typeof(DataColumn)));
1087 /// Gets the parent row of a DataRow using the specified DataRelation.
1089 public DataRow GetParentRow (DataRelation relation)
1091 return GetParentRow (relation, DataRowVersion.Default);
1095 /// Gets the parent row of a DataRow using the specified RelationName of a
1098 public DataRow GetParentRow (string relationName)
1100 return GetParentRow (relationName, DataRowVersion.Default);
1104 /// Gets the parent row of a DataRow using the specified DataRelation, and
1107 public DataRow GetParentRow (DataRelation relation, DataRowVersion version)
1109 DataRow[] rows = GetParentRows(relation, version);
1110 if (rows.Length == 0) return null;
1115 /// Gets the parent row of a DataRow using the specified RelationName of a
1116 /// DataRelation, and DataRowVersion.
1118 public DataRow GetParentRow (string relationName, DataRowVersion version)
1120 return GetParentRow (Table.DataSet.Relations[relationName], version);
1124 /// Gets the parent rows of a DataRow using the specified DataRelation.
1126 public DataRow[] GetParentRows (DataRelation relation)
1128 return GetParentRows (relation, DataRowVersion.Default);
1132 /// Gets the parent rows of a DataRow using the specified RelationName of a
1135 public DataRow[] GetParentRows (string relationName)
1137 return GetParentRows (relationName, DataRowVersion.Default);
1141 /// Gets the parent rows of a DataRow using the specified DataRelation, and
1144 public DataRow[] GetParentRows (DataRelation relation, DataRowVersion version)
1146 // TODO: Caching for better preformance
1147 if (relation == null)
1148 return Table.NewRowArray(0);
1150 if (this.Table == null)
1151 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.");
1153 if (relation.DataSet != this.Table.DataSet)
1154 throw new ArgumentException();
1156 if (_table != relation.ChildTable)
1157 throw new InvalidConstraintException ("GetParentRows requires a row whose Table is " + relation.ChildTable + ", but the specified row's table is " + _table);
1159 ArrayList rows = new ArrayList();
1160 DataColumn[] parentColumns = relation.ParentColumns;
1161 DataColumn[] childColumns = relation.ChildColumns;
1162 int numColumn = parentColumns.Length;
1164 int curIndex = IndexFromVersion(version);
1165 int tmpRecord = relation.ParentTable.RecordCache.NewRecord();
1166 for (int i = 0; i < numColumn; i++) {
1167 // according to MSDN: the DataType value for both columns must be identical.
1168 parentColumns[i].DataContainer.CopyValue(childColumns[i].DataContainer, curIndex, tmpRecord);
1172 Index index = relation.ParentTable.FindIndex(parentColumns);
1173 if (index != null) { // get the parent rows from the index
1174 int[] parentRecords = index.FindAll(tmpRecord);
1175 for (int i = 0; i < parentRecords.Length; i++) {
1176 rows.Add (parentColumns[i].Table.RecordCache[parentRecords[i]]);
1179 else { // no index so we have to search manualy.
1180 foreach (DataRow row in relation.ParentTable.Rows) {
1181 bool allColumnsMatch = false;
1182 if (row.HasVersion(DataRowVersion.Default)) {
1183 allColumnsMatch = true;
1184 int parentIndex = row.IndexFromVersion(DataRowVersion.Default);
1185 for (int columnCnt = 0; columnCnt < numColumn; columnCnt++) {
1186 if (parentColumns[columnCnt].DataContainer.CompareValues(parentIndex, tmpRecord) != 0) {
1187 allColumnsMatch = false;
1192 if (allColumnsMatch) {
1199 relation.ParentTable.RecordCache.DisposeRecord(tmpRecord);
1202 DataRow[] result = relation.ParentTable.NewRowArray(rows.Count);
1203 rows.CopyTo(result, 0);
1208 /// Gets the parent rows of a DataRow using the specified RelationName of a
1209 /// DataRelation, and DataRowVersion.
1211 public DataRow[] GetParentRows (string relationName, DataRowVersion version)
1213 return GetParentRows (Table.DataSet.Relations[relationName], version);
1217 /// Gets a value indicating whether a specified version exists.
1219 public bool HasVersion (DataRowVersion version)
1222 case DataRowVersion.Default:
1223 return (Proposed >= 0 || Current >= 0);
1224 case DataRowVersion.Proposed:
1225 return Proposed >= 0;
1226 case DataRowVersion.Current:
1227 return Current >= 0;
1228 case DataRowVersion.Original:
1229 return Original >= 0;
1231 return IndexFromVersion(version) >= 0;
1236 /// Gets a value indicating whether the specified DataColumn contains a null value.
1238 public bool IsNull (DataColumn column)
1240 return IsNull(column, DataRowVersion.Default);
1244 /// Gets a value indicating whether the column at the specified index contains a null
1247 public bool IsNull (int columnIndex)
1249 return IsNull(Table.Columns[columnIndex]);
1253 /// Gets a value indicating whether the named column contains a null value.
1255 public bool IsNull (string columnName)
1257 return IsNull(Table.Columns[columnName]);
1261 /// Gets a value indicating whether the specified DataColumn and DataRowVersion
1262 /// contains a null value.
1264 public bool IsNull (DataColumn column, DataRowVersion version)
1266 object o = this[column,version];
1267 return column.DataContainer.IsNull(IndexFromVersion(version));
1271 /// Returns a value indicating whether all of the row columns specified contain a null value.
1273 internal bool IsNullColumns(DataColumn[] columns)
1275 bool allNull = true;
1276 for (int i = 0; i < columns.Length; i++)
1278 if (!IsNull(columns[i]))
1288 /// Rejects all changes made to the row since AcceptChanges was last called.
1290 public void RejectChanges ()
1292 if (RowState == DataRowState.Detached)
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.");
1294 // If original is null, then nothing has happened since AcceptChanges
1295 // was last called. We have no "original" to go back to.
1296 if (HasVersion(DataRowVersion.Original)) {
1297 if (Current >= 0 && Current != Original) {
1298 Table.RecordCache.DisposeRecord(Current);
1300 CheckChildRows(DataRowAction.Rollback);
1304 _table.ChangedDataRow (this, DataRowAction.Rollback);
1308 case DataRowState.Added:
1309 _table.DeleteRowFromIndexes (this);
1310 _table.Rows.RemoveInternal (this);
1312 case DataRowState.Modified:
1313 if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
1314 _table.Rows.ValidateDataRowInternal(this);
1316 case DataRowState.Deleted:
1317 if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
1318 _table.Rows.ValidateDataRowInternal(this);
1324 // If rows are just loaded via Xml the original values are null.
1325 // So in this case we have to remove all columns.
1326 // FIXME: I'm not realy sure, does this break something else, but
1329 if ((RowState & DataRowState.Added) > 0)
1331 _table.DeleteRowFromIndexes (this);
1332 _table.Rows.RemoveInternal (this);
1333 // if row was in Added state we move it to Detached.
1340 /// Sets the error description for a column specified as a DataColumn.
1342 public void SetColumnError (DataColumn column, string error)
1344 SetColumnError (_table.Columns.IndexOf (column), error);
1348 /// Sets the error description for a column specified by index.
1350 public void SetColumnError (int columnIndex, string error)
1352 if (columnIndex < 0 || columnIndex >= Table.Columns.Count)
1353 throw new IndexOutOfRangeException ();
1355 while(ColumnErrors.Count < columnIndex) {
1356 ColumnErrors.Add(null);
1358 ColumnErrors.Add(error);
1362 /// Sets the error description for a column specified by name.
1364 public void SetColumnError (string columnName, string error)
1366 SetColumnError (_table.Columns.IndexOf (columnName), error);
1370 /// Sets the value of the specified DataColumn to a null value.
1372 protected void SetNull (DataColumn column)
1374 this[column] = DBNull.Value;
1378 /// Sets the parent row of a DataRow with specified new parent DataRow.
1380 public void SetParentRow (DataRow parentRow)
1382 SetParentRow(parentRow, null);
1386 /// Sets the parent row of a DataRow with specified new parent DataRow and
1389 public void SetParentRow (DataRow parentRow, DataRelation relation)
1391 if (_table == null || parentRow.Table == null)
1392 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.");
1394 if (parentRow != null && _table.DataSet != parentRow.Table.DataSet)
1395 throw new ArgumentException();
1399 IEnumerable relations;
1400 if (relation == null) {
1401 relations = _table.ParentRelations;
1404 relations = new DataRelation[] { relation };
1407 foreach (DataRelation rel in relations)
1409 DataColumn[] childCols = rel.ChildColumns;
1410 DataColumn[] parentCols = rel.ParentColumns;
1412 for (int i = 0; i < parentCols.Length; i++)
1414 if (parentRow == null) {
1415 childCols[i].DataContainer[Proposed] = DBNull.Value;
1418 int defaultIdx = parentRow.IndexFromVersion(DataRowVersion.Default);
1419 childCols[i].DataContainer.CopyValue(parentCols[i].DataContainer,defaultIdx,Proposed);
1428 //Copy all values of this DataRow to the row parameter.
1429 internal void CopyValuesToRow(DataRow row)
1432 throw new ArgumentNullException("row");
1434 throw new ArgumentException("'row' is the same as this object");
1436 foreach(DataColumn column in Table.Columns) {
1437 DataColumn targetColumn = row.Table.Columns[column.ColumnName];
1438 //if a column with the same name exists in both rows copy the values
1439 if(targetColumn != null) {
1440 int index = targetColumn.Ordinal;
1441 if (HasVersion(DataRowVersion.Original)) {
1442 if (row.Original < 0) {
1443 row.Original = row.Table.RecordCache.NewRecord();
1445 object val = column[Original];
1446 row.CheckValue(val, targetColumn);
1447 targetColumn[row.Original] = val;
1450 if (row.Original > 0) {
1451 row.Table.RecordCache.DisposeRecord(row.Original);
1456 if (HasVersion(DataRowVersion.Current)) {
1457 if (row.Current < 0) {
1458 row.Current = row.Table.RecordCache.NewRecord();
1460 object val = column[Current];
1461 row.CheckValue(val, targetColumn);
1462 targetColumn[row.Current] = val;
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 object val = column[row.Proposed];
1476 row.CheckValue(val, targetColumn);
1477 targetColumn[row.Proposed] = val;
1480 if (row.Proposed > 0) {
1481 row.Table.RecordCache.DisposeRecord(row.Proposed);
1492 internal void CopyErrors(DataRow row)
1494 row.RowError = RowError;
1495 DataColumn[] errorColumns = GetColumnsInError();
1496 foreach(DataColumn col in errorColumns) {
1497 DataColumn targetColumn = row.Table.Columns[col.ColumnName];
1498 row.SetColumnError(targetColumn,GetColumnError(col));
1502 internal bool IsRowChanged(DataRowState rowState) {
1503 if((RowState & rowState) != 0)
1506 //we need to find if child rows of this row changed.
1507 //if yes - we should return true
1509 // if the rowState is deleted we should get the original version of the row
1510 // else - we should get the current version of the row.
1511 DataRowVersion version = (rowState == DataRowState.Deleted) ? DataRowVersion.Original : DataRowVersion.Current;
1512 int count = Table.ChildRelations.Count;
1513 for (int i = 0; i < count; i++){
1514 DataRelation rel = Table.ChildRelations[i];
1515 DataRow[] childRows = GetChildRows(rel, version);
1516 for (int j = 0; j < childRows.Length; j++){
1517 if (childRows[j].IsRowChanged(rowState))
1525 internal bool HasParentCollection
1529 return _hasParentCollection;
1533 _hasParentCollection = value;
1537 internal void CheckNullConstraints()
1539 if (_nullConstraintViolation) {
1540 if (HasVersion(DataRowVersion.Proposed)) {
1541 foreach(DataColumn column in Table.Columns) {
1542 if (IsNull(column) && !column.AllowDBNull) {
1543 throw new NoNullAllowedException(_nullConstraintMessage);
1547 _nullConstraintViolation = false;
1551 internal void CheckReadOnlyStatus() {
1552 int defaultIdx = IndexFromVersion(DataRowVersion.Default);
1553 foreach(DataColumn column in Table.Columns) {
1554 if ((column.DataContainer.CompareValues(defaultIdx,Proposed) != 0) && column.ReadOnly) {
1555 throw new ReadOnlyException();
1560 #endregion // Methods
1564 /// This method loads a given value into the existing row affecting versions,
1565 /// state based on the LoadOption. The matrix of changes for this method are as
1566 /// mentioned in the DataTable.Load (IDataReader, LoadOption) method.
1568 [MonoTODO ("Raise necessary Events")]
1569 internal void Load (object [] values, LoadOption loadOption, bool is_new)
1571 DataRowAction action = DataRowAction.Change;
1573 int temp = Table.RecordCache.NewRecord ();
1574 for (int i = 0 ; i < Table.Columns.Count; i++)
1575 SetValue (i, values [i], temp);
1577 if (is_new) { // new row
1578 if (editing || RowState == DataRowState.Detached)
1585 if (loadOption == LoadOption.OverwriteChanges
1586 || (loadOption == LoadOption.PreserveChanges
1587 && rowState == DataRowState.Unchanged)) {
1593 rowState = DataRowState.Unchanged;
1594 action = DataRowAction.ChangeCurrentAndOriginal;
1598 if (loadOption == LoadOption.PreserveChanges) {
1599 if (rowState != DataRowState.Deleted) {
1601 rowState = DataRowState.Modified;
1602 action = DataRowAction.ChangeOriginal;
1607 bool not_used = true;
1609 if (rowState != DataRowState.Deleted) {
1610 int index = editing ? _proposed : _current;
1611 if (! RecordCache.CompareRecords (Table, index, temp)) {
1617 if (rowState == DataRowState.Unchanged)
1618 rowState = DataRowState.Modified;
1623 Table.RecordCache.DisposeRecord (temp);