1 //------------------------------------------------------------------------------
2 // <copyright file="DataRow.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="true" primary="false">Microsoft</owner>
7 //------------------------------------------------------------------------------
9 namespace System
.Data
{
11 using System
.Collections
;
12 using System
.ComponentModel
;
13 using System
.Diagnostics
;
14 using System
.Globalization
;
19 /// <para>Represents a row of data in a <see cref='System.Data.DataTable'/>.</para>
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
);
49 /// Initializes a new instance of the DataRow.
52 /// Constructs a row from the builder. Only for internal usage..
55 protected internal DataRow (DataRowBuilder builder
) {
56 tempRecord
= builder
._record
;
57 _table
= builder
._table
;
58 _columns
= _table
.Columns
;
61 internal XmlBoundElement Element
{
63 return (XmlBoundElement
) _element
;
70 internal DataColumn LastChangedColumn
{
71 get { // last successfully changed column or if multiple columns changed: null
72 if (_countColumnChange
!= 1) {
75 return _lastChangedColumn
;
79 _lastChangedColumn
= value;
83 internal bool HasPropertyChanged
{
84 get { return (0 < _countColumnChange); }
87 internal int RBTreeNodeId
{
92 Bid
.Trace("<ds.DataRow.set_RBTreeNodeId|INFO> %d#, value=%d\n", ObjectID
, value);
93 _rbTreeNodeId
= value;
98 /// <para>Gets or sets the custom error description for a row.</para>
100 public string RowError
{
102 return(error
== null ? String
.Empty
:error
.Text
);
105 Bid
.Trace("<ds.DataRow.set_RowError|API> %d#, value='%ls'\n", ObjectID
, value);
107 if (!Common
.ADP
.IsEmpty(value)) {
108 error
= new DataError(value);
112 else if(error
.Text
!= value) {
119 private void RowErrorChanged() {
120 // We don't know wich record was used by view index. try to use both.
122 _table
.RecordChanged(oldRecord
);
124 _table
.RecordChanged(newRecord
);
127 internal long rowID
{
132 ResetLastChangedColumn();
138 /// <para>Gets the current state of the row in regards to its relationship to the table.</para>
140 public DataRowState RowState
{
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
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
180 /// <para>Gets the <see cref='System.Data.DataTable'/>
181 /// for which this row has a schema.</para>
183 public DataTable Table
{
190 /// <para>Gets or sets the data stored in the column specified by index.</para>
192 public object this[int columnIndex
] {
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
];
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
))
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
);
221 parent
= parent
.GetParentRow(rel
);
225 internal int GetNestedParentCount() {
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
231 if (rel
.ParentTable
== _table
) // self-nested table
232 this.CheckForLoops(rel
);
233 DataRow row
= this.GetParentRow(rel
);
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
244 /// <para>Gets or sets the data stored in the column specified by
247 public object this[string columnName
] {
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
];
256 DataColumn column
= GetDataColumn(columnName
);
257 this[column
] = value;
262 /// <para>Gets or sets
263 /// the data stored in the specified <see cref='System.Data.DataColumn'/>.</para>
265 public object this[DataColumn column
] {
268 int record
= GetDefaultRecord();
269 _table
.recordManager
.VerifyRecord(record
, this);
270 VerifyValueFromStorage(column
, DataRowVersion
.Default
, column
[record
]);
271 return column
[record
];
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();
311 int record
= GetProposedRecordNo();
312 _table
.recordManager
.VerifyRecord(record
, this);
313 column
[record
] = proposed
;
315 catch (Exception e1
){
317 if (Common
.ADP
.IsCatchableOrSecurityExceptionType(e1
)) {
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
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
331 _table
.OnColumnChanged(e
); // user may call CancelEdit or EndEdit
335 Debug
.Assert(!inChangingEvent
, "how are we in a changing event to end?");
342 /// <para>Gets the data stored
343 /// in the column, specified by index and version of the data to retrieve.</para>
345 public object this[int columnIndex
, DataRowVersion version
] {
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
];
356 /// <para> Gets the specified version of data stored in
357 /// the named column.</para>
359 public object this[string columnName
, DataRowVersion version
] {
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
];
370 /// <para>Gets the specified version of data stored in the specified <see cref='System.Data.DataColumn'/>.</para>
372 public object this[DataColumn column
, DataRowVersion version
] {
375 int record
= GetRecordFromVersion(version
);
376 _table
.recordManager
.VerifyRecord(record
, this);
377 VerifyValueFromStorage(column
, version
, column
[record
]);
378 return column
[record
];
384 /// or sets all of the values for this row through an array.</para>
386 public object[] ItemArray
{
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
];
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
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
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
;
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
)) {
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
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
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?");
486 /// <para>Commits all the changes made to this row
487 /// since the last time <see cref='System.Data.DataRow.AcceptChanges'/> was called.</para>
489 public void AcceptChanges() {
491 Bid
.ScopeEnter(out hscp
, "<ds.DataRow.AcceptChanges|API> %d#\n", ObjectID
);
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);
511 Bid
.ScopeLeave(ref hscp
);
516 /// <para>Begins an edit operation on a <see cref='System.Data.DataRow'/>object.</para>
519 EditorBrowsableAttribute(EditorBrowsableState
.Advanced
),
521 public void BeginEdit() {
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
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
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");
559 /// <para>Cancels the current edit on the row.</para>
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
);
585 /// Throws a RowNotInTableException if row isn't in table.
587 internal void CheckInTable() {
589 throw ExceptionBuilder
.RowNotInTheTable();
594 /// <para>Deletes the row.</para>
596 public void Delete() {
597 if (inDeletingEvent
) {
598 throw ExceptionBuilder
.DeleteInRowDeleting();
604 _table
.DeleteRow(this);
608 /// <para>Ends the edit occurring on the row.</para>
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) {
624 // suppressing the ensure property changed because it's possible that no values have been modified
625 _table
.SetNewRecord(this, tempRecord
, suppressEnsurePropertyChanged
: true);
628 // a constraint violation may be thrown during SetNewRecord
629 ResetLastChangedColumn();
635 /// <para>Sets the error description for a column specified by index.</para>
637 public void SetColumnError(int columnIndex
, string error
) {
638 DataColumn column
= _columns
[columnIndex
];
640 throw ExceptionBuilder
.ColumnOutOfRange(columnIndex
);
642 SetColumnError(column
, error
);
647 /// the error description for a column specified by name.</para>
649 public void SetColumnError(string columnName
, string error
) {
650 DataColumn column
= GetDataColumn(columnName
);
651 SetColumnError(column
, error
);
655 /// <para>Sets the error description for a column specified as a <see cref='System.Data.DataColumn'/>.</para>
657 public void SetColumnError(DataColumn column
, string error
) {
661 Bid
.ScopeEnter(out hscp
, "<ds.DataRow.SetColumnError|API> %d#, column=%d, error='%ls'\n", ObjectID
, column
.ObjectID
, error
);
663 if (this.error
== null) this.error
= new DataError();
664 if(GetColumnError(column
) != error
) {
665 this.error
.SetColumnError(column
, error
);
670 Bid
.ScopeLeave(ref hscp
);
675 /// <para>Gets the error description for the column specified
678 public string GetColumnError(int columnIndex
) {
679 DataColumn column
= _columns
[columnIndex
];
680 return GetColumnError(column
);
684 /// <para>Gets the error description for a column, specified by name.</para>
686 public string GetColumnError(string columnName
) {
687 DataColumn column
= GetDataColumn(columnName
);
688 return GetColumnError(column
);
692 /// <para>Gets the error description of
693 /// the specified <see cref='System.Data.DataColumn'/>.</para>
695 public string GetColumnError(DataColumn column
) {
697 if (error
== null) error
= new DataError();
698 return error
.GetColumnError(column
);
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)'/>
705 public void ClearErrors() {
712 internal void ClearError(DataColumn column
) {
720 /// <para>Gets a value indicating whether there are errors in a columns collection.</para>
722 public bool HasErrors
{
724 return(error
== null ? false : error
.HasErrors
);
729 /// <para>Gets an array of columns that have errors.</para>
731 public DataColumn
[] GetColumnsInError() {
733 return DataTable
.zeroColumns
;
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
);
748 /// <para>Gets the child rows of this <see cref='System.Data.DataRow'/> using the
749 /// specified <see cref='System.Data.DataRelation'/>
752 public DataRow
[] GetChildRows(DataRelation relation
) {
753 return GetChildRows(relation
, DataRowVersion
.Default
);
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>
759 public DataRow
[] GetChildRows(DataRelation relation
, DataRowVersion version
) {
760 if (relation
== null)
761 return _table
.NewRowArray(0);
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
) {
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
);
790 /// <para>Gets the parent row of this <see cref='System.Data.DataRow'/> using the specified <see cref='System.Data.DataRelation'/> .</para>
792 public DataRow
GetParentRow(DataRelation relation
) {
793 return GetParentRow(relation
, DataRowVersion
.Default
);
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>
800 public DataRow
GetParentRow(DataRelation relation
, DataRowVersion version
) {
801 if (relation
== null)
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
823 if (rel
.ParentTable
== _table
) // self-nested table
824 this.CheckForLoops(rel
);
825 DataRow row
= this.GetParentRow(rel
, version
);
830 return null;// Rule 1: At all times, only ONE FK "(in a row) can be non-Null
833 // No Nested in 1-many
836 /// <para>[To be supplied.]</para>
838 public DataRow
[] GetParentRows(string relationName
) {
839 return GetParentRows(_table
.ParentRelations
[relationName
], DataRowVersion
.Default
);
843 /// <para>[To be supplied.]</para>
845 public DataRow
[] GetParentRows(string relationName
, DataRowVersion version
) {
846 return GetParentRows(_table
.ParentRelations
[relationName
], version
);
851 /// Gets the parent rows of this <see cref='System.Data.DataRow'/> using the specified <see cref='System.Data.DataRelation'/> .
854 public DataRow
[] GetParentRows(DataRelation relation
) {
855 return GetParentRows(relation
, DataRowVersion
.Default
);
860 /// Gets the parent rows of this <see cref='System.Data.DataRow'/> using the specified <see cref='System.Data.DataRelation'/> .
863 public DataRow
[] GetParentRows(DataRelation relation
, DataRowVersion version
) {
864 if (relation
== null)
865 return _table
.NewRowArray(0);
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() {
900 throw ExceptionBuilder
.NoCurrentData();
904 internal int GetDefaultRecord() {
905 if (tempRecord
!= -1)
907 if (newRecord
!= -1) {
910 // If row has oldRecord - this is deleted row.
912 throw ExceptionBuilder
.RowRemovedFromTheTable();
914 throw ExceptionBuilder
.DeletedRowInaccessible();
917 internal int GetOriginalRecordNo() {
919 throw ExceptionBuilder
.NoOriginalData();
923 private int GetProposedRecordNo() {
924 if (tempRecord
== -1)
925 throw ExceptionBuilder
.NoProposedData();
929 internal int GetRecordFromVersion(DataRowVersion 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();
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
) {
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
))
987 return !key
.RecordsEqual(GetRecordFromVersion(version1
), GetRecordFromVersion(version2
));
992 /// Gets a value indicating whether a specified version exists.
995 public bool HasVersion(DataRowVersion 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);
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) {
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
);
1037 /// a value indicating whether the column at the specified index contains a
1041 public bool IsNull(int columnIndex
) {
1042 DataColumn column
= _columns
[columnIndex
];
1043 int record
= GetDefaultRecord();
1044 return column
.IsNull(record
);
1049 /// Gets a value indicating whether the named column contains a null value.
1052 public bool IsNull(string columnName
) {
1053 DataColumn column
= GetDataColumn(columnName
);
1054 int record
= GetDefaultRecord();
1055 return column
.IsNull(record
);
1060 /// Gets a value indicating whether the specified <see cref='System.Data.DataColumn'/>
1061 /// contains a null value.
1064 public bool IsNull(DataColumn column
) {
1065 CheckColumn(column
);
1066 int record
= GetDefaultRecord();
1067 return column
.IsNull(record
);
1071 /// <para>[To be supplied.]</para>
1073 public bool IsNull(DataColumn column
, DataRowVersion version
) {
1074 CheckColumn(column
);
1075 int record
= GetRecordFromVersion(version
);
1076 return column
.IsNull(record
);
1081 /// Rejects all changes made to the row since <see cref='System.Data.DataRow.AcceptChanges'/>
1082 /// was last called.
1085 public void RejectChanges() {
1087 Bid
.ScopeEnter(out hscp
, "<ds.DataRow.RejectChanges|API> %d#\n", ObjectID
);
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
)
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
)
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);
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
) {
1141 BeginEditInternal();
1143 this[key
.ColumnsReference
[i
]] = keyValues
[i
];
1152 /// Sets the specified column's value to a null value.
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();
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
);
1175 this.GetParentRow(relation
);
1182 /// <para>[To be supplied.]</para>
1184 public void SetParentRow(DataRow parentRow
) {
1185 SetNestedParentRow(parentRow
, true);
1190 /// Sets current row's parent row with specified relation.
1193 public void SetParentRow(DataRow parentRow
, DataRelation relation
) {
1194 if (relation
== null) {
1195 SetParentRow(parentRow
);
1199 if (parentRow
== null) {
1200 SetParentRowToDBNull(relation
);
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() {
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.");
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);
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);
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
);
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
);
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
);
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
) {