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;
75 private XmlDataDocument.XmlDataElement mappedElement;
76 internal bool _inExpressionEvaluation = false;
83 /// This member supports the .NET Framework infrastructure and is not intended to be
84 /// used directly from your code.
86 protected internal DataRow (DataRowBuilder builder)
88 _table = builder.Table;
89 // Get the row id from the builder.
90 _rowId = builder._rowId;
92 rowError = String.Empty;
94 // create mapped XmlDataElement
95 DataSet ds = _table.DataSet;
96 if (ds != null && ds._xmlDataDocument != null)
97 mappedElement = new XmlDataDocument.XmlDataElement (this, _table.Prefix, _table.TableName, _table.Namespace, ds._xmlDataDocument);
100 internal DataRow(DataTable table,int rowId)
106 #endregion // Constructors
110 private ArrayList ColumnErrors
113 if (_columnErrors == null) {
114 _columnErrors = new ArrayList();
116 return _columnErrors;
120 _columnErrors = value;
125 /// Gets a value indicating whether there are errors in a row.
127 public bool HasErrors {
129 if (RowError != string.Empty)
132 foreach(String columnError in ColumnErrors) {
133 if (columnError != null && columnError != string.Empty) {
142 /// Gets or sets the data stored in the column specified by name.
144 public object this[string columnName] {
145 get { return this[columnName, DataRowVersion.Default]; }
147 int columnIndex = _table.Columns.IndexOf (columnName);
148 if (columnIndex == -1)
149 throw new IndexOutOfRangeException ();
150 this[columnIndex] = value;
155 /// Gets or sets the data stored in specified DataColumn
157 public object this[DataColumn column] {
160 return this[column, DataRowVersion.Default];}
162 int columnIndex = _table.Columns.IndexOf (column);
163 if (columnIndex == -1)
164 throw new ArgumentException ("The column does not belong to this table.");
165 this[columnIndex] = value;
170 /// Gets or sets the data stored in column specified by index.
172 public object this[int columnIndex] {
173 get { return this[columnIndex, DataRowVersion.Default]; }
175 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
176 throw new IndexOutOfRangeException ();
177 if (RowState == DataRowState.Deleted)
178 throw new DeletedRowInaccessibleException ();
180 DataColumn column = _table.Columns[columnIndex];
181 _table.ChangingDataColumn (this, column, value);
183 if (value == null && column.DataType != typeof(string)) {
184 throw new ArgumentException("Cannot set column " + column.ColumnName + " to be null, Please use DBNull instead");
187 CheckValue (value, column);
188 bool orginalEditing = Proposed >= 0;
189 if (!orginalEditing) {
193 column[Proposed] = value;
194 _table.ChangedDataColumn (this, column, value);
195 if (!orginalEditing) {
202 /// Gets the specified version of data stored in the named column.
204 public object this[string columnName, DataRowVersion version] {
206 int columnIndex = _table.Columns.IndexOf (columnName);
207 if (columnIndex == -1)
208 throw new IndexOutOfRangeException ();
209 return this[columnIndex, version];
214 /// Gets the specified version of data stored in the specified DataColumn.
216 public object this[DataColumn column, DataRowVersion version] {
218 if (column.Table != Table)
219 throw new ArgumentException ("The column does not belong to this table.");
220 int columnIndex = column.Ordinal;
221 return this[columnIndex, version];
226 /// Set a value for the column into the offset specified by the version.<br>
227 /// If the value is auto increment or null, necessary auto increment value
228 /// or the default value will be used.
230 internal void SetValue (int column, object value, int version)
232 DataColumn dc = Table.Columns[column];
234 if (value == null && ! dc.AutoIncrement) // set default value / auto increment
235 value = dc.DefaultValue;
237 Table.ChangingDataColumn (this, dc, value);
238 CheckValue (value, dc);
239 if ( ! dc.AutoIncrement)
240 dc [version] = value;
241 else if (_proposed >= 0 && _proposed != version) // proposed holds the AI
242 dc [version] = dc [_proposed];
246 /// Gets the data stored in the column, specified by index and version of the data to
249 public object this[int columnIndex, DataRowVersion version] {
251 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
252 throw new IndexOutOfRangeException ();
254 DataColumn column = _table.Columns[columnIndex];
255 int recordIndex = IndexFromVersion(version);
257 if (column.Expression != String.Empty) {
258 object o = column.CompiledExpression.Eval (this);
259 if (o != null && o != DBNull.Value) {
260 o = Convert.ChangeType (o, column.DataType);
262 column[recordIndex] = o;
263 return column[recordIndex];
266 return column[recordIndex];
271 /// Gets or sets all of the values for this row through an array.
273 public object[] ItemArray {
275 // Accessing deleted rows
276 if (RowState == DataRowState.Deleted)
277 throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
280 if (RowState == DataRowState.Detached)
281 // Check if datarow is removed from the table.
283 throw new RowNotInTableException(
284 "This row has been removed from a table and does not have any data."
285 +" BeginEdit() will allow creation of new data in this row.");
291 object[] items = new object[_table.Columns.Count];
293 foreach(DataColumn column in _table.Columns)
294 items[column.Ordinal] = column[index];
298 if (value.Length > _table.Columns.Count)
299 throw new ArgumentException ();
301 if (RowState == DataRowState.Deleted)
302 throw new DeletedRowInaccessibleException ();
306 DataColumnChangeEventArgs e = new DataColumnChangeEventArgs();
307 foreach(DataColumn column in _table.Columns) {
308 int i = column.Ordinal;
309 object newVal = (i < value.Length) ? value[i] : null;
314 e.Initialize(this, column, newVal);
315 CheckValue (e.ProposedValue, column);
316 _table.RaiseOnColumnChanging(e);
317 column[Proposed] = e.ProposedValue;
318 _table.RaiseOnColumnChanged(e);
326 /// Gets the current state of the row in regards to its relationship to the
327 /// DataRowCollection.
329 public DataRowState RowState {
332 if ((Original == -1) && (Current == -1))
333 return DataRowState.Detached;
334 if (Original == Current)
335 return DataRowState.Unchanged;
337 return DataRowState.Added;
339 return DataRowState.Deleted;
340 return DataRowState.Modified;
345 /// Gets the DataTable for which this row has a schema.
347 public DataTable Table {
354 /// Gets and sets index of row. This is used from
357 internal int XmlRowID {
367 /// Gets and sets index of row.
378 internal int Original
385 //Table.RecordCache[_original] = null;
386 Table.RecordCache[value] = this;
399 //Table.RecordCache[_current] = null;
400 Table.RecordCache[value] = this;
406 internal int Proposed
413 //Table.RecordCache[_proposed] = null;
414 Table.RecordCache[value] = this;
424 //FIXME?: Couldn't find a way to set the RowState when adding the DataRow
425 //to a Datatable so I added this method. Delete if there is a better way.
426 internal void AttachRow() {
427 if (Proposed != -1) {
429 Table.RecordCache.DisposeRecord(Current);
436 //FIXME?: Couldn't find a way to set the RowState when removing the DataRow
437 //from a Datatable so I added this method. Delete if there is a better way.
438 internal void DetachRow() {
440 _table.RecordCache.DisposeRecord(Proposed);
441 if (Proposed == Current) {
444 if (Proposed == Original) {
451 _table.RecordCache.DisposeRecord(Current);
452 if (Current == Original) {
459 _table.RecordCache.DisposeRecord(Original);
464 _hasParentCollection = false;
467 internal void ImportRecord(int record)
469 if (HasVersion(DataRowVersion.Proposed)) {
470 Table.RecordCache.DisposeRecord(Proposed);
475 foreach(DataColumn column in Table.Columns.AutoIncrmentColumns) {
476 column.UpdateAutoIncrementValue(column.DataContainer.GetInt64(Proposed));
481 private void CheckValue (object v, DataColumn col)
483 if (_hasParentCollection && col.ReadOnly) {
484 throw new ReadOnlyException ();
487 if (v == null || v == DBNull.Value) {
488 if (col.AllowDBNull || col.AutoIncrement || col.DefaultValue != DBNull.Value) {
492 //Constraint violations during data load is raise in DataTable EndLoad
493 this._nullConstraintViolation = true;
494 if (this.Table._duringDataLoad) {
495 this.Table._nullConstraintViolationDuringDataLoad = true;
497 _nullConstraintMessage = "Column '" + col.ColumnName + "' does not allow nulls.";
503 /// Gets or sets the custom error description for a row.
505 public string RowError {
514 internal int IndexFromVersion(DataRowVersion version)
517 case DataRowVersion.Default:
525 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.");
527 throw new DeletedRowInaccessibleException("Deleted row information cannot be accessed through the row.");
529 case DataRowVersion.Proposed:
530 return AssertValidVersionIndex(version, Proposed);
531 case DataRowVersion.Current:
532 return AssertValidVersionIndex(version, Current);
533 case DataRowVersion.Original:
534 return AssertValidVersionIndex(version, Original);
536 throw new DataException ("Version must be Original, Current, or Proposed.");
540 private int AssertValidVersionIndex(DataRowVersion version, int index) {
544 throw new VersionNotFoundException(String.Format("There is no {0} data to accces.", version));
547 internal DataRowVersion VersionFromIndex(int index) {
549 throw new ArgumentException("Index must not be negative.");
551 // the order of ifs matters
552 if (index == Current)
553 return DataRowVersion.Current;
554 if (index == Original)
555 return DataRowVersion.Original;
556 if (index == Proposed)
557 return DataRowVersion.Proposed;
559 throw new ArgumentException(String.Format("The index {0} does not belong to this row.", index) );
562 internal XmlDataDocument.XmlDataElement DataElement {
563 get { return mappedElement; }
564 set { mappedElement = value; }
567 internal void SetOriginalValue (string columnName, object val)
569 DataColumn column = _table.Columns[columnName];
570 _table.ChangingDataColumn (this, column, val);
572 if (Original < 0 || Original == Current) {
573 Original = Table.RecordCache.NewRecord();
575 CheckValue (val, column);
576 column[Original] = val;
580 /// Commits all the changes made to this row since the last time AcceptChanges was
583 public void AcceptChanges ()
585 EndEdit(); // in case it hasn't been called
587 _table.ChangingDataRow (this, DataRowAction.Commit);
588 CheckChildRows(DataRowAction.Commit);
590 case DataRowState.Unchanged:
592 case DataRowState.Added:
593 case DataRowState.Modified:
594 int original = Original;
595 DataRowState oldState = RowState;
597 Table.RecordCache.DisposeRecord(Original);
600 foreach (Index index in Table.Indexes)
601 index.Update(this, original, DataRowVersion.Original, oldState);
603 case DataRowState.Deleted:
604 Table.DeleteRowFromIndexes(this);
605 _table.Rows.RemoveInternal (this);
608 case DataRowState.Detached:
609 throw new RowNotInTableException("Cannot perform this operation on a row not in the table.");
612 _table.ChangedDataRow (this, DataRowAction.Commit);
616 /// Begins an edit operation on a DataRow object.
619 [EditorBrowsable (EditorBrowsableState.Advanced)]
621 public void BeginEdit ()
623 if (_inChangingEvent)
624 throw new InRowChangingEventException("Cannot call BeginEdit inside an OnRowChanging event.");
625 if (RowState == DataRowState.Deleted)
626 throw new DeletedRowInaccessibleException ();
628 if (!HasVersion (DataRowVersion.Proposed)) {
629 Proposed = Table.RecordCache.NewRecord();
630 int from = HasVersion(DataRowVersion.Current) ? Current : Table.DefaultValuesRowIndex;
631 for(int i = 0; i < Table.Columns.Count; i++){
632 DataColumn column = Table.Columns[i];
633 column.DataContainer.CopyValue(from,Proposed);
639 /// Cancels the current edit on the row.
642 [EditorBrowsable (EditorBrowsableState.Advanced)]
644 public void CancelEdit ()
646 if (_inChangingEvent) {
647 throw new InRowChangingEventException("Cannot call CancelEdit inside an OnRowChanging event.");
650 if (HasVersion (DataRowVersion.Proposed)) {
651 int oldRecord = Proposed;
652 DataRowState oldState = RowState;
653 Table.RecordCache.DisposeRecord(Proposed);
656 foreach(Index index in Table.Indexes)
657 index.Update(this,oldRecord, DataRowVersion.Proposed, oldState);
662 /// Clears the errors for the row, including the RowError and errors set with
665 public void ClearErrors ()
667 rowError = String.Empty;
668 ColumnErrors.Clear();
672 /// Deletes the DataRow.
674 public void Delete ()
676 _table.DeletingDataRow(this, DataRowAction.Delete);
678 case DataRowState.Added:
679 // check what to do with child rows
680 CheckChildRows(DataRowAction.Delete);
681 _table.DeleteRowFromIndexes (this);
682 Table.Rows.RemoveInternal (this);
684 // if row was in Added state we move it to Detached.
687 case DataRowState.Deleted:
688 case DataRowState.Detached:
691 // check what to do with child rows
692 CheckChildRows(DataRowAction.Delete);
696 int current = Current;
697 DataRowState oldState = RowState;
698 if (Current != Original) {
699 _table.RecordCache.DisposeRecord(Current);
702 foreach(Index index in Table.Indexes)
703 index.Update(this, current, DataRowVersion.Current, oldState);
705 _table.DeletedDataRow(this, DataRowAction.Delete);
708 // check the child rows of this row before deleting the row.
709 private void CheckChildRows(DataRowAction action)
712 // in this method we find the row that this row is in a relation with them.
713 // in shortly we find all child rows of this row.
714 // then we function according to the DeleteRule of the foriegnkey.
717 // 1. find if this row is attached to dataset.
718 // 2. find if EnforceConstraints is true.
719 // 3. find if there are any constraint on the table that the row is in.
720 if (_table.DataSet != null && _table.DataSet.EnforceConstraints && _table.Constraints.Count > 0)
722 foreach (DataTable table in _table.DataSet.Tables)
724 // loop on all ForeignKeyConstrain of the table.
725 foreach (Constraint constraint in table.Constraints) {
726 if (constraint is ForeignKeyConstraint) {
727 ForeignKeyConstraint fk = (ForeignKeyConstraint) constraint;
728 if (fk.RelatedTable == _table) {
730 case DataRowAction.Delete:
731 CheckChildRows(fk, action, fk.DeleteRule);
733 case DataRowAction.Commit:
734 case DataRowAction.Rollback:
735 if (fk.AcceptRejectRule != AcceptRejectRule.None)
736 CheckChildRows(fk, action, Rule.Cascade);
739 CheckChildRows(fk, action, fk.UpdateRule);
749 private void CheckChildRows(ForeignKeyConstraint fkc, DataRowAction action, Rule rule)
751 DataRow[] childRows = GetChildRows(fkc, DataRowVersion.Current);
754 case Rule.Cascade: // delete or change all relted rows.
755 if (childRows != null)
757 for (int j = 0; j < childRows.Length; j++)
759 // if action is delete we delete all child rows
761 case DataRowAction.Delete: {
762 if (childRows[j].RowState != DataRowState.Deleted)
763 childRows[j].Delete();
767 // if action is change we change the values in the child row
768 case DataRowAction.Change: {
769 // change only the values in the key columns
770 // set the childcolumn value to the new parent row value
771 for (int k = 0; k < fkc.Columns.Length; k++)
772 if (!fkc.RelatedColumns [k].DataContainer [Original].Equals (fkc.RelatedColumns [k].DataContainer [Proposed]))
773 childRows[j][fkc.Columns[k]] = this[fkc.RelatedColumns[k], DataRowVersion.Proposed];
778 case DataRowAction.Rollback: {
779 if (childRows[j].RowState != DataRowState.Unchanged)
780 childRows[j].RejectChanges ();
787 case Rule.None: // throw an exception if there are any child rows.
788 if (childRows != null)
790 for (int j = 0; j < childRows.Length; j++)
792 if (childRows[j].RowState != DataRowState.Deleted)
794 string changeStr = "Cannot change this row because constraints are enforced on relation " + fkc.ConstraintName +", and changing this row will strand child rows.";
795 string delStr = "Cannot delete this row because constraints are enforced on relation " + fkc.ConstraintName +", and deleting this row will strand child rows.";
796 string message = action == DataRowAction.Delete ? delStr : changeStr;
797 throw new InvalidConstraintException(message);
802 case Rule.SetDefault: // set the values in the child rows to the defult value of the columns.
803 if (childRows != null && childRows.Length > 0) {
804 int defaultValuesRowIndex = childRows[0].Table.DefaultValuesRowIndex;
805 foreach(DataRow childRow in childRows) {
806 if (childRow.RowState != DataRowState.Deleted) {
807 int defaultIdx = childRow.IndexFromVersion(DataRowVersion.Default);
808 foreach(DataColumn column in fkc.Columns) {
809 column.DataContainer.CopyValue(defaultValuesRowIndex,defaultIdx);
815 case Rule.SetNull: // set the values in the child row to null.
816 if (childRows != null)
818 for (int j = 0; j < childRows.Length; j++)
820 DataRow child = childRows[j];
821 if (childRows[j].RowState != DataRowState.Deleted)
823 // set only the key columns to DBNull
824 for (int k = 0; k < fkc.Columns.Length; k++)
825 child.SetNull(fkc.Columns[k]);
835 /// Ends the edit occurring on the row.
838 [EditorBrowsable (EditorBrowsableState.Advanced)]
840 public void EndEdit ()
842 if (_inChangingEvent)
843 throw new InRowChangingEventException("Cannot call EndEdit inside an OnRowChanging event.");
845 if (RowState == DataRowState.Detached)
848 if (HasVersion (DataRowVersion.Proposed))
850 CheckReadOnlyStatus();
852 _inChangingEvent = true;
855 _table.ChangingDataRow(this, DataRowAction.Change);
859 _inChangingEvent = false;
862 DataRowState oldState = RowState;
864 int oldRecord = Current;
868 if (!Table._duringDataLoad) {
869 foreach(Index index in Table.Indexes) {
870 index.Update(this,oldRecord, DataRowVersion.Current, oldState);
877 // restore previous state to let the cascade update to find the rows
881 CheckChildRows(DataRowAction.Change);
888 int proposed = Proposed >= 0 ? Proposed : Current;
890 if (!Table._duringDataLoad) {
891 foreach(Index index in Table.Indexes) {
892 index.Update(this,proposed, DataRowVersion.Current, RowState);
898 if (Original != oldRecord) {
899 Table.RecordCache.DisposeRecord(oldRecord);
902 // Note : row state must not be changed before all the job on indexes finished,
903 // since the indexes works with recods rather than with rows and the decision
904 // which of row records to choose depends on row state.
905 _table.ChangedDataRow(this, DataRowAction.Change);
910 /// Gets the child rows of this DataRow using the specified DataRelation.
912 public DataRow[] GetChildRows (DataRelation relation)
914 return GetChildRows (relation, DataRowVersion.Default);
918 /// Gets the child rows of a DataRow using the specified RelationName of a
921 public DataRow[] GetChildRows (string relationName)
923 return GetChildRows (Table.DataSet.Relations[relationName]);
927 /// Gets the child rows of a DataRow using the specified DataRelation, and
930 public DataRow[] GetChildRows (DataRelation relation, DataRowVersion version)
932 if (relation == null)
933 return Table.NewRowArray(0);
935 if (this.Table == null)
936 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.");
938 if (relation.DataSet != this.Table.DataSet)
939 throw new ArgumentException();
941 if (_table != relation.ParentTable)
942 throw new InvalidConstraintException ("GetChildRow requires a row whose Table is " + relation.ParentTable + ", but the specified row's table is " + _table);
944 if (relation.ChildKeyConstraint != null)
945 return GetChildRows (relation.ChildKeyConstraint, version);
947 ArrayList rows = new ArrayList();
948 DataColumn[] parentColumns = relation.ParentColumns;
949 DataColumn[] childColumns = relation.ChildColumns;
950 int numColumn = parentColumns.Length;
951 DataRow[] result = null;
953 int versionIndex = IndexFromVersion(version);
954 int tmpRecord = relation.ChildTable.RecordCache.NewRecord();
957 for (int i = 0; i < numColumn; i++) {
958 // according to MSDN: the DataType value for both columns must be identical.
959 childColumns[i].DataContainer.CopyValue(parentColumns[i].DataContainer, versionIndex, tmpRecord);
962 Index index = relation.ChildTable.FindIndex(childColumns);
965 int[] records = index.FindAll(tmpRecord);
966 result = relation.ChildTable.NewRowArray(records.Length);
967 for(int i=0; i < records.Length; i++) {
968 result[i] = relation.ChildTable.RecordCache[records[i]];
972 foreach (DataRow row in relation.ChildTable.Rows) {
973 bool allColumnsMatch = false;
974 if (row.HasVersion(DataRowVersion.Default)) {
975 allColumnsMatch = true;
976 int childIndex = row.IndexFromVersion(DataRowVersion.Default);
977 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) {
978 if (childColumns[columnCnt].DataContainer.CompareValues(childIndex, tmpRecord) != 0) {
979 allColumnsMatch = false;
984 if (allColumnsMatch) rows.Add(row);
986 result = relation.ChildTable.NewRowArray(rows.Count);
987 rows.CopyTo(result, 0);
991 relation.ChildTable.RecordCache.DisposeRecord(tmpRecord);
998 /// Gets the child rows of a DataRow using the specified RelationName of a
999 /// DataRelation, and DataRowVersion.
1001 public DataRow[] GetChildRows (string relationName, DataRowVersion version)
1003 return GetChildRows (Table.DataSet.Relations[relationName], version);
1006 private DataRow[] GetChildRows (ForeignKeyConstraint fkc, DataRowVersion version)
1008 ArrayList rows = new ArrayList();
1009 DataColumn[] parentColumns = fkc.RelatedColumns;
1010 DataColumn[] childColumns = fkc.Columns;
1011 int numColumn = parentColumns.Length;
1013 Index index = fkc.Index;
1015 int curIndex = IndexFromVersion(version);
1016 int tmpRecord = fkc.Table.RecordCache.NewRecord();
1017 for (int i = 0; i < numColumn; i++) {
1018 // according to MSDN: the DataType value for both columns must be identical.
1019 childColumns[i].DataContainer.CopyValue(parentColumns[i].DataContainer, curIndex, tmpRecord);
1023 if (index != null) {
1024 // get the child rows from the index
1025 int[] childRecords = index.FindAll(tmpRecord);
1026 for (int i = 0; i < childRecords.Length; i++) {
1027 rows.Add (childColumns[i].Table.RecordCache[childRecords[i]]);
1030 else { // if there is no index we search manualy.
1031 foreach (DataRow row in fkc.Table.Rows) {
1032 bool allColumnsMatch = false;
1033 if (row.HasVersion(DataRowVersion.Default)) {
1034 allColumnsMatch = true;
1035 int childIndex = row.IndexFromVersion(DataRowVersion.Default);
1036 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) {
1037 if (childColumns[columnCnt].DataContainer.CompareValues(childIndex, tmpRecord) != 0) {
1038 allColumnsMatch = false;
1043 if (allColumnsMatch) {
1050 fkc.Table.RecordCache.DisposeRecord(tmpRecord);
1053 DataRow[] result = fkc.Table.NewRowArray(rows.Count);
1054 rows.CopyTo(result, 0);
1059 /// Gets the error description of the specified DataColumn.
1061 public string GetColumnError (DataColumn column)
1064 throw new ArgumentNullException("column");
1066 int index = _table.Columns.IndexOf(column);
1068 throw new ArgumentException(String.Format("Column '{0}' does not belong to table {1}.", column.ColumnName, Table.TableName));
1070 return GetColumnError (index);
1074 /// Gets the error description for the column specified by index.
1076 public string GetColumnError (int columnIndex)
1078 if (columnIndex < 0 || columnIndex >= Table.Columns.Count)
1079 throw new IndexOutOfRangeException ();
1081 string retVal = null;
1082 if (columnIndex < ColumnErrors.Count) {
1083 retVal = (String) ColumnErrors[columnIndex];
1085 return (retVal != null) ? retVal : String.Empty;
1089 /// Gets the error description for the column, specified by name.
1091 public string GetColumnError (string columnName)
1093 return GetColumnError (_table.Columns.IndexOf(columnName));
1097 /// Gets an array of columns that have errors.
1099 public DataColumn[] GetColumnsInError ()
1101 ArrayList dataColumns = new ArrayList ();
1103 int columnOrdinal = 0;
1104 foreach(String columnError in ColumnErrors) {
1105 if (columnError != null && columnError != String.Empty) {
1106 dataColumns.Add (_table.Columns[columnOrdinal]);
1111 return (DataColumn[])(dataColumns.ToArray (typeof(DataColumn)));
1115 /// Gets the parent row of a DataRow using the specified DataRelation.
1117 public DataRow GetParentRow (DataRelation relation)
1119 return GetParentRow (relation, DataRowVersion.Default);
1123 /// Gets the parent row of a DataRow using the specified RelationName of a
1126 public DataRow GetParentRow (string relationName)
1128 return GetParentRow (relationName, DataRowVersion.Default);
1132 /// Gets the parent row of a DataRow using the specified DataRelation, and
1135 public DataRow GetParentRow (DataRelation relation, DataRowVersion version)
1137 DataRow[] rows = GetParentRows(relation, version);
1138 if (rows.Length == 0) return null;
1143 /// Gets the parent row of a DataRow using the specified RelationName of a
1144 /// DataRelation, and DataRowVersion.
1146 public DataRow GetParentRow (string relationName, DataRowVersion version)
1148 return GetParentRow (Table.DataSet.Relations[relationName], version);
1152 /// Gets the parent rows of a DataRow using the specified DataRelation.
1154 public DataRow[] GetParentRows (DataRelation relation)
1156 return GetParentRows (relation, DataRowVersion.Default);
1160 /// Gets the parent rows of a DataRow using the specified RelationName of a
1163 public DataRow[] GetParentRows (string relationName)
1165 return GetParentRows (relationName, DataRowVersion.Default);
1169 /// Gets the parent rows of a DataRow using the specified DataRelation, and
1172 public DataRow[] GetParentRows (DataRelation relation, DataRowVersion version)
1174 // TODO: Caching for better preformance
1175 if (relation == null)
1176 return Table.NewRowArray(0);
1178 if (this.Table == null)
1179 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.");
1181 if (relation.DataSet != this.Table.DataSet)
1182 throw new ArgumentException();
1184 if (_table != relation.ChildTable)
1185 throw new InvalidConstraintException ("GetParentRows requires a row whose Table is " + relation.ChildTable + ", but the specified row's table is " + _table);
1187 ArrayList rows = new ArrayList();
1188 DataColumn[] parentColumns = relation.ParentColumns;
1189 DataColumn[] childColumns = relation.ChildColumns;
1190 int numColumn = parentColumns.Length;
1192 int curIndex = IndexFromVersion(version);
1193 int tmpRecord = relation.ParentTable.RecordCache.NewRecord();
1194 for (int i = 0; i < numColumn; i++) {
1195 // according to MSDN: the DataType value for both columns must be identical.
1196 parentColumns[i].DataContainer.CopyValue(childColumns[i].DataContainer, curIndex, tmpRecord);
1200 Index index = relation.ParentTable.FindIndex(parentColumns);
1201 if (index != null) { // get the parent rows from the index
1202 int[] parentRecords = index.FindAll(tmpRecord);
1203 for (int i = 0; i < parentRecords.Length; i++) {
1204 rows.Add (parentColumns[i].Table.RecordCache[parentRecords[i]]);
1207 else { // no index so we have to search manualy.
1208 foreach (DataRow row in relation.ParentTable.Rows) {
1209 bool allColumnsMatch = false;
1210 if (row.HasVersion(DataRowVersion.Default)) {
1211 allColumnsMatch = true;
1212 int parentIndex = row.IndexFromVersion(DataRowVersion.Default);
1213 for (int columnCnt = 0; columnCnt < numColumn; columnCnt++) {
1214 if (parentColumns[columnCnt].DataContainer.CompareValues(parentIndex, tmpRecord) != 0) {
1215 allColumnsMatch = false;
1220 if (allColumnsMatch) {
1227 relation.ParentTable.RecordCache.DisposeRecord(tmpRecord);
1230 DataRow[] result = relation.ParentTable.NewRowArray(rows.Count);
1231 rows.CopyTo(result, 0);
1236 /// Gets the parent rows of a DataRow using the specified RelationName of a
1237 /// DataRelation, and DataRowVersion.
1239 public DataRow[] GetParentRows (string relationName, DataRowVersion version)
1241 return GetParentRows (Table.DataSet.Relations[relationName], version);
1245 /// Gets a value indicating whether a specified version exists.
1247 public bool HasVersion (DataRowVersion version)
1250 case DataRowVersion.Default:
1251 return (Proposed >= 0 || Current >= 0);
1252 case DataRowVersion.Proposed:
1253 return Proposed >= 0;
1254 case DataRowVersion.Current:
1255 return Current >= 0;
1256 case DataRowVersion.Original:
1257 return Original >= 0;
1259 return IndexFromVersion(version) >= 0;
1264 /// Gets a value indicating whether the specified DataColumn contains a null value.
1266 public bool IsNull (DataColumn column)
1268 return IsNull(column, DataRowVersion.Default);
1272 /// Gets a value indicating whether the column at the specified index contains a null
1275 public bool IsNull (int columnIndex)
1277 return IsNull(Table.Columns[columnIndex]);
1281 /// Gets a value indicating whether the named column contains a null value.
1283 public bool IsNull (string columnName)
1285 return IsNull(Table.Columns[columnName]);
1289 /// Gets a value indicating whether the specified DataColumn and DataRowVersion
1290 /// contains a null value.
1292 public bool IsNull (DataColumn column, DataRowVersion version)
1294 object o = this[column,version];
1295 return column.DataContainer.IsNull(IndexFromVersion(version));
1299 /// Returns a value indicating whether all of the row columns specified contain a null value.
1301 internal bool IsNullColumns(DataColumn[] columns)
1303 bool allNull = true;
1304 for (int i = 0; i < columns.Length; i++)
1306 if (!IsNull(columns[i]))
1316 /// Rejects all changes made to the row since AcceptChanges was last called.
1318 public void RejectChanges ()
1320 if (RowState == DataRowState.Detached)
1321 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.");
1322 // If original is null, then nothing has happened since AcceptChanges
1323 // was last called. We have no "original" to go back to.
1325 _table.ChangedDataRow (this, DataRowAction.Rollback);
1328 //TODO : Need to Verify the constraints..
1330 case DataRowState.Added:
1331 _table.DeleteRowFromIndexes (this);
1332 _table.Rows.RemoveInternal (this);
1335 case DataRowState.Modified:
1336 Table.RecordCache.DisposeRecord (Current);
1337 CheckChildRows (DataRowAction.Rollback);
1338 Table.DeleteRowFromIndexes(this);
1341 case DataRowState.Deleted:
1342 CheckChildRows (DataRowAction.Rollback);
1343 Table.DeleteRowFromIndexes(this);
1350 /// Sets the error description for a column specified as a DataColumn.
1352 public void SetColumnError (DataColumn column, string error)
1354 SetColumnError (_table.Columns.IndexOf (column), error);
1358 /// Sets the error description for a column specified by index.
1360 public void SetColumnError (int columnIndex, string error)
1362 if (columnIndex < 0 || columnIndex >= Table.Columns.Count)
1363 throw new IndexOutOfRangeException ();
1365 while(ColumnErrors.Count < columnIndex) {
1366 ColumnErrors.Add(null);
1368 ColumnErrors.Add(error);
1372 /// Sets the error description for a column specified by name.
1374 public void SetColumnError (string columnName, string error)
1376 SetColumnError (_table.Columns.IndexOf (columnName), error);
1380 /// Sets the value of the specified DataColumn to a null value.
1382 protected void SetNull (DataColumn column)
1384 this[column] = DBNull.Value;
1388 /// Sets the parent row of a DataRow with specified new parent DataRow.
1390 public void SetParentRow (DataRow parentRow)
1392 SetParentRow(parentRow, null);
1396 /// Sets the parent row of a DataRow with specified new parent DataRow and
1399 public void SetParentRow (DataRow parentRow, DataRelation relation)
1401 if (_table == null || parentRow.Table == null)
1402 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.");
1404 if (parentRow != null && _table.DataSet != parentRow.Table.DataSet)
1405 throw new ArgumentException();
1407 if (RowState == DataRowState.Detached && !HasVersion(DataRowVersion.Default)) {
1408 // 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
1409 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.");
1414 IEnumerable relations;
1415 if (relation == null) {
1416 relations = _table.ParentRelations;
1419 relations = new DataRelation[] { relation };
1422 foreach (DataRelation rel in relations)
1424 DataColumn[] childCols = rel.ChildColumns;
1425 DataColumn[] parentCols = rel.ParentColumns;
1427 for (int i = 0; i < parentCols.Length; i++)
1429 if (parentRow == null) {
1430 childCols[i].DataContainer[Proposed] = DBNull.Value;
1433 int defaultIdx = parentRow.IndexFromVersion(DataRowVersion.Default);
1434 childCols[i].DataContainer.CopyValue(parentCols[i].DataContainer,defaultIdx,Proposed);
1443 //Copy all values of this DataRow to the row parameter.
1444 internal void CopyValuesToRow(DataRow row)
1447 throw new ArgumentNullException("row");
1449 throw new ArgumentException("'row' is the same as this object");
1451 // create target records if missing.
1452 if (HasVersion(DataRowVersion.Original)) {
1453 if (row.Original < 0)
1454 row.Original = row.Table.RecordCache.NewRecord();
1455 else if (row.Original == row.Current) {
1456 row.Original = row.Table.RecordCache.NewRecord();
1457 row.Table.RecordCache.CopyRecord (row.Table, row.Current, row.Original);
1460 if (row.Original > 0) {
1461 if (row.Original != row.Current)
1462 row.Table.RecordCache.DisposeRecord(row.Original);
1467 if (HasVersion(DataRowVersion.Current)) {
1468 if (Current == Original) {
1469 if (row.Current >= 0)
1470 row.Table.RecordCache.DisposeRecord(row.Current);
1471 row.Current = row.Original;
1473 if (row.Current < 0)
1474 row.Current = row.Table.RecordCache.NewRecord();
1477 if (row.Current > 0) {
1478 row.Table.RecordCache.DisposeRecord(row.Current);
1483 if (HasVersion(DataRowVersion.Proposed)) {
1484 if (row.Proposed < 0)
1485 row.Proposed = row.Table.RecordCache.NewRecord();
1487 if (row.Proposed > 0) {
1488 row.Table.RecordCache.DisposeRecord(row.Proposed);
1493 // copy source record values to target records
1494 foreach(DataColumn column in Table.Columns) {
1495 DataColumn targetColumn = row.Table.Columns[column.ColumnName];
1496 //if a column with the same name exists in both rows copy the values
1497 if(targetColumn != null) {
1498 if (HasVersion(DataRowVersion.Original)) {
1499 object val = column[Original];
1500 row.CheckValue(val, targetColumn);
1501 targetColumn[row.Original] = val;
1504 if (HasVersion(DataRowVersion.Current)
1505 && Current != Original) {
1506 object val = column[Current];
1507 row.CheckValue(val, targetColumn);
1508 targetColumn[row.Current] = val;
1511 if (HasVersion(DataRowVersion.Proposed)) {
1512 object val = column[row.Proposed];
1513 row.CheckValue(val, targetColumn);
1514 targetColumn[row.Proposed] = val;
1523 //Merge all values of this DataRow to the row parameter according to merge rules.
1524 internal void MergeValuesToRow(DataRow row, bool preserveChanges)
1527 throw new ArgumentNullException("row");
1529 throw new ArgumentException("'row' is the same as this object");
1531 // Original values are anyway copied
1532 if (HasVersion(DataRowVersion.Original)) {
1533 if (row.Original < 0)
1534 row.Original = row.Table.RecordCache.NewRecord();
1535 else if (row.Original == row.Current
1536 && !(Original == Current && ! preserveChanges)) {
1537 row.Original = row.Table.RecordCache.NewRecord();
1538 row.Table.RecordCache.CopyRecord (row.Table, row.Current, row.Original);
1541 if (row.Original == row.Current) { // if target has same current, better create new original
1542 row.Original = row.Table.RecordCache.NewRecord();
1543 row.Table.RecordCache.CopyRecord (row.Table, row.Current, row.Original);
1547 // if i have current, push all
1548 if (HasVersion(DataRowVersion.Current)) {
1549 if (! preserveChanges && row.Current < 0)
1550 row.Current = row.Table.RecordCache.NewRecord();
1552 if (row.Current > 0 && ! preserveChanges) {
1553 row.Table.RecordCache.DisposeRecord(row.Current);
1558 // copy source record values to target records
1559 foreach(DataColumn column in Table.Columns) {
1560 DataColumn targetColumn = row.Table.Columns[column.ColumnName];
1561 //if a column with the same name exists in both rows copy the values
1562 if(targetColumn != null) {
1563 if (HasVersion(DataRowVersion.Original)) {
1564 object val = column[Original];
1565 row.CheckValue(val, targetColumn);
1566 targetColumn[row.Original] = val;
1569 if (HasVersion(DataRowVersion.Current)
1570 && !preserveChanges) {
1571 object val = column[Current];
1572 row.CheckValue(val, targetColumn);
1573 targetColumn[row.Current] = val;
1582 internal void CopyErrors(DataRow row)
1584 row.RowError = RowError;
1585 DataColumn[] errorColumns = GetColumnsInError();
1586 foreach(DataColumn col in errorColumns) {
1587 DataColumn targetColumn = row.Table.Columns[col.ColumnName];
1588 row.SetColumnError(targetColumn,GetColumnError(col));
1592 internal bool IsRowChanged(DataRowState rowState) {
1593 if((RowState & rowState) != 0)
1596 //we need to find if child rows of this row changed.
1597 //if yes - we should return true
1599 // if the rowState is deleted we should get the original version of the row
1600 // else - we should get the current version of the row.
1601 DataRowVersion version = (rowState == DataRowState.Deleted) ? DataRowVersion.Original : DataRowVersion.Current;
1602 int count = Table.ChildRelations.Count;
1603 for (int i = 0; i < count; i++){
1604 DataRelation rel = Table.ChildRelations[i];
1605 DataRow[] childRows = GetChildRows(rel, version);
1606 for (int j = 0; j < childRows.Length; j++){
1607 if (childRows[j].IsRowChanged(rowState))
1615 internal bool HasParentCollection
1619 return _hasParentCollection;
1623 _hasParentCollection = value;
1627 internal void Validate() {
1628 Table.AddRowToIndexes(this);
1629 AssertConstraints();
1632 void AssertConstraints() {
1633 if (Table == null || Table._duringDataLoad)
1636 if (Table.DataSet != null && !Table.DataSet.EnforceConstraints)
1638 foreach(DataColumn column in Table.Columns) {
1639 if (!column.AllowDBNull && IsNull(column)) {
1640 throw new NoNullAllowedException(_nullConstraintMessage);
1644 foreach(Constraint constraint in Table.Constraints) {
1646 constraint.AssertConstraint(this);
1648 catch(Exception e) {
1649 Table.DeleteRowFromIndexes(this);
1655 internal void CheckNullConstraints()
1657 if (_nullConstraintViolation) {
1658 if (HasVersion(DataRowVersion.Proposed)) {
1659 foreach(DataColumn column in Table.Columns) {
1660 if (IsNull(column) && !column.AllowDBNull) {
1661 throw new NoNullAllowedException(_nullConstraintMessage);
1665 _nullConstraintViolation = false;
1669 internal void CheckReadOnlyStatus() {
1670 int defaultIdx = IndexFromVersion(DataRowVersion.Default);
1671 foreach(DataColumn column in Table.Columns) {
1672 if ((column.DataContainer.CompareValues(defaultIdx,Proposed) != 0) && column.ReadOnly) {
1673 throw new ReadOnlyException();
1678 #endregion // Methods
1682 /// This method loads a given value into the existing row affecting versions,
1683 /// state based on the LoadOption. The matrix of changes for this method are as
1684 /// mentioned in the DataTable.Load (IDataReader, LoadOption) method.
1686 internal void Load (object [] values, LoadOption loadOption)
1691 if (loadOption == LoadOption.OverwriteChanges
1692 || (loadOption == LoadOption.PreserveChanges
1693 && RowState == DataRowState.Unchanged)) {
1694 Table.ChangingDataRow (this, DataRowAction.ChangeCurrentAndOriginal);
1695 temp = Table.CreateRecord (values);
1696 Table.DeleteRowFromIndexes(this);
1697 if (HasVersion (DataRowVersion.Original) && Current != Original)
1698 Table.RecordCache.DisposeRecord (Original);
1701 if (HasVersion (DataRowVersion.Current))
1702 Table.RecordCache.DisposeRecord (Current);
1704 Table.AddRowToIndexes(this);
1705 Table.ChangedDataRow (this, DataRowAction.ChangeCurrentAndOriginal);
1709 if (loadOption == LoadOption.PreserveChanges) {
1710 Table.ChangingDataRow (this, DataRowAction.ChangeOriginal);
1711 temp = Table.CreateRecord (values);
1712 if (HasVersion (DataRowVersion.Original) && Current != Original)
1713 Table.RecordCache.DisposeRecord (Original);
1715 Table.ChangedDataRow (this, DataRowAction.ChangeOriginal);
1720 if (RowState != DataRowState.Deleted) {
1721 int rindex = HasVersion (DataRowVersion.Proposed) ? Proposed : Current;
1722 temp = Table.CreateRecord (values);
1723 if (RowState == DataRowState.Added
1724 || Table.CompareRecords (rindex, temp) != 0) {
1725 Table.ChangingDataRow (this, DataRowAction.Change);
1726 Table.DeleteRowFromIndexes(this);
1727 if (HasVersion (DataRowVersion.Proposed)) {
1728 Table.RecordCache.DisposeRecord (Proposed);
1732 if (Original != Current)
1733 Table.RecordCache.DisposeRecord (Current);
1735 Table.AddRowToIndexes(this);
1736 Table.ChangedDataRow (this, DataRowAction.Change);
1738 Table.ChangingDataRow (this, DataRowAction.Nothing);
1739 Table.RecordCache.DisposeRecord (temp);
1740 Table.ChangedDataRow (this, DataRowAction.Nothing);