2 // System.Data.DataRow.cs
5 // Rodrigo Moya <rodrigo@ximian.com>
6 // Daniel Morgan <danmorg@sc.rr.com>
7 // Tim Coleman <tim@timcoleman.com>
8 // Ville Palo <vi64pa@koti.soon.fi>
9 // Alan Tam Siu Lung <Tam@SiuLung.com>
10 // Sureshkumar T <tsureshkumar@novell.com>
12 // (C) Ximian, Inc 2002
13 // (C) Daniel Morgan 2002, 2003
14 // Copyright (C) 2002 Tim Coleman
18 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
20 // Permission is hereby granted, free of charge, to any person obtaining
21 // a copy of this software and associated documentation files (the
22 // "Software"), to deal in the Software without restriction, including
23 // without limitation the rights to use, copy, modify, merge, publish,
24 // distribute, sublicense, and/or sell copies of the Software, and to
25 // permit persons to whom the Software is furnished to do so, subject to
26 // the following conditions:
28 // The above copyright notice and this permission notice shall be
29 // included in all copies or substantial portions of the Software.
31 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
32 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
33 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
34 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
35 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
36 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
37 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
41 using System.Data.Common;
42 using System.Collections;
43 using System.Globalization;
46 using System.ComponentModel;
49 namespace System.Data {
51 /// Represents a row of data in a DataTable.
60 private DataTable _table;
62 internal int _original = -1;
63 internal int _current = -1;
64 internal int _proposed = -1;
66 private ArrayList _columnErrors;
67 private string rowError;
68 internal int xmlRowID = 0;
69 internal bool _nullConstraintViolation;
70 private string _nullConstraintMessage;
71 private bool _hasParentCollection;
72 private bool _inChangingEvent;
74 internal bool _rowChanged = false;
76 private XmlDataDocument.XmlDataElement mappedElement;
77 internal bool _inExpressionEvaluation = false;
84 /// This member supports the .NET Framework infrastructure and is not intended to be
85 /// used directly from your code.
87 protected internal DataRow (DataRowBuilder builder)
89 _table = builder.Table;
90 // Get the row id from the builder.
91 _rowId = builder._rowId;
93 rowError = String.Empty;
96 internal DataRow(DataTable table,int rowId)
102 #endregion // Constructors
106 private ArrayList ColumnErrors
109 if (_columnErrors == null) {
110 _columnErrors = new ArrayList();
112 return _columnErrors;
116 _columnErrors = value;
121 /// Gets a value indicating whether there are errors in a row.
123 public bool HasErrors {
125 if (RowError != string.Empty)
128 foreach(String columnError in ColumnErrors) {
129 if (columnError != null && columnError != string.Empty) {
138 /// Gets or sets the data stored in the column specified by name.
140 public object this[string columnName] {
141 get { return this[columnName, DataRowVersion.Default]; }
143 int columnIndex = _table.Columns.IndexOf (columnName);
144 if (columnIndex == -1) {
145 throw new ArgumentException ("The column " + columnName +
146 " does not belong to the table : " + _table.TableName);
148 this[columnIndex] = value;
153 /// Gets or sets the data stored in specified DataColumn
155 public object this[DataColumn column] {
158 return this[column, DataRowVersion.Default];}
160 int columnIndex = _table.Columns.IndexOf (column);
161 if (columnIndex == -1)
162 throw new ArgumentException ("The column does not belong to this table.");
163 this[columnIndex] = value;
168 /// Gets or sets the data stored in column specified by index.
170 public object this[int columnIndex] {
171 get { return this[columnIndex, DataRowVersion.Default]; }
173 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
174 throw new IndexOutOfRangeException ();
175 if (RowState == DataRowState.Deleted)
176 throw new DeletedRowInaccessibleException ();
178 DataColumn column = _table.Columns[columnIndex];
179 _table.ChangingDataColumn (this, column, value);
181 if (value == null && column.DataType != typeof(string)) {
182 throw new ArgumentException("Cannot set column " + column.ColumnName + " to be null, Please use DBNull instead");
186 CheckValue (value, column);
187 bool orginalEditing = Proposed >= 0;
188 if (!orginalEditing) {
192 column[Proposed] = value;
193 _table.ChangedDataColumn (this, column, value);
194 if (!orginalEditing) {
201 /// Gets the specified version of data stored in the named column.
203 public object this[string columnName, DataRowVersion version] {
205 int columnIndex = _table.Columns.IndexOf (columnName);
206 if (columnIndex == -1) {
207 throw new ArgumentException ("The column " + columnName +
208 " does not belong to the table : " + _table.TableName);
210 return this[columnIndex, version];
215 /// Gets the specified version of data stored in the specified DataColumn.
217 public object this[DataColumn column, DataRowVersion version] {
219 if (column.Table != Table)
220 throw new ArgumentException ("The column does not belong to this table.");
221 int columnIndex = column.Ordinal;
222 return this[columnIndex, version];
227 /// Set a value for the column into the offset specified by the version.<br>
228 /// If the value is auto increment or null, necessary auto increment value
229 /// or the default value will be used.
231 internal void SetValue (int column, object value, int version)
233 DataColumn dc = Table.Columns[column];
235 if (value == null && ! dc.AutoIncrement) // set default value / auto increment
236 value = dc.DefaultValue;
238 Table.ChangingDataColumn (this, dc, value);
239 CheckValue (value, dc);
240 if ( ! dc.AutoIncrement)
241 dc [version] = value;
242 else if (_proposed >= 0 && _proposed != version) // proposed holds the AI
243 dc [version] = dc [_proposed];
247 /// Gets the data stored in the column, specified by index and version of the data to
250 public object this[int columnIndex, DataRowVersion version] {
252 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
253 throw new IndexOutOfRangeException ();
255 DataColumn column = _table.Columns[columnIndex];
256 int recordIndex = IndexFromVersion(version);
258 if (column.Expression != String.Empty && _table.Rows.IndexOf (this) != -1) {
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 return column[recordIndex];
272 /// Gets or sets all of the values for this row through an array.
274 public object[] ItemArray {
276 // Accessing deleted rows
277 if (RowState == DataRowState.Deleted)
278 throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
281 if (RowState == DataRowState.Detached)
282 // Check if datarow is removed from the table.
284 throw new RowNotInTableException(
285 "This row has been removed from a table and does not have any data."
286 +" BeginEdit() will allow creation of new data in this row.");
292 object[] items = new object[_table.Columns.Count];
294 foreach(DataColumn column in _table.Columns)
295 items[column.Ordinal] = column[index];
299 if (value.Length > _table.Columns.Count)
300 throw new ArgumentException ();
302 if (RowState == DataRowState.Deleted)
303 throw new DeletedRowInaccessibleException ();
307 DataColumnChangeEventArgs e = new DataColumnChangeEventArgs();
308 foreach(DataColumn column in _table.Columns) {
309 int i = column.Ordinal;
310 object newVal = (i < value.Length) ? value[i] : null;
315 e.Initialize(this, column, newVal);
316 CheckValue (e.ProposedValue, column);
317 _table.RaiseOnColumnChanging(e);
318 column[Proposed] = e.ProposedValue;
319 _table.RaiseOnColumnChanged(e);
327 /// Gets the current state of the row in regards to its relationship to the
328 /// DataRowCollection.
330 public DataRowState RowState {
333 if ((Original == -1) && (Current == -1))
334 return DataRowState.Detached;
335 if (Original == Current)
336 return DataRowState.Unchanged;
338 return DataRowState.Added;
340 return DataRowState.Deleted;
341 return DataRowState.Modified;
345 if (DataRowState.Detached == value) {
349 if (DataRowState.Unchanged == value)
351 if (DataRowState.Added == value)
353 if (DataRowState.Deleted == value)
360 public void SetAdded ()
362 if (RowState != DataRowState.Unchanged)
363 throw new InvalidOperationException ("SetAdded and SetModified can only be called on DataRows with Unchanged DataRowState.");
367 public void SetModified ()
369 if (RowState != DataRowState.Unchanged)
370 throw new InvalidOperationException ("SetAdded and SetModified can only be called on DataRows with Unchanged DataRowState.");
371 Current = _table.RecordCache.NewRecord ();
372 _table.RecordCache.CopyRecord (_table, Original, Current);
377 /// Gets the DataTable for which this row has a schema.
379 public DataTable Table {
391 /// Gets and sets index of row. This is used from
394 internal int XmlRowID {
404 /// Gets and sets index of row.
415 internal int Original
422 //Table.RecordCache[_original] = null;
423 Table.RecordCache[value] = this;
436 //Table.RecordCache[_current] = null;
437 Table.RecordCache[value] = this;
443 internal int Proposed
450 //Table.RecordCache[_proposed] = null;
451 Table.RecordCache[value] = this;
461 //FIXME?: Couldn't find a way to set the RowState when adding the DataRow
462 //to a Datatable so I added this method. Delete if there is a better way.
463 internal void AttachRow() {
464 if (Proposed != -1) {
466 Table.RecordCache.DisposeRecord(Current);
473 //FIXME?: Couldn't find a way to set the RowState when removing the DataRow
474 //from a Datatable so I added this method. Delete if there is a better way.
475 internal void DetachRow() {
477 _table.RecordCache.DisposeRecord(Proposed);
478 if (Proposed == Current) {
481 if (Proposed == Original) {
488 _table.RecordCache.DisposeRecord(Current);
489 if (Current == Original) {
496 _table.RecordCache.DisposeRecord(Original);
501 _hasParentCollection = false;
504 internal void ImportRecord(int record)
506 if (HasVersion(DataRowVersion.Proposed)) {
507 Table.RecordCache.DisposeRecord(Proposed);
512 foreach(DataColumn column in Table.Columns.AutoIncrmentColumns) {
513 column.UpdateAutoIncrementValue(column.DataContainer.GetInt64(Proposed));
516 foreach (DataColumn col in Table.Columns)
517 CheckValue (this [col], col);
520 private void CheckValue (object v, DataColumn col)
522 if (_hasParentCollection && col.ReadOnly) {
523 throw new ReadOnlyException ();
526 if (v == null || v == DBNull.Value) {
527 if (col.AllowDBNull || col.AutoIncrement || col.DefaultValue != DBNull.Value) {
531 //Constraint violations during data load is raise in DataTable EndLoad
532 this._nullConstraintViolation = true;
533 if (this.Table._duringDataLoad || (Table.DataSet != null && !Table.DataSet.EnforceConstraints))
534 this.Table._nullConstraintViolationDuringDataLoad = true;
535 _nullConstraintMessage = "Column '" + col.ColumnName + "' does not allow nulls.";
540 /// Gets or sets the custom error description for a row.
542 public string RowError {
551 internal int IndexFromVersion(DataRowVersion version)
554 case DataRowVersion.Default:
562 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.");
564 throw new DeletedRowInaccessibleException("Deleted row information cannot be accessed through the row.");
566 case DataRowVersion.Proposed:
567 return AssertValidVersionIndex(version, Proposed);
568 case DataRowVersion.Current:
569 return AssertValidVersionIndex(version, Current);
570 case DataRowVersion.Original:
571 return AssertValidVersionIndex(version, Original);
573 throw new DataException ("Version must be Original, Current, or Proposed.");
577 private int AssertValidVersionIndex(DataRowVersion version, int index) {
581 throw new VersionNotFoundException(String.Format("There is no {0} data to accces.", version));
584 internal DataRowVersion VersionFromIndex(int index) {
586 throw new ArgumentException("Index must not be negative.");
588 // the order of ifs matters
589 if (index == Current)
590 return DataRowVersion.Current;
591 if (index == Original)
592 return DataRowVersion.Original;
593 if (index == Proposed)
594 return DataRowVersion.Proposed;
596 throw new ArgumentException(String.Format("The index {0} does not belong to this row.", index) );
599 internal XmlDataDocument.XmlDataElement DataElement {
601 if (mappedElement != null || _table.DataSet == null ||
602 _table.DataSet._xmlDataDocument == null)
603 return mappedElement;
605 // create mapped XmlDataElement
606 mappedElement = new XmlDataDocument.XmlDataElement (this, _table.Prefix, XmlHelper.Encode (_table.TableName),
607 _table.Namespace, _table.DataSet._xmlDataDocument);
608 return mappedElement;
610 set { mappedElement = value; }
613 internal void SetOriginalValue (string columnName, object val)
615 DataColumn column = _table.Columns[columnName];
616 _table.ChangingDataColumn (this, column, val);
618 if (Original < 0 || Original == Current) {
619 Original = Table.RecordCache.NewRecord();
621 CheckValue (val, column);
622 column[Original] = val;
626 /// Commits all the changes made to this row since the last time AcceptChanges was
629 public void AcceptChanges ()
631 EndEdit(); // in case it hasn't been called
633 _table.ChangingDataRow (this, DataRowAction.Commit);
634 CheckChildRows(DataRowAction.Commit);
636 case DataRowState.Unchanged:
638 case DataRowState.Added:
639 case DataRowState.Modified:
641 Table.RecordCache.DisposeRecord(Original);
644 case DataRowState.Deleted:
645 Table.DeleteRowFromIndexes(this);
646 _table.Rows.RemoveInternal (this);
649 case DataRowState.Detached:
650 throw new RowNotInTableException("Cannot perform this operation on a row not in the table.");
653 _table.ChangedDataRow (this, DataRowAction.Commit);
657 /// Begins an edit operation on a DataRow object.
660 [EditorBrowsable (EditorBrowsableState.Advanced)]
662 public void BeginEdit ()
664 if (_inChangingEvent)
665 throw new InRowChangingEventException("Cannot call BeginEdit inside an OnRowChanging event.");
666 if (RowState == DataRowState.Deleted)
667 throw new DeletedRowInaccessibleException ();
669 if (!HasVersion (DataRowVersion.Proposed)) {
670 Proposed = Table.RecordCache.NewRecord();
671 int from = HasVersion(DataRowVersion.Current) ? Current : Table.DefaultValuesRowIndex;
672 for(int i = 0; i < Table.Columns.Count; i++){
673 DataColumn column = Table.Columns[i];
674 column.DataContainer.CopyValue(from,Proposed);
680 /// Cancels the current edit on the row.
683 [EditorBrowsable (EditorBrowsableState.Advanced)]
685 public void CancelEdit ()
687 if (_inChangingEvent) {
688 throw new InRowChangingEventException("Cannot call CancelEdit inside an OnRowChanging event.");
691 if (HasVersion (DataRowVersion.Proposed)) {
692 int oldRecord = Proposed;
693 DataRowState oldState = RowState;
694 Table.RecordCache.DisposeRecord(Proposed);
697 foreach(Index index in Table.Indexes)
698 index.Update(this,oldRecord, DataRowVersion.Proposed, oldState);
703 /// Clears the errors for the row, including the RowError and errors set with
706 public void ClearErrors ()
708 rowError = String.Empty;
709 ColumnErrors.Clear();
713 /// Deletes the DataRow.
715 public void Delete ()
717 _table.DeletingDataRow(this, DataRowAction.Delete);
719 case DataRowState.Added:
720 // check what to do with child rows
721 CheckChildRows(DataRowAction.Delete);
722 _table.DeleteRowFromIndexes (this);
723 Table.Rows.RemoveInternal (this);
725 // if row was in Added state we move it to Detached.
728 case DataRowState.Deleted:
729 case DataRowState.Detached:
732 // check what to do with child rows
733 CheckChildRows(DataRowAction.Delete);
737 int current = Current;
738 DataRowState oldState = RowState;
739 if (Current != Original)
740 _table.RecordCache.DisposeRecord(Current);
742 foreach(Index index in Table.Indexes)
743 index.Update(this, current, DataRowVersion.Current, oldState);
745 _table.DeletedDataRow(this, DataRowAction.Delete);
748 // check the child rows of this row before deleting the row.
749 private void CheckChildRows(DataRowAction action)
752 // in this method we find the row that this row is in a relation with them.
753 // in shortly we find all child rows of this row.
754 // then we function according to the DeleteRule of the foriegnkey.
757 // 1. find if this row is attached to dataset.
758 // 2. find if EnforceConstraints is true.
759 // 3. find if there are any constraint on the table that the row is in.
760 if (_table.DataSet != null && _table.DataSet.EnforceConstraints && _table.Constraints.Count > 0)
762 foreach (DataTable table in _table.DataSet.Tables)
764 // loop on all ForeignKeyConstrain of the table.
765 foreach (Constraint constraint in table.Constraints) {
766 if (constraint is ForeignKeyConstraint) {
767 ForeignKeyConstraint fk = (ForeignKeyConstraint) constraint;
768 if (fk.RelatedTable == _table) {
770 case DataRowAction.Delete:
771 CheckChildRows(fk, action, fk.DeleteRule);
773 case DataRowAction.Commit:
774 case DataRowAction.Rollback:
775 if (fk.AcceptRejectRule != AcceptRejectRule.None)
776 CheckChildRows(fk, action, Rule.Cascade);
779 CheckChildRows(fk, action, fk.UpdateRule);
789 private void CheckChildRows(ForeignKeyConstraint fkc, DataRowAction action, Rule rule)
791 DataRow[] childRows = GetChildRows(fkc, DataRowVersion.Current);
794 case Rule.Cascade: // delete or change all relted rows.
795 if (childRows != null)
797 for (int j = 0; j < childRows.Length; j++)
799 // if action is delete we delete all child rows
801 case DataRowAction.Delete: {
802 if (childRows[j].RowState != DataRowState.Deleted)
803 childRows[j].Delete();
807 // if action is change we change the values in the child row
808 case DataRowAction.Change: {
809 // change only the values in the key columns
810 // set the childcolumn value to the new parent row value
811 for (int k = 0; k < fkc.Columns.Length; k++)
812 if (!fkc.RelatedColumns [k].DataContainer [Current].Equals (fkc.RelatedColumns [k].DataContainer [Proposed]))
813 childRows[j][fkc.Columns[k]] = this[fkc.RelatedColumns[k], DataRowVersion.Proposed];
818 case DataRowAction.Rollback: {
819 if (childRows[j].RowState != DataRowState.Unchanged)
820 childRows[j].RejectChanges ();
827 case Rule.None: // throw an exception if there are any child rows.
828 if (childRows != null)
830 for (int j = 0; j < childRows.Length; j++)
832 if (childRows[j].RowState != DataRowState.Deleted)
834 string changeStr = "Cannot change this row because constraints are enforced on relation " + fkc.ConstraintName +", and changing this row will strand child rows.";
835 string delStr = "Cannot delete this row because constraints are enforced on relation " + fkc.ConstraintName +", and deleting this row will strand child rows.";
836 string message = action == DataRowAction.Delete ? delStr : changeStr;
837 throw new InvalidConstraintException(message);
842 case Rule.SetDefault: // set the values in the child rows to the defult value of the columns.
843 if (childRows != null && childRows.Length > 0) {
844 int defaultValuesRowIndex = childRows[0].Table.DefaultValuesRowIndex;
845 foreach(DataRow childRow in childRows) {
846 if (childRow.RowState != DataRowState.Deleted) {
847 int defaultIdx = childRow.IndexFromVersion(DataRowVersion.Default);
848 foreach(DataColumn column in fkc.Columns) {
849 column.DataContainer.CopyValue(defaultValuesRowIndex,defaultIdx);
855 case Rule.SetNull: // set the values in the child row to null.
856 if (childRows != null)
858 for (int j = 0; j < childRows.Length; j++)
860 DataRow child = childRows[j];
861 if (childRows[j].RowState != DataRowState.Deleted)
863 // set only the key columns to DBNull
864 for (int k = 0; k < fkc.Columns.Length; k++)
865 child.SetNull(fkc.Columns[k]);
875 /// Ends the edit occurring on the row.
878 [EditorBrowsable (EditorBrowsableState.Advanced)]
880 public void EndEdit ()
882 if (_inChangingEvent)
883 throw new InRowChangingEventException("Cannot call EndEdit inside an OnRowChanging event.");
885 if (RowState == DataRowState.Detached)
888 if (HasVersion (DataRowVersion.Proposed))
890 CheckReadOnlyStatus();
892 _inChangingEvent = true;
895 _table.ChangingDataRow(this, DataRowAction.Change);
899 _inChangingEvent = false;
902 DataRowState oldState = RowState;
904 int oldRecord = Current;
908 //FIXME : ideally indexes shouldnt be maintained during dataload.But this needs to
909 //be implemented at multiple places.For now, just maintain the index.
910 //if (!Table._duringDataLoad) {
911 foreach(Index index in Table.Indexes) {
912 index.Update(this,oldRecord, DataRowVersion.Current, oldState);
919 // restore previous state to let the cascade update to find the rows
923 CheckChildRows(DataRowAction.Change);
930 int proposed = Proposed >= 0 ? Proposed : Current;
932 //if (!Table._duringDataLoad) {
933 foreach(Index index in Table.Indexes) {
934 index.Update(this,proposed, DataRowVersion.Current, RowState);
940 if (Original != oldRecord) {
941 Table.RecordCache.DisposeRecord(oldRecord);
944 // Note : row state must not be changed before all the job on indexes finished,
945 // since the indexes works with recods rather than with rows and the decision
946 // which of row records to choose depends on row state.
947 if (_rowChanged == true) {
948 _table.ChangedDataRow(this, DataRowAction.Change);
955 /// Gets the child rows of this DataRow using the specified DataRelation.
957 public DataRow[] GetChildRows (DataRelation relation)
959 return GetChildRows (relation, DataRowVersion.Default);
963 /// Gets the child rows of a DataRow using the specified RelationName of a
966 public DataRow[] GetChildRows (string relationName)
968 return GetChildRows (Table.DataSet.Relations[relationName]);
972 /// Gets the child rows of a DataRow using the specified DataRelation, and
975 public DataRow[] GetChildRows (DataRelation relation, DataRowVersion version)
977 if (relation == null)
978 return Table.NewRowArray(0);
980 if (this.Table == null)
981 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.");
983 if (relation.DataSet != this.Table.DataSet)
984 throw new ArgumentException();
986 if (_table != relation.ParentTable)
987 throw new InvalidConstraintException ("GetChildRow requires a row whose Table is " + relation.ParentTable + ", but the specified row's table is " + _table);
989 if (relation.ChildKeyConstraint != null)
990 return GetChildRows (relation.ChildKeyConstraint, version);
992 ArrayList rows = new ArrayList();
993 DataColumn[] parentColumns = relation.ParentColumns;
994 DataColumn[] childColumns = relation.ChildColumns;
995 int numColumn = parentColumns.Length;
996 DataRow[] result = null;
998 int versionIndex = IndexFromVersion(version);
999 int tmpRecord = relation.ChildTable.RecordCache.NewRecord();
1002 for (int i = 0; i < numColumn; i++) {
1003 // according to MSDN: the DataType value for both columns must be identical.
1004 childColumns[i].DataContainer.CopyValue(parentColumns[i].DataContainer, versionIndex, tmpRecord);
1007 Index index = relation.ChildTable.FindIndex(childColumns);
1009 if (index != null) {
1010 int[] records = index.FindAll(tmpRecord);
1011 result = relation.ChildTable.NewRowArray(records.Length);
1012 for(int i=0; i < records.Length; i++) {
1013 result[i] = relation.ChildTable.RecordCache[records[i]];
1017 foreach (DataRow row in relation.ChildTable.Rows) {
1018 bool allColumnsMatch = false;
1019 if (row.HasVersion(DataRowVersion.Default)) {
1020 allColumnsMatch = true;
1021 int childIndex = row.IndexFromVersion(DataRowVersion.Default);
1022 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) {
1023 if (childColumns[columnCnt].DataContainer.CompareValues(childIndex, tmpRecord) != 0) {
1024 allColumnsMatch = false;
1029 if (allColumnsMatch) rows.Add(row);
1031 result = relation.ChildTable.NewRowArray(rows.Count);
1032 rows.CopyTo(result, 0);
1036 relation.ChildTable.RecordCache.DisposeRecord(tmpRecord);
1043 /// Gets the child rows of a DataRow using the specified RelationName of a
1044 /// DataRelation, and DataRowVersion.
1046 public DataRow[] GetChildRows (string relationName, DataRowVersion version)
1048 return GetChildRows (Table.DataSet.Relations[relationName], version);
1051 private DataRow[] GetChildRows (ForeignKeyConstraint fkc, DataRowVersion version)
1053 ArrayList rows = new ArrayList();
1054 DataColumn[] parentColumns = fkc.RelatedColumns;
1055 DataColumn[] childColumns = fkc.Columns;
1056 int numColumn = parentColumns.Length;
1058 Index index = fkc.Index;
1060 int curIndex = IndexFromVersion(version);
1061 int tmpRecord = fkc.Table.RecordCache.NewRecord();
1062 for (int i = 0; i < numColumn; i++) {
1063 // according to MSDN: the DataType value for both columns must be identical.
1064 childColumns[i].DataContainer.CopyValue(parentColumns[i].DataContainer, curIndex, tmpRecord);
1068 if (index != null) {
1069 // get the child rows from the index
1070 int[] childRecords = index.FindAll(tmpRecord);
1071 for (int i = 0; i < childRecords.Length; i++) {
1072 rows.Add (childColumns[i].Table.RecordCache[childRecords[i]]);
1075 else { // if there is no index we search manualy.
1076 foreach (DataRow row in fkc.Table.Rows) {
1077 bool allColumnsMatch = false;
1078 if (row.HasVersion(DataRowVersion.Default)) {
1079 allColumnsMatch = true;
1080 int childIndex = row.IndexFromVersion(DataRowVersion.Default);
1081 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) {
1082 if (childColumns[columnCnt].DataContainer.CompareValues(childIndex, tmpRecord) != 0) {
1083 allColumnsMatch = false;
1088 if (allColumnsMatch) {
1095 fkc.Table.RecordCache.DisposeRecord(tmpRecord);
1098 DataRow[] result = fkc.Table.NewRowArray(rows.Count);
1099 rows.CopyTo(result, 0);
1104 /// Gets the error description of the specified DataColumn.
1106 public string GetColumnError (DataColumn column)
1109 throw new ArgumentNullException("column");
1111 int index = _table.Columns.IndexOf(column);
1113 throw new ArgumentException(String.Format("Column '{0}' does not belong to table {1}.", column.ColumnName, Table.TableName));
1115 return GetColumnError (index);
1119 /// Gets the error description for the column specified by index.
1121 public string GetColumnError (int columnIndex)
1123 if (columnIndex < 0 || columnIndex >= Table.Columns.Count)
1124 throw new IndexOutOfRangeException ();
1126 string retVal = null;
1127 if (columnIndex < ColumnErrors.Count) {
1128 retVal = (String) ColumnErrors[columnIndex];
1130 return (retVal != null) ? retVal : String.Empty;
1134 /// Gets the error description for the column, specified by name.
1136 public string GetColumnError (string columnName)
1138 return GetColumnError (_table.Columns.IndexOf(columnName));
1142 /// Gets an array of columns that have errors.
1144 public DataColumn[] GetColumnsInError ()
1146 ArrayList dataColumns = new ArrayList ();
1148 int columnOrdinal = 0;
1149 foreach(String columnError in ColumnErrors) {
1150 if (columnError != null && columnError != String.Empty) {
1151 dataColumns.Add (_table.Columns[columnOrdinal]);
1156 return (DataColumn[])(dataColumns.ToArray (typeof(DataColumn)));
1160 /// Gets the parent row of a DataRow using the specified DataRelation.
1162 public DataRow GetParentRow (DataRelation relation)
1164 return GetParentRow (relation, DataRowVersion.Default);
1168 /// Gets the parent row of a DataRow using the specified RelationName of a
1171 public DataRow GetParentRow (string relationName)
1173 return GetParentRow (relationName, DataRowVersion.Default);
1177 /// Gets the parent row of a DataRow using the specified DataRelation, and
1180 public DataRow GetParentRow (DataRelation relation, DataRowVersion version)
1182 DataRow[] rows = GetParentRows(relation, version);
1183 if (rows.Length == 0) return null;
1188 /// Gets the parent row of a DataRow using the specified RelationName of a
1189 /// DataRelation, and DataRowVersion.
1191 public DataRow GetParentRow (string relationName, DataRowVersion version)
1193 return GetParentRow (Table.DataSet.Relations[relationName], version);
1197 /// Gets the parent rows of a DataRow using the specified DataRelation.
1199 public DataRow[] GetParentRows (DataRelation relation)
1201 return GetParentRows (relation, DataRowVersion.Default);
1205 /// Gets the parent rows of a DataRow using the specified RelationName of a
1208 public DataRow[] GetParentRows (string relationName)
1210 return GetParentRows (relationName, DataRowVersion.Default);
1214 /// Gets the parent rows of a DataRow using the specified DataRelation, and
1217 public DataRow[] GetParentRows (DataRelation relation, DataRowVersion version)
1219 // TODO: Caching for better preformance
1220 if (relation == null)
1221 return Table.NewRowArray(0);
1223 if (this.Table == null)
1224 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.");
1226 if (relation.DataSet != this.Table.DataSet)
1227 throw new ArgumentException();
1229 if (_table != relation.ChildTable)
1230 throw new InvalidConstraintException ("GetParentRows requires a row whose Table is " + relation.ChildTable + ", but the specified row's table is " + _table);
1232 ArrayList rows = new ArrayList();
1233 DataColumn[] parentColumns = relation.ParentColumns;
1234 DataColumn[] childColumns = relation.ChildColumns;
1235 int numColumn = parentColumns.Length;
1237 int curIndex = IndexFromVersion(version);
1238 int tmpRecord = relation.ParentTable.RecordCache.NewRecord();
1239 for (int i = 0; i < numColumn; i++) {
1240 // according to MSDN: the DataType value for both columns must be identical.
1241 parentColumns[i].DataContainer.CopyValue(childColumns[i].DataContainer, curIndex, tmpRecord);
1245 Index index = relation.ParentTable.FindIndex(parentColumns);
1246 if (index != null) { // get the parent rows from the index
1247 int[] parentRecords = index.FindAll(tmpRecord);
1248 for (int i = 0; i < parentRecords.Length; i++) {
1249 rows.Add (parentColumns[i].Table.RecordCache[parentRecords[i]]);
1252 else { // no index so we have to search manualy.
1253 foreach (DataRow row in relation.ParentTable.Rows) {
1254 bool allColumnsMatch = false;
1255 if (row.HasVersion(DataRowVersion.Default)) {
1256 allColumnsMatch = true;
1257 int parentIndex = row.IndexFromVersion(DataRowVersion.Default);
1258 for (int columnCnt = 0; columnCnt < numColumn; columnCnt++) {
1259 if (parentColumns[columnCnt].DataContainer.CompareValues(parentIndex, tmpRecord) != 0) {
1260 allColumnsMatch = false;
1265 if (allColumnsMatch) {
1272 relation.ParentTable.RecordCache.DisposeRecord(tmpRecord);
1275 DataRow[] result = relation.ParentTable.NewRowArray(rows.Count);
1276 rows.CopyTo(result, 0);
1281 /// Gets the parent rows of a DataRow using the specified RelationName of a
1282 /// DataRelation, and DataRowVersion.
1284 public DataRow[] GetParentRows (string relationName, DataRowVersion version)
1286 return GetParentRows (Table.DataSet.Relations[relationName], version);
1290 /// Gets a value indicating whether a specified version exists.
1292 public bool HasVersion (DataRowVersion version)
1295 case DataRowVersion.Default:
1296 return (Proposed >= 0 || Current >= 0);
1297 case DataRowVersion.Proposed:
1298 return Proposed >= 0;
1299 case DataRowVersion.Current:
1300 return Current >= 0;
1301 case DataRowVersion.Original:
1302 return Original >= 0;
1304 return IndexFromVersion(version) >= 0;
1309 /// Gets a value indicating whether the specified DataColumn contains a null value.
1311 public bool IsNull (DataColumn column)
1313 return IsNull(column, DataRowVersion.Default);
1317 /// Gets a value indicating whether the column at the specified index contains a null
1320 public bool IsNull (int columnIndex)
1322 return IsNull(Table.Columns[columnIndex]);
1326 /// Gets a value indicating whether the named column contains a null value.
1328 public bool IsNull (string columnName)
1330 return IsNull(Table.Columns[columnName]);
1334 /// Gets a value indicating whether the specified DataColumn and DataRowVersion
1335 /// contains a null value.
1337 public bool IsNull (DataColumn column, DataRowVersion version)
1339 object o = this[column,version];
1340 return column.DataContainer.IsNull(IndexFromVersion(version));
1344 /// Returns a value indicating whether all of the row columns specified contain a null value.
1346 internal bool IsNullColumns(DataColumn[] columns)
1348 bool allNull = true;
1349 for (int i = 0; i < columns.Length; i++)
1351 if (!IsNull(columns[i]))
1361 /// Rejects all changes made to the row since AcceptChanges was last called.
1363 public void RejectChanges ()
1365 if (RowState == DataRowState.Detached)
1366 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.");
1367 // If original is null, then nothing has happened since AcceptChanges
1368 // was last called. We have no "original" to go back to.
1370 _table.ChangedDataRow (this, DataRowAction.Rollback);
1373 //TODO : Need to Verify the constraints..
1375 case DataRowState.Added:
1376 _table.DeleteRowFromIndexes (this);
1377 _table.Rows.RemoveInternal (this);
1380 case DataRowState.Modified:
1381 int current = Current;
1382 Table.RecordCache.DisposeRecord (Current);
1383 CheckChildRows (DataRowAction.Rollback);
1385 foreach (Index index in Table.Indexes)
1386 index.Update(this, current, DataRowVersion.Current, DataRowState.Modified);
1388 case DataRowState.Deleted:
1389 CheckChildRows (DataRowAction.Rollback);
1391 // Add row to index and validate if the constraints are satisfied
1398 /// Sets the error description for a column specified as a DataColumn.
1400 public void SetColumnError (DataColumn column, string error)
1402 SetColumnError (_table.Columns.IndexOf (column), error);
1406 /// Sets the error description for a column specified by index.
1408 public void SetColumnError (int columnIndex, string error)
1410 if (columnIndex < 0 || columnIndex >= Table.Columns.Count)
1411 throw new IndexOutOfRangeException ();
1413 while (columnIndex >= ColumnErrors.Count)
1414 ColumnErrors.Add (null);
1416 ColumnErrors [columnIndex] = error;
1420 /// Sets the error description for a column specified by name.
1422 public void SetColumnError (string columnName, string error)
1424 SetColumnError (_table.Columns.IndexOf (columnName), error);
1428 /// Sets the value of the specified DataColumn to a null value.
1430 protected void SetNull (DataColumn column)
1432 this[column] = DBNull.Value;
1436 /// Sets the parent row of a DataRow with specified new parent DataRow.
1438 public void SetParentRow (DataRow parentRow)
1440 SetParentRow(parentRow, null);
1444 /// Sets the parent row of a DataRow with specified new parent DataRow and
1447 public void SetParentRow (DataRow parentRow, DataRelation relation)
1449 if (_table == null || parentRow.Table == null)
1450 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.");
1452 if (parentRow != null && _table.DataSet != parentRow.Table.DataSet)
1453 throw new ArgumentException();
1455 if (RowState == DataRowState.Detached && !HasVersion(DataRowVersion.Default)) {
1456 // 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
1457 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.");
1462 IEnumerable relations;
1463 if (relation == null) {
1464 relations = _table.ParentRelations;
1467 relations = new DataRelation[] { relation };
1470 foreach (DataRelation rel in relations)
1472 DataColumn[] childCols = rel.ChildColumns;
1473 DataColumn[] parentCols = rel.ParentColumns;
1475 for (int i = 0; i < parentCols.Length; i++)
1477 if (parentRow == null) {
1478 childCols[i].DataContainer[Proposed] = DBNull.Value;
1481 int defaultIdx = parentRow.IndexFromVersion(DataRowVersion.Default);
1482 childCols[i].DataContainer.CopyValue(parentCols[i].DataContainer,defaultIdx,Proposed);
1491 //Copy all values of this DataRow to the row parameter.
1492 internal void CopyValuesToRow(DataRow row)
1495 throw new ArgumentNullException("row");
1497 throw new ArgumentException("'row' is the same as this object");
1499 // create target records if missing.
1500 if (HasVersion(DataRowVersion.Original)) {
1501 if (row.Original < 0)
1502 row.Original = row.Table.RecordCache.NewRecord();
1503 else if (row.Original == row.Current) {
1504 row.Original = row.Table.RecordCache.NewRecord();
1505 row.Table.RecordCache.CopyRecord (row.Table, row.Current, row.Original);
1508 if (row.Original > 0) {
1509 if (row.Original != row.Current)
1510 row.Table.RecordCache.DisposeRecord(row.Original);
1515 if (HasVersion(DataRowVersion.Current)) {
1516 if (Current == Original) {
1517 if (row.Current >= 0)
1518 row.Table.RecordCache.DisposeRecord(row.Current);
1519 row.Current = row.Original;
1521 if (row.Current < 0)
1522 row.Current = row.Table.RecordCache.NewRecord();
1525 if (row.Current > 0) {
1526 row.Table.RecordCache.DisposeRecord(row.Current);
1531 if (HasVersion(DataRowVersion.Proposed)) {
1532 if (row.Proposed < 0)
1533 row.Proposed = row.Table.RecordCache.NewRecord();
1535 if (row.Proposed > 0) {
1536 row.Table.RecordCache.DisposeRecord(row.Proposed);
1541 // copy source record values to target records
1542 foreach(DataColumn column in Table.Columns) {
1543 DataColumn targetColumn = row.Table.Columns[column.ColumnName];
1544 //if a column with the same name exists in both rows copy the values
1545 if(targetColumn != null) {
1546 if (HasVersion(DataRowVersion.Original)) {
1547 object val = column[Original];
1548 row.CheckValue(val, targetColumn);
1549 targetColumn[row.Original] = val;
1552 if (HasVersion(DataRowVersion.Current)
1553 && Current != Original) {
1554 object val = column[Current];
1555 row.CheckValue(val, targetColumn);
1556 targetColumn[row.Current] = val;
1559 if (HasVersion(DataRowVersion.Proposed)) {
1560 object val = column[row.Proposed];
1561 row.CheckValue(val, targetColumn);
1562 targetColumn[row.Proposed] = val;
1571 //Merge all values of this DataRow to the row parameter according to merge rules.
1572 internal void MergeValuesToRow(DataRow row, bool preserveChanges)
1575 throw new ArgumentNullException("row");
1577 throw new ArgumentException("'row' is the same as this object");
1579 // Original values are anyway copied
1580 if (HasVersion(DataRowVersion.Original)) {
1581 if (row.Original < 0)
1582 row.Original = row.Table.RecordCache.NewRecord();
1583 else if (row.Original == row.Current
1584 && !(Original == Current && ! preserveChanges)) {
1585 row.Original = row.Table.RecordCache.NewRecord();
1586 row.Table.RecordCache.CopyRecord (row.Table, row.Current, row.Original);
1589 if (row.Original == row.Current) { // if target has same current, better create new original
1590 row.Original = row.Table.RecordCache.NewRecord();
1591 row.Table.RecordCache.CopyRecord (row.Table, row.Current, row.Original);
1595 // if i have current, push all
1596 if (HasVersion(DataRowVersion.Current)) {
1597 if (! preserveChanges && row.Current < 0)
1598 row.Current = row.Table.RecordCache.NewRecord();
1600 if (row.Current > 0 && ! preserveChanges) {
1601 row.Table.RecordCache.DisposeRecord(row.Current);
1606 // copy source record values to target records
1607 foreach(DataColumn column in Table.Columns) {
1608 DataColumn targetColumn = row.Table.Columns[column.ColumnName];
1609 //if a column with the same name exists in both rows copy the values
1610 if(targetColumn != null) {
1611 if (HasVersion(DataRowVersion.Original)) {
1612 object val = column[Original];
1613 row.CheckValue(val, targetColumn);
1614 targetColumn[row.Original] = val;
1617 if (HasVersion(DataRowVersion.Current)
1618 && !preserveChanges) {
1619 object val = column[Current];
1620 row.CheckValue(val, targetColumn);
1621 targetColumn[row.Current] = val;
1630 internal void CopyErrors(DataRow row)
1632 row.RowError = RowError;
1633 DataColumn[] errorColumns = GetColumnsInError();
1634 foreach(DataColumn col in errorColumns) {
1635 DataColumn targetColumn = row.Table.Columns[col.ColumnName];
1636 row.SetColumnError(targetColumn,GetColumnError(col));
1640 internal bool IsRowChanged(DataRowState rowState) {
1641 if((RowState & rowState) != 0)
1644 //we need to find if child rows of this row changed.
1645 //if yes - we should return true
1647 // if the rowState is deleted we should get the original version of the row
1648 // else - we should get the current version of the row.
1649 DataRowVersion version = (rowState == DataRowState.Deleted) ? DataRowVersion.Original : DataRowVersion.Current;
1650 int count = Table.ChildRelations.Count;
1651 for (int i = 0; i < count; i++){
1652 DataRelation rel = Table.ChildRelations[i];
1653 DataRow[] childRows = GetChildRows(rel, version);
1654 for (int j = 0; j < childRows.Length; j++){
1655 if (childRows[j].IsRowChanged(rowState))
1663 internal bool HasParentCollection
1667 return _hasParentCollection;
1671 _hasParentCollection = value;
1675 internal void Validate() {
1676 Table.AddRowToIndexes(this);
1677 AssertConstraints();
1680 void AssertConstraints() {
1681 if (Table == null || Table._duringDataLoad)
1684 if (Table.DataSet != null && !Table.DataSet.EnforceConstraints)
1686 foreach(DataColumn column in Table.Columns) {
1687 if (!column.AllowDBNull && IsNull(column)) {
1688 throw new NoNullAllowedException(_nullConstraintMessage);
1692 foreach(Constraint constraint in Table.Constraints) {
1694 constraint.AssertConstraint(this);
1696 catch(Exception e) {
1697 Table.DeleteRowFromIndexes(this);
1703 internal void CheckNullConstraints()
1705 if (_nullConstraintViolation) {
1706 if (HasVersion(DataRowVersion.Proposed)) {
1707 foreach(DataColumn column in Table.Columns) {
1708 if (IsNull(column) && !column.AllowDBNull) {
1709 throw new NoNullAllowedException(_nullConstraintMessage);
1713 _nullConstraintViolation = false;
1717 internal void CheckReadOnlyStatus() {
1718 int defaultIdx = IndexFromVersion(DataRowVersion.Default);
1719 foreach(DataColumn column in Table.Columns) {
1720 if ((column.DataContainer.CompareValues(defaultIdx,Proposed) != 0) && column.ReadOnly) {
1721 throw new ReadOnlyException();
1726 #endregion // Methods
1730 /// This method loads a given value into the existing row affecting versions,
1731 /// state based on the LoadOption. The matrix of changes for this method are as
1732 /// mentioned in the DataTable.Load (IDataReader, LoadOption) method.
1734 internal void Load (object [] values, LoadOption loadOption)
1739 if (loadOption == LoadOption.OverwriteChanges
1740 || (loadOption == LoadOption.PreserveChanges
1741 && RowState == DataRowState.Unchanged)) {
1742 Table.ChangingDataRow (this, DataRowAction.ChangeCurrentAndOriginal);
1743 temp = Table.CreateRecord (values);
1744 Table.DeleteRowFromIndexes(this);
1745 if (HasVersion (DataRowVersion.Original) && Current != Original)
1746 Table.RecordCache.DisposeRecord (Original);
1749 if (HasVersion (DataRowVersion.Current))
1750 Table.RecordCache.DisposeRecord (Current);
1752 Table.AddRowToIndexes(this);
1753 Table.ChangedDataRow (this, DataRowAction.ChangeCurrentAndOriginal);
1757 if (loadOption == LoadOption.PreserveChanges) {
1758 Table.ChangingDataRow (this, DataRowAction.ChangeOriginal);
1759 temp = Table.CreateRecord (values);
1760 if (HasVersion (DataRowVersion.Original) && Current != Original)
1761 Table.RecordCache.DisposeRecord (Original);
1763 Table.ChangedDataRow (this, DataRowAction.ChangeOriginal);
1768 if (RowState != DataRowState.Deleted) {
1769 int rindex = HasVersion (DataRowVersion.Proposed) ? Proposed : Current;
1770 temp = Table.CreateRecord (values);
1771 if (RowState == DataRowState.Added
1772 || Table.CompareRecords (rindex, temp) != 0) {
1773 Table.ChangingDataRow (this, DataRowAction.Change);
1774 Table.DeleteRowFromIndexes(this);
1775 if (HasVersion (DataRowVersion.Proposed)) {
1776 Table.RecordCache.DisposeRecord (Proposed);
1780 if (Original != Current)
1781 Table.RecordCache.DisposeRecord (Current);
1783 Table.AddRowToIndexes(this);
1784 Table.ChangedDataRow (this, DataRowAction.Change);
1786 Table.ChangingDataRow (this, DataRowAction.Nothing);
1787 Table.RecordCache.DisposeRecord (temp);
1788 Table.ChangedDataRow (this, DataRowAction.Nothing);