Updates referencesource to .NET 4.7
[mono-project.git] / mcs / class / referencesource / System.Data / System / Data / DataRow.cs
blobacfadee9030300ad7d99101dfb91b77bc1246396
1 //------------------------------------------------------------------------------
2 // <copyright file="DataRow.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="true" primary="false">Microsoft</owner>
7 //------------------------------------------------------------------------------
9 namespace System.Data {
10 using System;
11 using System.Collections;
12 using System.ComponentModel;
13 using System.Diagnostics;
14 using System.Globalization;
15 using System.Xml;
18 /// <devdoc>
19 /// <para>Represents a row of data in a <see cref='System.Data.DataTable'/>.</para>
20 /// </devdoc>
21 public class DataRow {
22 private readonly DataTable _table;
23 private readonly DataColumnCollection _columns;
25 internal int oldRecord = -1;
26 internal int newRecord = -1;
27 internal int tempRecord;
28 internal long _rowID = -1;
30 internal DataRowAction _action;
32 internal bool inChangingEvent;
33 internal bool inDeletingEvent;
34 internal bool inCascade;
36 private DataColumn _lastChangedColumn; // last successfully changed column
37 private int _countColumnChange; // number of columns changed during edit mode
39 private DataError error;
40 private object _element;
42 private int _rbTreeNodeId; // if row is not detached, Id used for computing index in rows collection
44 private static int _objectTypeCount; // Bid counter
45 internal readonly int ObjectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
47 /// <devdoc>
48 /// <para>
49 /// Initializes a new instance of the DataRow.
50 /// </para>
51 /// <para>
52 /// Constructs a row from the builder. Only for internal usage..
53 /// </para>
54 /// </devdoc>
55 protected internal DataRow (DataRowBuilder builder) {
56 tempRecord = builder._record;
57 _table = builder._table;
58 _columns = _table.Columns;
61 internal XmlBoundElement Element {
62 get {
63 return (XmlBoundElement) _element;
65 set {
66 _element = value;
70 internal DataColumn LastChangedColumn {
71 get { // last successfully changed column or if multiple columns changed: null
72 if (_countColumnChange != 1) {
73 return null;
75 return _lastChangedColumn;
77 set {
78 _countColumnChange++;
79 _lastChangedColumn = value;
83 internal bool HasPropertyChanged {
84 get { return (0 < _countColumnChange); }
87 internal int RBTreeNodeId {
88 get {
89 return _rbTreeNodeId;
91 set {
92 Bid.Trace("<ds.DataRow.set_RBTreeNodeId|INFO> %d#, value=%d\n", ObjectID, value);
93 _rbTreeNodeId = value;
97 /// <devdoc>
98 /// <para>Gets or sets the custom error description for a row.</para>
99 /// </devdoc>
100 public string RowError {
101 get {
102 return(error == null ? String.Empty :error.Text);
104 set {
105 Bid.Trace("<ds.DataRow.set_RowError|API> %d#, value='%ls'\n", ObjectID, value);
106 if (error == null) {
107 if (!Common.ADP.IsEmpty(value)) {
108 error = new DataError(value);
110 RowErrorChanged();
112 else if(error.Text != value) {
113 error.Text = value;
114 RowErrorChanged();
119 private void RowErrorChanged() {
120 // We don't know wich record was used by view index. try to use both.
121 if (oldRecord != -1)
122 _table.RecordChanged(oldRecord);
123 if (newRecord != -1)
124 _table.RecordChanged(newRecord);
127 internal long rowID {
128 get {
129 return _rowID;
131 set {
132 ResetLastChangedColumn();
133 _rowID = value;
137 /// <devdoc>
138 /// <para>Gets the current state of the row in regards to its relationship to the table.</para>
139 /// </devdoc>
140 public DataRowState RowState {
141 get {
143 if (oldRecord == -1 && newRecord == -1)
144 state = DataRowState.Detached; // 2
145 else if (oldRecord == newRecord)
146 state = DataRowState.Unchanged; // 2
147 else if (oldRecord == -1)
148 state = DataRowState.Added; // 4
149 else if (newRecord == -1)
150 state = DataRowState.Deleted; // 4
151 else
152 state = DataRowState.Modified; // 4
154 if (oldRecord == newRecord) {
155 if (oldRecord == -1) {
156 return DataRowState.Detached; // 2
158 if (0 < _columns.ColumnsImplementingIChangeTrackingCount) {
159 foreach(DataColumn dc in _columns.ColumnsImplementingIChangeTracking) {
160 object value = this[dc];
161 if ((DBNull.Value != value) && ((IChangeTracking)value).IsChanged) {
162 return DataRowState.Modified; // 3 + _columns.columnsImplementingIChangeTracking.Count
166 return DataRowState.Unchanged; // 3
168 else if (oldRecord == -1) {
169 return DataRowState.Added; // 2
171 else if (newRecord == -1) {
172 return DataRowState.Deleted; // 3
174 return DataRowState.Modified; // 3
179 /// <devdoc>
180 /// <para>Gets the <see cref='System.Data.DataTable'/>
181 /// for which this row has a schema.</para>
182 /// </devdoc>
183 public DataTable Table {
184 get {
185 return _table;
189 /// <devdoc>
190 /// <para>Gets or sets the data stored in the column specified by index.</para>
191 /// </devdoc>
192 public object this[int columnIndex] {
193 get {
194 DataColumn column = _columns[columnIndex];
195 int record = GetDefaultRecord();
196 _table.recordManager.VerifyRecord(record, this);
197 VerifyValueFromStorage(column, DataRowVersion.Default, column[record]);
198 return column[record];
200 set {
201 DataColumn column = _columns[columnIndex];
202 this[column] = value;
206 internal void CheckForLoops(DataRelation rel){
207 // don't check for loops in the diffgram
208 // because there may be some holes in the rowCollection
209 // and index creation may fail. The check will be done
210 // after all the loading is done _and_ we are sure there
211 // are no holes in the collection.
212 if (_table.fInLoadDiffgram || (_table.DataSet != null && _table.DataSet.fInLoadDiffgram))
213 return;
214 int count = _table.Rows.Count, i = 0;
215 // need to optimize this for count > 100
216 DataRow parent = this.GetParentRow(rel);
217 while (parent != null) {
218 if ((parent == this) || (i>count))
219 throw ExceptionBuilder.NestedCircular(_table.TableName);
220 i++;
221 parent = parent.GetParentRow(rel);
225 internal int GetNestedParentCount() {
226 int count = 0;
227 DataRelation[] nestedParentRelations = _table.NestedParentRelations;
228 foreach(DataRelation rel in nestedParentRelations) {
229 if (rel == null) // don't like this but done for backward code compatability
230 continue;
231 if (rel.ParentTable == _table) // self-nested table
232 this.CheckForLoops(rel);
233 DataRow row = this.GetParentRow(rel);
234 if (row != null) {
235 count++;
238 return count ;
239 // Rule 1: At all times, only ONE FK "(in a row) can be non-Null
240 // we wont allow a row to have multiple parents, as we cant handle it , also in diffgram
243 /// <devdoc>
244 /// <para>Gets or sets the data stored in the column specified by
245 /// name.</para>
246 /// </devdoc>
247 public object this[string columnName] {
248 get {
249 DataColumn column = GetDataColumn(columnName);
250 int record = GetDefaultRecord();
251 _table.recordManager.VerifyRecord(record, this);
252 VerifyValueFromStorage(column, DataRowVersion.Default, column[record]);
253 return column[record];
255 set {
256 DataColumn column = GetDataColumn(columnName);
257 this[column] = value;
261 /// <devdoc>
262 /// <para>Gets or sets
263 /// the data stored in the specified <see cref='System.Data.DataColumn'/>.</para>
264 /// </devdoc>
265 public object this[DataColumn column] {
266 get {
267 CheckColumn(column);
268 int record = GetDefaultRecord();
269 _table.recordManager.VerifyRecord(record, this);
270 VerifyValueFromStorage(column, DataRowVersion.Default, column[record]);
271 return column[record];
273 set {
274 CheckColumn(column);
275 if (inChangingEvent) {
276 throw ExceptionBuilder.EditInRowChanging();
278 if ((-1 != rowID) && column.ReadOnly) {
279 throw ExceptionBuilder.ReadOnly(column.ColumnName);
282 // allow users to tailor the proposed value, or throw an exception.
283 // note we intentionally do not try/catch this event.
284 // note: we also allow user to do anything at this point
285 // infinite loops are possible if user calls Item or ItemArray during the event
286 DataColumnChangeEventArgs e = null;
287 if (_table.NeedColumnChangeEvents) {
288 e = new DataColumnChangeEventArgs(this, column, value);
289 _table.OnColumnChanging(e);
292 if (column.Table != _table) {
293 // user removed column from table during OnColumnChanging event
294 throw ExceptionBuilder.ColumnNotInTheTable(column.ColumnName, _table.TableName);
296 if ((-1 != rowID) && column.ReadOnly) {
297 // user adds row to table during OnColumnChanging event
298 throw ExceptionBuilder.ReadOnly(column.ColumnName);
301 object proposed = ((null != e) ? e.ProposedValue : value);
302 if (null == proposed) {
303 if (column.IsValueType) { // WebData 105963
304 throw ExceptionBuilder.CannotSetToNull(column);
306 proposed = DBNull.Value;
309 bool immediate = BeginEditInternal();
310 try {
311 int record = GetProposedRecordNo();
312 _table.recordManager.VerifyRecord(record, this);
313 column[record] = proposed;
315 catch (Exception e1){
317 if (Common.ADP.IsCatchableOrSecurityExceptionType(e1)) {
318 if (immediate) {
319 Debug.Assert(!inChangingEvent, "how are we in a changing event to cancel?");
320 Debug.Assert(-1 != tempRecord, "how no propsed record to cancel?");
321 CancelEdit(); // WebData 107154
324 throw;
326 LastChangedColumn = column;
328 // note: we intentionally do not try/catch this event.
329 // infinite loops are possible if user calls Item or ItemArray during the event
330 if (null != e) {
331 _table.OnColumnChanged(e); // user may call CancelEdit or EndEdit
334 if (immediate) {
335 Debug.Assert(!inChangingEvent, "how are we in a changing event to end?");
336 EndEdit();
341 /// <devdoc>
342 /// <para>Gets the data stored
343 /// in the column, specified by index and version of the data to retrieve.</para>
344 /// </devdoc>
345 public object this[int columnIndex, DataRowVersion version] {
346 get {
347 DataColumn column = _columns[columnIndex];
348 int record = GetRecordFromVersion(version);
349 _table.recordManager.VerifyRecord(record, this);
350 VerifyValueFromStorage(column, version, column[record]);
351 return column[record];
355 /// <devdoc>
356 /// <para> Gets the specified version of data stored in
357 /// the named column.</para>
358 /// </devdoc>
359 public object this[string columnName, DataRowVersion version] {
360 get {
361 DataColumn column = GetDataColumn(columnName);
362 int record = GetRecordFromVersion(version);
363 _table.recordManager.VerifyRecord(record, this);
364 VerifyValueFromStorage(column, version, column[record]);
365 return column[record];
369 /// <devdoc>
370 /// <para>Gets the specified version of data stored in the specified <see cref='System.Data.DataColumn'/>.</para>
371 /// </devdoc>
372 public object this[DataColumn column, DataRowVersion version] {
373 get {
374 CheckColumn(column);
375 int record = GetRecordFromVersion(version);
376 _table.recordManager.VerifyRecord(record, this);
377 VerifyValueFromStorage(column, version, column[record]);
378 return column[record];
382 /// <devdoc>
383 /// <para>Gets
384 /// or sets all of the values for this row through an array.</para>
385 /// </devdoc>
386 public object[] ItemArray {
387 get {
388 int record = GetDefaultRecord();
389 _table.recordManager.VerifyRecord(record, this);
390 object[] values = new object[_columns.Count];
391 for (int i = 0; i < values.Length; i++) {
392 DataColumn column = _columns[i];
393 VerifyValueFromStorage(column, DataRowVersion.Default, column[record]);
394 values[i] = column[record];
396 return values;
398 set {
399 if (null == value) { // WebData 104372
400 throw ExceptionBuilder.ArgumentNull("ItemArray");
402 if (_columns.Count < value.Length) {
403 throw ExceptionBuilder.ValueArrayLength();
405 DataColumnChangeEventArgs e = null;
406 if (_table.NeedColumnChangeEvents) {
407 e = new DataColumnChangeEventArgs(this);
409 bool immediate = BeginEditInternal();
411 for (int i = 0; i < value.Length; ++i) {
412 // Empty means don't change the row.
413 if (null != value[i]) {
414 // may throw exception if user removes column from table during event
415 DataColumn column = _columns[i];
417 if ((-1 != rowID) && column.ReadOnly) {
418 throw ExceptionBuilder.ReadOnly(column.ColumnName);
421 // allow users to tailor the proposed value, or throw an exception.
422 // note: we intentionally do not try/catch this event.
423 // note: we also allow user to do anything at this point
424 // infinite loops are possible if user calls Item or ItemArray during the event
425 if (null != e) {
426 e.InitializeColumnChangeEvent(column, value[i]);
427 _table.OnColumnChanging(e);
430 if (column.Table != _table) {
431 // user removed column from table during OnColumnChanging event
432 throw ExceptionBuilder.ColumnNotInTheTable(column.ColumnName, _table.TableName);
434 if ((-1 != rowID) && column.ReadOnly) {
435 // user adds row to table during OnColumnChanging event
436 throw ExceptionBuilder.ReadOnly(column.ColumnName);
438 if (tempRecord == -1) {
439 // user affected CancelEdit or EndEdit during OnColumnChanging event of the last value
440 BeginEditInternal();
443 object proposed = (null != e) ? e.ProposedValue : value[i];
444 if (null == proposed) {
445 if (column.IsValueType) { // WebData 105963
446 throw ExceptionBuilder.CannotSetToNull(column);
448 proposed = DBNull.Value;
451 try {
452 // must get proposed record after each event because user may have
453 // called EndEdit(), AcceptChanges(), BeginEdit() during the event
454 int record = GetProposedRecordNo();
455 _table.recordManager.VerifyRecord(record, this);
456 column[record] = proposed;
458 catch (Exception e1) {
460 if (Common.ADP.IsCatchableOrSecurityExceptionType(e1)) {
461 if (immediate) {
462 Debug.Assert(!inChangingEvent, "how are we in a changing event to cancel?");
463 Debug.Assert(-1 != tempRecord, "how no propsed record to cancel?");
464 CancelEdit(); // WebData 107154
467 throw;
469 LastChangedColumn = column;
471 // note: we intentionally do not try/catch this event.
472 // infinite loops are possible if user calls Item or ItemArray during the event
473 if (null != e) {
474 _table.OnColumnChanged(e); // user may call CancelEdit or EndEdit
479 // proposed breaking change: if (immediate){ EndEdit(); } because table currently always fires RowChangedEvent
480 Debug.Assert(!inChangingEvent, "how are we in a changing event to end?");
481 EndEdit();
485 /// <devdoc>
486 /// <para>Commits all the changes made to this row
487 /// since the last time <see cref='System.Data.DataRow.AcceptChanges'/> was called.</para>
488 /// </devdoc>
489 public void AcceptChanges() {
490 IntPtr hscp;
491 Bid.ScopeEnter(out hscp, "<ds.DataRow.AcceptChanges|API> %d#\n", ObjectID);
492 try {
493 EndEdit();
495 if (this.RowState != DataRowState.Detached && this.RowState != DataRowState.Deleted) {
496 if (_columns.ColumnsImplementingIChangeTrackingCount > 0) {
497 foreach(DataColumn dc in _columns.ColumnsImplementingIChangeTracking) {
498 object value = this[dc];
499 if (DBNull.Value != value) {
500 IChangeTracking tracking = (IChangeTracking)value;
501 if (tracking.IsChanged) {
502 tracking.AcceptChanges();
508 _table.CommitRow(this);
510 finally {
511 Bid.ScopeLeave(ref hscp);
515 /// <devdoc>
516 /// <para>Begins an edit operation on a <see cref='System.Data.DataRow'/>object.</para>
517 /// </devdoc>
519 EditorBrowsableAttribute(EditorBrowsableState.Advanced),
521 public void BeginEdit() {
522 BeginEditInternal();
525 private bool BeginEditInternal() {
526 if (inChangingEvent) {
527 throw ExceptionBuilder.BeginEditInRowChanging();
529 if (tempRecord != -1) {
530 if (tempRecord < _table.recordManager.LastFreeRecord) {
531 return false; // we will not call EndEdit
533 else {
534 // partial fix for detached row after Table.Clear scenario
535 // in debug, it will have asserted earlier, but with this
536 // it will go get a new record for editing
537 tempRecord = -1;
539 // shifted VerifyRecord to first make the correction, then verify
540 _table.recordManager.VerifyRecord(tempRecord, this);
543 if (oldRecord != -1 && newRecord == -1) {
544 throw ExceptionBuilder.DeletedRowInaccessible();
549 ResetLastChangedColumn(); // shouldn't have to do this
551 tempRecord = _table.NewRecord(newRecord);
552 Debug.Assert(-1 != tempRecord, "missing temp record");
553 Debug.Assert(0 == _countColumnChange, "unexpected column change count");
554 Debug.Assert(null == _lastChangedColumn, "unexpected last column change");
555 return true;
558 /// <devdoc>
559 /// <para>Cancels the current edit on the row.</para>
560 /// </devdoc>
562 EditorBrowsableAttribute(EditorBrowsableState.Advanced),
564 public void CancelEdit() {
565 if (inChangingEvent) {
566 throw ExceptionBuilder.CancelEditInRowChanging();
569 _table.FreeRecord(ref tempRecord);
570 Debug.Assert(-1 == tempRecord, "unexpected temp record");
571 ResetLastChangedColumn();
574 private void CheckColumn(DataColumn column) {
575 if (column == null) {
576 throw ExceptionBuilder.ArgumentNull("column");
579 if (column.Table != _table) {
580 throw ExceptionBuilder.ColumnNotInTheTable(column.ColumnName, _table.TableName);
584 /// <devdoc>
585 /// Throws a RowNotInTableException if row isn't in table.
586 /// </devdoc>
587 internal void CheckInTable() {
588 if (rowID == -1) {
589 throw ExceptionBuilder.RowNotInTheTable();
593 /// <devdoc>
594 /// <para>Deletes the row.</para>
595 /// </devdoc>
596 public void Delete() {
597 if (inDeletingEvent) {
598 throw ExceptionBuilder.DeleteInRowDeleting();
601 if (newRecord == -1)
602 return;
604 _table.DeleteRow(this);
607 /// <devdoc>
608 /// <para>Ends the edit occurring on the row.</para>
609 /// </devdoc>
611 EditorBrowsableAttribute(EditorBrowsableState.Advanced),
613 public void EndEdit() {
614 if (inChangingEvent) {
615 throw ExceptionBuilder.EndEditInRowChanging();
618 if (newRecord == -1) {
619 return; // this is meaningless, detatched row case
622 if (tempRecord != -1) {
623 try {
624 // suppressing the ensure property changed because it's possible that no values have been modified
625 _table.SetNewRecord(this, tempRecord, suppressEnsurePropertyChanged: true);
627 finally {
628 // a constraint violation may be thrown during SetNewRecord
629 ResetLastChangedColumn();
634 /// <devdoc>
635 /// <para>Sets the error description for a column specified by index.</para>
636 /// </devdoc>
637 public void SetColumnError(int columnIndex, string error) {
638 DataColumn column = _columns[columnIndex];
639 if (column == null)
640 throw ExceptionBuilder.ColumnOutOfRange(columnIndex);
642 SetColumnError(column, error);
645 /// <devdoc>
646 /// <para>Sets
647 /// the error description for a column specified by name.</para>
648 /// </devdoc>
649 public void SetColumnError(string columnName, string error) {
650 DataColumn column = GetDataColumn(columnName);
651 SetColumnError(column, error);
654 /// <devdoc>
655 /// <para>Sets the error description for a column specified as a <see cref='System.Data.DataColumn'/>.</para>
656 /// </devdoc>
657 public void SetColumnError(DataColumn column, string error) {
658 CheckColumn(column);
660 IntPtr hscp;
661 Bid.ScopeEnter(out hscp, "<ds.DataRow.SetColumnError|API> %d#, column=%d, error='%ls'\n", ObjectID, column.ObjectID, error);
662 try {
663 if (this.error == null) this.error = new DataError();
664 if(GetColumnError(column) != error) {
665 this.error.SetColumnError(column, error);
666 RowErrorChanged();
669 finally {
670 Bid.ScopeLeave(ref hscp);
674 /// <devdoc>
675 /// <para>Gets the error description for the column specified
676 /// by index.</para>
677 /// </devdoc>
678 public string GetColumnError(int columnIndex) {
679 DataColumn column = _columns[columnIndex];
680 return GetColumnError(column);
683 /// <devdoc>
684 /// <para>Gets the error description for a column, specified by name.</para>
685 /// </devdoc>
686 public string GetColumnError(string columnName) {
687 DataColumn column = GetDataColumn(columnName);
688 return GetColumnError(column);
691 /// <devdoc>
692 /// <para>Gets the error description of
693 /// the specified <see cref='System.Data.DataColumn'/>.</para>
694 /// </devdoc>
695 public string GetColumnError(DataColumn column) {
696 CheckColumn(column);
697 if (error == null) error = new DataError();
698 return error.GetColumnError(column);
701 /// <summary>
702 /// Clears the errors for the row, including the <see cref='System.Data.DataRow.RowError'/>
703 /// and errors set with <see cref='System.Data.DataRow.SetColumnError(DataColumn, string)'/>
704 /// </summary>
705 public void ClearErrors() {
706 if (error != null) {
707 error.Clear();
708 RowErrorChanged();
712 internal void ClearError(DataColumn column) {
713 if (error != null) {
714 error.Clear(column);
715 RowErrorChanged();
719 /// <devdoc>
720 /// <para>Gets a value indicating whether there are errors in a columns collection.</para>
721 /// </devdoc>
722 public bool HasErrors {
723 get {
724 return(error == null ? false : error.HasErrors);
728 /// <devdoc>
729 /// <para>Gets an array of columns that have errors.</para>
730 /// </devdoc>
731 public DataColumn[] GetColumnsInError() {
732 if (error == null)
733 return DataTable.zeroColumns;
734 else
735 return error.GetColumnsInError();
739 public DataRow[] GetChildRows(string relationName) {
740 return GetChildRows(_table.ChildRelations[relationName], DataRowVersion.Default);
743 public DataRow[] GetChildRows(string relationName, DataRowVersion version) {
744 return GetChildRows(_table.ChildRelations[relationName], version);
747 /// <devdoc>
748 /// <para>Gets the child rows of this <see cref='System.Data.DataRow'/> using the
749 /// specified <see cref='System.Data.DataRelation'/>
750 /// .</para>
751 /// </devdoc>
752 public DataRow[] GetChildRows(DataRelation relation) {
753 return GetChildRows(relation, DataRowVersion.Default);
756 /// <devdoc>
757 /// <para>Gets the child rows of this <see cref='System.Data.DataRow'/> using the specified <see cref='System.Data.DataRelation'/> and the specified <see cref='System.Data.DataRowVersion'/></para>
758 /// </devdoc>
759 public DataRow[] GetChildRows(DataRelation relation, DataRowVersion version) {
760 if (relation == null)
761 return _table.NewRowArray(0);
763 //if (-1 == rowID)
764 // throw ExceptionBuilder.RowNotInTheTable();
766 if (relation.DataSet != _table.DataSet)
767 throw ExceptionBuilder.RowNotInTheDataSet();
768 if (relation.ParentKey.Table != _table)
769 throw ExceptionBuilder.RelationForeignTable(relation.ParentTable.TableName, _table.TableName);
770 return DataRelation.GetChildRows(relation.ParentKey, relation.ChildKey, this, version);
773 internal DataColumn GetDataColumn(string columnName) {
774 DataColumn column = _columns[columnName];
775 if (null != column) {
776 return column;
778 throw ExceptionBuilder.ColumnNotInTheTable(columnName, _table.TableName);
781 public DataRow GetParentRow(string relationName) {
782 return GetParentRow(_table.ParentRelations[relationName], DataRowVersion.Default);
785 public DataRow GetParentRow(string relationName, DataRowVersion version) {
786 return GetParentRow(_table.ParentRelations[relationName], version);
789 /// <devdoc>
790 /// <para>Gets the parent row of this <see cref='System.Data.DataRow'/> using the specified <see cref='System.Data.DataRelation'/> .</para>
791 /// </devdoc>
792 public DataRow GetParentRow(DataRelation relation) {
793 return GetParentRow(relation, DataRowVersion.Default);
796 /// <devdoc>
797 /// <para>Gets the parent row of this <see cref='System.Data.DataRow'/>
798 /// using the specified <see cref='System.Data.DataRelation'/> and <see cref='System.Data.DataRowVersion'/>.</para>
799 /// </devdoc>
800 public DataRow GetParentRow(DataRelation relation, DataRowVersion version) {
801 if (relation == null)
802 return null;
804 //if (-1 == rowID)
805 // throw ExceptionBuilder.RowNotInTheTable();
807 if (relation.DataSet != _table.DataSet)
808 throw ExceptionBuilder.RelationForeignRow();
810 if (relation.ChildKey.Table != _table)
811 throw ExceptionBuilder.GetParentRowTableMismatch(relation.ChildTable.TableName, _table.TableName);
813 return DataRelation.GetParentRow(relation.ParentKey, relation.ChildKey, this, version);
815 // a multiple nested child table's row can have only one non-null FK per row. So table has multiple
816 // parents, but a row can have only one parent. Same nested row cannot below to 2 parent rows.
817 internal DataRow GetNestedParentRow(DataRowVersion version) {
818 // 1) Walk over all FKs and get the non-null. 2) Get the relation. 3) Get the parent Row.
819 DataRelation[] nestedParentRelations = _table.NestedParentRelations;
820 foreach(DataRelation rel in nestedParentRelations) {
821 if (rel == null) // don't like this but done for backward code compatability
822 continue;
823 if (rel.ParentTable == _table) // self-nested table
824 this.CheckForLoops(rel);
825 DataRow row = this.GetParentRow(rel, version);
826 if (row != null) {
827 return row;
830 return null;// Rule 1: At all times, only ONE FK "(in a row) can be non-Null
833 // No Nested in 1-many
835 /// <devdoc>
836 /// <para>[To be supplied.]</para>
837 /// </devdoc>
838 public DataRow[] GetParentRows(string relationName) {
839 return GetParentRows(_table.ParentRelations[relationName], DataRowVersion.Default);
842 /// <devdoc>
843 /// <para>[To be supplied.]</para>
844 /// </devdoc>
845 public DataRow[] GetParentRows(string relationName, DataRowVersion version) {
846 return GetParentRows(_table.ParentRelations[relationName], version);
849 /// <devdoc>
850 /// <para>
851 /// Gets the parent rows of this <see cref='System.Data.DataRow'/> using the specified <see cref='System.Data.DataRelation'/> .
852 /// </para>
853 /// </devdoc>
854 public DataRow[] GetParentRows(DataRelation relation) {
855 return GetParentRows(relation, DataRowVersion.Default);
858 /// <devdoc>
859 /// <para>
860 /// Gets the parent rows of this <see cref='System.Data.DataRow'/> using the specified <see cref='System.Data.DataRelation'/> .
861 /// </para>
862 /// </devdoc>
863 public DataRow[] GetParentRows(DataRelation relation, DataRowVersion version) {
864 if (relation == null)
865 return _table.NewRowArray(0);
867 //if (-1 == rowID)
868 // throw ExceptionBuilder.RowNotInTheTable();
870 if (relation.DataSet != _table.DataSet)
871 throw ExceptionBuilder.RowNotInTheDataSet();
873 if (relation.ChildKey.Table != _table)
874 throw ExceptionBuilder.GetParentRowTableMismatch(relation.ChildTable.TableName, _table.TableName);
876 return DataRelation.GetParentRows(relation.ParentKey, relation.ChildKey, this, version);
879 internal object[] GetColumnValues(DataColumn[] columns) {
880 return GetColumnValues(columns, DataRowVersion.Default);
883 internal object[] GetColumnValues(DataColumn[] columns, DataRowVersion version) {
884 DataKey key = new DataKey(columns, false); // temporary key, don't copy columns
885 return GetKeyValues(key, version);
888 internal object[] GetKeyValues(DataKey key) {
889 int record = GetDefaultRecord();
890 return key.GetKeyValues(record);
893 internal object[] GetKeyValues(DataKey key, DataRowVersion version) {
894 int record = GetRecordFromVersion(version);
895 return key.GetKeyValues(record);
898 internal int GetCurrentRecordNo() {
899 if (newRecord == -1)
900 throw ExceptionBuilder.NoCurrentData();
901 return newRecord;
904 internal int GetDefaultRecord() {
905 if (tempRecord != -1)
906 return tempRecord;
907 if (newRecord != -1) {
908 return newRecord;
910 // If row has oldRecord - this is deleted row.
911 if (oldRecord == -1)
912 throw ExceptionBuilder.RowRemovedFromTheTable();
913 else
914 throw ExceptionBuilder.DeletedRowInaccessible();
917 internal int GetOriginalRecordNo() {
918 if (oldRecord == -1)
919 throw ExceptionBuilder.NoOriginalData();
920 return oldRecord;
923 private int GetProposedRecordNo() {
924 if (tempRecord == -1)
925 throw ExceptionBuilder.NoProposedData();
926 return tempRecord;
929 internal int GetRecordFromVersion(DataRowVersion version) {
930 switch (version) {
931 case DataRowVersion.Original:
932 return GetOriginalRecordNo();
933 case DataRowVersion.Current:
934 return GetCurrentRecordNo();
935 case DataRowVersion.Proposed:
936 return GetProposedRecordNo();
937 case DataRowVersion.Default:
938 return GetDefaultRecord();
939 default:
940 throw ExceptionBuilder.InvalidRowVersion();
944 internal DataRowVersion GetDefaultRowVersion(DataViewRowState viewState) {
945 if (oldRecord == newRecord) {
946 if (oldRecord == -1) {
947 // should be DataView.addNewRow
948 return DataRowVersion.Default;
950 Debug.Assert(0 != (DataViewRowState.Unchanged & viewState), "not DataViewRowState.Unchanged");
951 return DataRowVersion.Default;
953 else if (oldRecord == -1) {
954 Debug.Assert(0 != (DataViewRowState.Added & viewState), "not DataViewRowState.Added");
955 return DataRowVersion.Default;
957 else if (newRecord == -1) {
958 Debug.Assert(_action==DataRowAction.Rollback || 0 != (DataViewRowState.Deleted & viewState), "not DataViewRowState.Deleted");
959 return DataRowVersion.Original;
961 else if (0 != (DataViewRowState.ModifiedCurrent & viewState)) {
962 return DataRowVersion.Default;
964 Debug.Assert(0 != (DataViewRowState.ModifiedOriginal & viewState), "not DataViewRowState.ModifiedOriginal");
965 return DataRowVersion.Original;
968 internal DataViewRowState GetRecordState(int record) {
969 if (record == -1)
970 return DataViewRowState.None;
971 if (record == oldRecord && record == newRecord)
972 return DataViewRowState.Unchanged;
973 if (record == oldRecord)
974 return(newRecord != -1) ? DataViewRowState.ModifiedOriginal : DataViewRowState.Deleted;
975 if (record == newRecord)
976 return(oldRecord != -1) ? DataViewRowState.ModifiedCurrent : DataViewRowState.Added;
977 return DataViewRowState.None;
980 internal bool HasKeyChanged(DataKey key) {
981 return HasKeyChanged(key, DataRowVersion.Current, DataRowVersion.Proposed);
984 internal bool HasKeyChanged(DataKey key, DataRowVersion version1, DataRowVersion version2) {
985 if (!HasVersion(version1) || !HasVersion(version2))
986 return true;
987 return !key.RecordsEqual(GetRecordFromVersion(version1), GetRecordFromVersion(version2));
990 /// <devdoc>
991 /// <para>
992 /// Gets a value indicating whether a specified version exists.
993 /// </para>
994 /// </devdoc>
995 public bool HasVersion(DataRowVersion version) {
996 switch (version) {
997 case DataRowVersion.Original:
998 return(oldRecord != -1);
999 case DataRowVersion.Current:
1000 return(newRecord != -1);
1001 case DataRowVersion.Proposed:
1002 return(tempRecord != -1);
1003 case DataRowVersion.Default:
1004 return(tempRecord != -1 || newRecord != -1);
1005 default:
1006 throw ExceptionBuilder.InvalidRowVersion();
1010 internal bool HasChanges() {
1011 if (!HasVersion(DataRowVersion.Original) || !HasVersion(DataRowVersion.Current)) {
1012 return true; // if does not have original, its added row, if does not have current, its deleted row so it has changes
1014 foreach(DataColumn dc in Table.Columns) {
1015 if (dc.Compare(oldRecord, newRecord) != 0) {
1016 return true;
1019 return false;
1022 internal bool HaveValuesChanged(DataColumn[] columns) {
1023 return HaveValuesChanged(columns, DataRowVersion.Current, DataRowVersion.Proposed);
1026 internal bool HaveValuesChanged(DataColumn[] columns, DataRowVersion version1, DataRowVersion version2) {
1027 for (int i = 0; i < columns.Length; i++) {
1028 CheckColumn(columns[i]);
1030 DataKey key = new DataKey(columns, false); // temporary key, don't copy columns
1031 return HasKeyChanged(key, version1, version2);
1034 /// <devdoc>
1035 /// <para>
1036 /// Gets
1037 /// a value indicating whether the column at the specified index contains a
1038 /// null value.
1039 /// </para>
1040 /// </devdoc>
1041 public bool IsNull(int columnIndex) {
1042 DataColumn column = _columns[columnIndex];
1043 int record = GetDefaultRecord();
1044 return column.IsNull(record);
1047 /// <devdoc>
1048 /// <para>
1049 /// Gets a value indicating whether the named column contains a null value.
1050 /// </para>
1051 /// </devdoc>
1052 public bool IsNull(string columnName) {
1053 DataColumn column = GetDataColumn(columnName);
1054 int record = GetDefaultRecord();
1055 return column.IsNull(record);
1058 /// <devdoc>
1059 /// <para>
1060 /// Gets a value indicating whether the specified <see cref='System.Data.DataColumn'/>
1061 /// contains a null value.
1062 /// </para>
1063 /// </devdoc>
1064 public bool IsNull(DataColumn column) {
1065 CheckColumn(column);
1066 int record = GetDefaultRecord();
1067 return column.IsNull(record);
1070 /// <devdoc>
1071 /// <para>[To be supplied.]</para>
1072 /// </devdoc>
1073 public bool IsNull(DataColumn column, DataRowVersion version) {
1074 CheckColumn(column);
1075 int record = GetRecordFromVersion(version);
1076 return column.IsNull(record);
1079 /// <devdoc>
1080 /// <para>
1081 /// Rejects all changes made to the row since <see cref='System.Data.DataRow.AcceptChanges'/>
1082 /// was last called.
1083 /// </para>
1084 /// </devdoc>
1085 public void RejectChanges() {
1086 IntPtr hscp;
1087 Bid.ScopeEnter(out hscp, "<ds.DataRow.RejectChanges|API> %d#\n", ObjectID);
1088 try {
1089 if (this.RowState != DataRowState.Detached) {
1090 if (_columns.ColumnsImplementingIChangeTrackingCount != _columns.ColumnsImplementingIRevertibleChangeTrackingCount) {
1091 foreach(DataColumn dc in _columns.ColumnsImplementingIChangeTracking) {
1092 if (!dc.ImplementsIRevertibleChangeTracking) {
1093 object value = null;
1094 if (this.RowState != DataRowState.Deleted)
1095 value = this[dc];
1096 else
1097 value = this[dc, DataRowVersion.Original];
1098 if (DBNull.Value != value){
1099 if (((IChangeTracking)value).IsChanged) {
1100 throw ExceptionBuilder.UDTImplementsIChangeTrackingButnotIRevertible(dc.DataType.AssemblyQualifiedName);
1106 foreach(DataColumn dc in _columns.ColumnsImplementingIChangeTracking) {
1107 object value = null;
1108 if (this.RowState != DataRowState.Deleted)
1109 value = this[dc];
1110 else
1111 value = this[dc, DataRowVersion.Original];
1112 if (DBNull.Value != value) {
1113 IChangeTracking tracking = (IChangeTracking)value;
1114 if (tracking.IsChanged) {
1115 ((IRevertibleChangeTracking)value).RejectChanges();
1120 _table.RollbackRow(this);
1122 finally {
1123 Bid.ScopeLeave(ref hscp);
1127 internal void ResetLastChangedColumn() {
1128 _lastChangedColumn = null;
1129 _countColumnChange = 0;
1132 internal void SetKeyValues(DataKey key, object[] keyValues) {
1133 bool fFirstCall = true;
1134 bool immediate = (tempRecord == -1);
1136 for (int i = 0; i < keyValues.Length; i++) {
1137 object value = this[key.ColumnsReference[i]];
1138 if (!value.Equals(keyValues[i])) {
1139 if (immediate && fFirstCall) {
1140 fFirstCall = false;
1141 BeginEditInternal();
1143 this[key.ColumnsReference[i]] = keyValues[i];
1146 if (!fFirstCall)
1147 EndEdit();
1150 /// <devdoc>
1151 /// <para>
1152 /// Sets the specified column's value to a null value.
1153 /// </para>
1154 /// </devdoc>
1155 protected void SetNull(DataColumn column) {
1156 this[column] = DBNull.Value;
1159 internal void SetNestedParentRow(DataRow parentRow, bool setNonNested) {
1160 if (parentRow == null) {
1161 SetParentRowToDBNull();
1162 return;
1165 foreach (DataRelation relation in _table.ParentRelations) {
1166 if (relation.Nested || setNonNested) {
1167 if (relation.ParentKey.Table == parentRow._table) {
1168 object[] parentKeyValues = parentRow.GetKeyValues(relation.ParentKey);
1169 this.SetKeyValues(relation.ChildKey, parentKeyValues);
1171 if (relation.Nested) {
1172 if (parentRow._table == _table)
1173 this.CheckForLoops(relation);
1174 else
1175 this.GetParentRow(relation);
1181 /// <devdoc>
1182 /// <para>[To be supplied.]</para>
1183 /// </devdoc>
1184 public void SetParentRow(DataRow parentRow) {
1185 SetNestedParentRow(parentRow, true);
1188 /// <devdoc>
1189 /// <para>
1190 /// Sets current row's parent row with specified relation.
1191 /// </para>
1192 /// </devdoc>
1193 public void SetParentRow(DataRow parentRow, DataRelation relation) {
1194 if (relation == null) {
1195 SetParentRow(parentRow);
1196 return;
1199 if (parentRow == null) {
1200 SetParentRowToDBNull(relation);
1201 return;
1204 //if (-1 == rowID)
1205 // throw ExceptionBuilder.ChildRowNotInTheTable();
1207 //if (-1 == parentRow.rowID)
1208 // throw ExceptionBuilder.ParentRowNotInTheTable();
1210 if (_table.DataSet != parentRow._table.DataSet)
1211 throw ExceptionBuilder.ParentRowNotInTheDataSet();
1213 if (relation.ChildKey.Table != _table)
1214 throw ExceptionBuilder.SetParentRowTableMismatch(relation.ChildKey.Table.TableName, _table.TableName);
1216 if (relation.ParentKey.Table != parentRow._table)
1217 throw ExceptionBuilder.SetParentRowTableMismatch(relation.ParentKey.Table.TableName, parentRow._table.TableName);
1219 object[] parentKeyValues = parentRow.GetKeyValues(relation.ParentKey);
1220 this.SetKeyValues(relation.ChildKey, parentKeyValues);
1223 internal void SetParentRowToDBNull() {
1224 //if (-1 == rowID)
1225 // throw ExceptionBuilder.ChildRowNotInTheTable();
1227 foreach (DataRelation relation in _table.ParentRelations)
1228 SetParentRowToDBNull(relation);
1231 internal void SetParentRowToDBNull(DataRelation relation) {
1232 Debug.Assert(relation != null, "The relation should not be null here.");
1234 //if (-1 == rowID)
1235 // throw ExceptionBuilder.ChildRowNotInTheTable();
1237 if (relation.ChildKey.Table != _table)
1238 throw ExceptionBuilder.SetParentRowTableMismatch(relation.ChildKey.Table.TableName, _table.TableName);
1241 object[] parentKeyValues = new object[1];
1242 parentKeyValues[0] = DBNull.Value;
1243 this.SetKeyValues(relation.ChildKey, parentKeyValues);
1245 public void SetAdded(){
1246 if (this.RowState == DataRowState.Unchanged) {
1247 _table.SetOldRecord(this, -1);
1249 else {
1250 throw ExceptionBuilder.SetAddedAndModifiedCalledOnnonUnchanged();
1254 public void SetModified(){
1255 if (this.RowState == DataRowState.Unchanged) {
1256 tempRecord = _table.NewRecord(newRecord);
1257 if (tempRecord != -1) {
1258 // suppressing the ensure property changed because no values have changed
1259 _table.SetNewRecord(this, tempRecord, suppressEnsurePropertyChanged: true);
1262 else {
1263 throw ExceptionBuilder.SetAddedAndModifiedCalledOnnonUnchanged();
1268 RecordList contains the empty column storage needed. We need to copy the existing record values into this storage.
1270 internal int CopyValuesIntoStore(ArrayList storeList, ArrayList nullbitList, int storeIndex) {
1271 int recordCount = 0;
1272 if (oldRecord != -1) {//Copy original record for the row in Unchanged, Modified, Deleted state.
1273 for (int i = 0; i < _columns.Count; i++) {
1274 _columns[i].CopyValueIntoStore(oldRecord, storeList[i], (BitArray) nullbitList[i], storeIndex);
1276 recordCount++;
1277 storeIndex++;
1280 DataRowState state = RowState;
1281 if ((DataRowState.Added == state) || (DataRowState.Modified == state)) { //Copy current record for the row in Added, Modified state.
1282 for (int i = 0; i < _columns.Count; i++) {
1283 _columns[i].CopyValueIntoStore(newRecord, storeList[i], (BitArray) nullbitList[i], storeIndex);
1285 recordCount++;
1286 storeIndex++;
1289 if (-1 != tempRecord) {//Copy temp record for the row in edit mode.
1290 for (int i = 0; i < _columns.Count; i++) {
1291 _columns[i].CopyValueIntoStore(tempRecord, storeList[i], (BitArray)nullbitList[i], storeIndex);
1293 recordCount++;
1294 storeIndex++;
1296 return recordCount;
1299 [Conditional("DEBUG")]
1300 private void VerifyValueFromStorage(DataColumn column, DataRowVersion version, object valueFromStorage) {
1301 // Dev11 900390: ignore deleted rows by adding "newRecord != -1" condition - we do not evaluate computed rows if they are deleted
1302 if (column.DataExpression != null && !inChangingEvent && tempRecord == -1 && newRecord != -1)
1304 // for unchanged rows, check current if original is asked for.
1305 // this is because by design, there is only single storage for an unchanged row.
1306 if (version == DataRowVersion.Original && oldRecord == newRecord) {
1307 version = DataRowVersion.Current;
1309 // There are various known issues detected by this assert for non-default versions,
1310 // for example DevDiv2 bug 73753
1311 // Since changes consitutute breaking change (either way customer will get another result),
1312 // we decided not to fix them in Dev 11
1313 Debug.Assert(valueFromStorage.Equals(column.DataExpression.Evaluate(this, version)),
1314 "Value from storage does lazily computed expression value");
1319 public sealed class DataRowBuilder {
1320 internal readonly DataTable _table;
1321 internal int _record;
1323 internal DataRowBuilder(DataTable table, int record) {
1324 _table = table;
1325 _record = record;