2016-08-03 10:59:49 +00:00
//------------------------------------------------------------------------------
// <copyright file="DataRow.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
2017-08-21 15:34:15 +00:00
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="true" primary="false">Microsoft</owner>
2016-08-03 10:59:49 +00:00
//------------------------------------------------------------------------------
namespace System.Data {
using System ;
using System.Collections ;
using System.ComponentModel ;
using System.Diagnostics ;
using System.Globalization ;
using System.Xml ;
/// <devdoc>
/// <para>Represents a row of data in a <see cref='System.Data.DataTable'/>.</para>
/// </devdoc>
public class DataRow {
private readonly DataTable _table ;
private readonly DataColumnCollection _columns ;
internal int oldRecord = - 1 ;
internal int newRecord = - 1 ;
internal int tempRecord ;
internal long _rowID = - 1 ;
internal DataRowAction _action ;
internal bool inChangingEvent ;
internal bool inDeletingEvent ;
internal bool inCascade ;
private DataColumn _lastChangedColumn ; // last successfully changed column
private int _countColumnChange ; // number of columns changed during edit mode
private DataError error ;
private object _element ;
private int _rbTreeNodeId ; // if row is not detached, Id used for computing index in rows collection
private static int _objectTypeCount ; // Bid counter
internal readonly int ObjectID = System . Threading . Interlocked . Increment ( ref _objectTypeCount ) ;
/// <devdoc>
/// <para>
/// Initializes a new instance of the DataRow.
/// </para>
/// <para>
/// Constructs a row from the builder. Only for internal usage..
/// </para>
/// </devdoc>
protected internal DataRow ( DataRowBuilder builder ) {
tempRecord = builder . _record ;
_table = builder . _table ;
_columns = _table . Columns ;
}
internal XmlBoundElement Element {
get {
return ( XmlBoundElement ) _element ;
}
set {
_element = value ;
}
}
internal DataColumn LastChangedColumn {
get { // last successfully changed column or if multiple columns changed: null
if ( _countColumnChange ! = 1 ) {
return null ;
}
return _lastChangedColumn ;
}
set {
_countColumnChange + + ;
_lastChangedColumn = value ;
}
}
internal bool HasPropertyChanged {
get { return ( 0 < _countColumnChange ) ; }
}
internal int RBTreeNodeId {
get {
return _rbTreeNodeId ;
}
set {
Bid . Trace ( "<ds.DataRow.set_RBTreeNodeId|INFO> %d#, value=%d\n" , ObjectID , value ) ;
_rbTreeNodeId = value ;
}
}
/// <devdoc>
/// <para>Gets or sets the custom error description for a row.</para>
/// </devdoc>
public string RowError {
get {
return ( error = = null ? String . Empty : error . Text ) ;
}
set {
Bid . Trace ( "<ds.DataRow.set_RowError|API> %d#, value='%ls'\n" , ObjectID , value ) ;
if ( error = = null ) {
if ( ! Common . ADP . IsEmpty ( value ) ) {
error = new DataError ( value ) ;
}
RowErrorChanged ( ) ;
}
else if ( error . Text ! = value ) {
error . Text = value ;
RowErrorChanged ( ) ;
}
}
}
private void RowErrorChanged ( ) {
// We don't know wich record was used by view index. try to use both.
if ( oldRecord ! = - 1 )
_table . RecordChanged ( oldRecord ) ;
if ( newRecord ! = - 1 )
_table . RecordChanged ( newRecord ) ;
}
internal long rowID {
get {
return _rowID ;
}
set {
ResetLastChangedColumn ( ) ;
_rowID = value ;
}
}
/// <devdoc>
/// <para>Gets the current state of the row in regards to its relationship to the table.</para>
/// </devdoc>
public DataRowState RowState {
get {
/ *
if ( oldRecord = = - 1 & & newRecord = = - 1 )
state = DataRowState . Detached ; // 2
else if ( oldRecord = = newRecord )
state = DataRowState . Unchanged ; // 2
else if ( oldRecord = = - 1 )
state = DataRowState . Added ; // 4
else if ( newRecord = = - 1 )
state = DataRowState . Deleted ; // 4
else
state = DataRowState . Modified ; // 4
* /
if ( oldRecord = = newRecord ) {
if ( oldRecord = = - 1 ) {
return DataRowState . Detached ; // 2
}
if ( 0 < _columns . ColumnsImplementingIChangeTrackingCount ) {
foreach ( DataColumn dc in _columns . ColumnsImplementingIChangeTracking ) {
object value = this [ dc ] ;
if ( ( DBNull . Value ! = value ) & & ( ( IChangeTracking ) value ) . IsChanged ) {
return DataRowState . Modified ; // 3 + _columns.columnsImplementingIChangeTracking.Count
}
}
}
return DataRowState . Unchanged ; // 3
}
else if ( oldRecord = = - 1 ) {
return DataRowState . Added ; // 2
}
else if ( newRecord = = - 1 ) {
return DataRowState . Deleted ; // 3
}
return DataRowState . Modified ; // 3
}
}
/// <devdoc>
/// <para>Gets the <see cref='System.Data.DataTable'/>
/// for which this row has a schema.</para>
/// </devdoc>
public DataTable Table {
get {
return _table ;
}
}
/// <devdoc>
/// <para>Gets or sets the data stored in the column specified by index.</para>
/// </devdoc>
public object this [ int columnIndex ] {
get {
DataColumn column = _columns [ columnIndex ] ;
int record = GetDefaultRecord ( ) ;
_table . recordManager . VerifyRecord ( record , this ) ;
VerifyValueFromStorage ( column , DataRowVersion . Default , column [ record ] ) ;
return column [ record ] ;
}
set {
DataColumn column = _columns [ columnIndex ] ;
this [ column ] = value ;
}
}
internal void CheckForLoops ( DataRelation rel ) {
// don't check for loops in the diffgram
// because there may be some holes in the rowCollection
// and index creation may fail. The check will be done
// after all the loading is done _and_ we are sure there
// are no holes in the collection.
if ( _table . fInLoadDiffgram | | ( _table . DataSet ! = null & & _table . DataSet . fInLoadDiffgram ) )
return ;
int count = _table . Rows . Count , i = 0 ;
// need to optimize this for count > 100
DataRow parent = this . GetParentRow ( rel ) ;
while ( parent ! = null ) {
if ( ( parent = = this ) | | ( i > count ) )
throw ExceptionBuilder . NestedCircular ( _table . TableName ) ;
i + + ;
parent = parent . GetParentRow ( rel ) ;
}
}
internal int GetNestedParentCount ( ) {
int count = 0 ;
DataRelation [ ] nestedParentRelations = _table . NestedParentRelations ;
foreach ( DataRelation rel in nestedParentRelations ) {
if ( rel = = null ) // don't like this but done for backward code compatability
continue ;
if ( rel . ParentTable = = _table ) // self-nested table
this . CheckForLoops ( rel ) ;
DataRow row = this . GetParentRow ( rel ) ;
if ( row ! = null ) {
count + + ;
}
}
return count ;
// Rule 1: At all times, only ONE FK "(in a row) can be non-Null
// we wont allow a row to have multiple parents, as we cant handle it , also in diffgram
}
/// <devdoc>
/// <para>Gets or sets the data stored in the column specified by
/// name.</para>
/// </devdoc>
public object this [ string columnName ] {
get {
DataColumn column = GetDataColumn ( columnName ) ;
int record = GetDefaultRecord ( ) ;
_table . recordManager . VerifyRecord ( record , this ) ;
VerifyValueFromStorage ( column , DataRowVersion . Default , column [ record ] ) ;
return column [ record ] ;
}
set {
DataColumn column = GetDataColumn ( columnName ) ;
this [ column ] = value ;
}
}
/// <devdoc>
/// <para>Gets or sets
/// the data stored in the specified <see cref='System.Data.DataColumn'/>.</para>
/// </devdoc>
public object this [ DataColumn column ] {
get {
CheckColumn ( column ) ;
int record = GetDefaultRecord ( ) ;
_table . recordManager . VerifyRecord ( record , this ) ;
VerifyValueFromStorage ( column , DataRowVersion . Default , column [ record ] ) ;
return column [ record ] ;
}
set {
CheckColumn ( column ) ;
if ( inChangingEvent ) {
throw ExceptionBuilder . EditInRowChanging ( ) ;
}
if ( ( - 1 ! = rowID ) & & column . ReadOnly ) {
throw ExceptionBuilder . ReadOnly ( column . ColumnName ) ;
}
// allow users to tailor the proposed value, or throw an exception.
// note we intentionally do not try/catch this event.
// note: we also allow user to do anything at this point
// infinite loops are possible if user calls Item or ItemArray during the event
DataColumnChangeEventArgs e = null ;
if ( _table . NeedColumnChangeEvents ) {
e = new DataColumnChangeEventArgs ( this , column , value ) ;
_table . OnColumnChanging ( e ) ;
}
if ( column . Table ! = _table ) {
// user removed column from table during OnColumnChanging event
throw ExceptionBuilder . ColumnNotInTheTable ( column . ColumnName , _table . TableName ) ;
}
if ( ( - 1 ! = rowID ) & & column . ReadOnly ) {
// user adds row to table during OnColumnChanging event
throw ExceptionBuilder . ReadOnly ( column . ColumnName ) ;
}
object proposed = ( ( null ! = e ) ? e . ProposedValue : value ) ;
if ( null = = proposed ) {
if ( column . IsValueType ) { // WebData 105963
throw ExceptionBuilder . CannotSetToNull ( column ) ;
}
proposed = DBNull . Value ;
}
bool immediate = BeginEditInternal ( ) ;
try {
int record = GetProposedRecordNo ( ) ;
_table . recordManager . VerifyRecord ( record , this ) ;
column [ record ] = proposed ;
}
catch ( Exception e1 ) {
//
if ( Common . ADP . IsCatchableOrSecurityExceptionType ( e1 ) ) {
if ( immediate ) {
Debug . Assert ( ! inChangingEvent , "how are we in a changing event to cancel?" ) ;
Debug . Assert ( - 1 ! = tempRecord , "how no propsed record to cancel?" ) ;
CancelEdit ( ) ; // WebData 107154
}
}
throw ;
}
LastChangedColumn = column ;
// note: we intentionally do not try/catch this event.
// infinite loops are possible if user calls Item or ItemArray during the event
if ( null ! = e ) {
_table . OnColumnChanged ( e ) ; // user may call CancelEdit or EndEdit
}
if ( immediate ) {
Debug . Assert ( ! inChangingEvent , "how are we in a changing event to end?" ) ;
EndEdit ( ) ;
}
}
}
/// <devdoc>
/// <para>Gets the data stored
/// in the column, specified by index and version of the data to retrieve.</para>
/// </devdoc>
public object this [ int columnIndex , DataRowVersion version ] {
get {
DataColumn column = _columns [ columnIndex ] ;
int record = GetRecordFromVersion ( version ) ;
_table . recordManager . VerifyRecord ( record , this ) ;
VerifyValueFromStorage ( column , version , column [ record ] ) ;
return column [ record ] ;
}
}
/// <devdoc>
/// <para> Gets the specified version of data stored in
/// the named column.</para>
/// </devdoc>
public object this [ string columnName , DataRowVersion version ] {
get {
DataColumn column = GetDataColumn ( columnName ) ;
int record = GetRecordFromVersion ( version ) ;
_table . recordManager . VerifyRecord ( record , this ) ;
VerifyValueFromStorage ( column , version , column [ record ] ) ;
return column [ record ] ;
}
}
/// <devdoc>
/// <para>Gets the specified version of data stored in the specified <see cref='System.Data.DataColumn'/>.</para>
/// </devdoc>
public object this [ DataColumn column , DataRowVersion version ] {
get {
CheckColumn ( column ) ;
int record = GetRecordFromVersion ( version ) ;
_table . recordManager . VerifyRecord ( record , this ) ;
VerifyValueFromStorage ( column , version , column [ record ] ) ;
return column [ record ] ;
}
}
/// <devdoc>
/// <para>Gets
/// or sets all of the values for this row through an array.</para>
/// </devdoc>
public object [ ] ItemArray {
get {
int record = GetDefaultRecord ( ) ;
_table . recordManager . VerifyRecord ( record , this ) ;
object [ ] values = new object [ _columns . Count ] ;
for ( int i = 0 ; i < values . Length ; i + + ) {
DataColumn column = _columns [ i ] ;
VerifyValueFromStorage ( column , DataRowVersion . Default , column [ record ] ) ;
values [ i ] = column [ record ] ;
}
return values ;
}
set {
if ( null = = value ) { // WebData 104372
throw ExceptionBuilder . ArgumentNull ( "ItemArray" ) ;
}
if ( _columns . Count < value . Length ) {
throw ExceptionBuilder . ValueArrayLength ( ) ;
}
DataColumnChangeEventArgs e = null ;
if ( _table . NeedColumnChangeEvents ) {
e = new DataColumnChangeEventArgs ( this ) ;
}
bool immediate = BeginEditInternal ( ) ;
for ( int i = 0 ; i < value . Length ; + + i ) {
// Empty means don't change the row.
if ( null ! = value [ i ] ) {
// may throw exception if user removes column from table during event
DataColumn column = _columns [ i ] ;
if ( ( - 1 ! = rowID ) & & column . ReadOnly ) {
throw ExceptionBuilder . ReadOnly ( column . ColumnName ) ;
}
// allow users to tailor the proposed value, or throw an exception.
// note: we intentionally do not try/catch this event.
// note: we also allow user to do anything at this point
// infinite loops are possible if user calls Item or ItemArray during the event
if ( null ! = e ) {
e . InitializeColumnChangeEvent ( column , value [ i ] ) ;
_table . OnColumnChanging ( e ) ;
}
if ( column . Table ! = _table ) {
// user removed column from table during OnColumnChanging event
throw ExceptionBuilder . ColumnNotInTheTable ( column . ColumnName , _table . TableName ) ;
}
if ( ( - 1 ! = rowID ) & & column . ReadOnly ) {
// user adds row to table during OnColumnChanging event
throw ExceptionBuilder . ReadOnly ( column . ColumnName ) ;
}
if ( tempRecord = = - 1 ) {
// user affected CancelEdit or EndEdit during OnColumnChanging event of the last value
BeginEditInternal ( ) ;
}
object proposed = ( null ! = e ) ? e . ProposedValue : value [ i ] ;
if ( null = = proposed ) {
if ( column . IsValueType ) { // WebData 105963
throw ExceptionBuilder . CannotSetToNull ( column ) ;
}
proposed = DBNull . Value ;
}
try {
// must get proposed record after each event because user may have
// called EndEdit(), AcceptChanges(), BeginEdit() during the event
int record = GetProposedRecordNo ( ) ;
_table . recordManager . VerifyRecord ( record , this ) ;
column [ record ] = proposed ;
}
catch ( Exception e1 ) {
//
if ( Common . ADP . IsCatchableOrSecurityExceptionType ( e1 ) ) {
if ( immediate ) {
Debug . Assert ( ! inChangingEvent , "how are we in a changing event to cancel?" ) ;
Debug . Assert ( - 1 ! = tempRecord , "how no propsed record to cancel?" ) ;
CancelEdit ( ) ; // WebData 107154
}
}
throw ;
}
LastChangedColumn = column ;
// note: we intentionally do not try/catch this event.
// infinite loops are possible if user calls Item or ItemArray during the event
if ( null ! = e ) {
_table . OnColumnChanged ( e ) ; // user may call CancelEdit or EndEdit
}
}
}
// proposed breaking change: if (immediate){ EndEdit(); } because table currently always fires RowChangedEvent
Debug . Assert ( ! inChangingEvent , "how are we in a changing event to end?" ) ;
EndEdit ( ) ;
}
}
/// <devdoc>
/// <para>Commits all the changes made to this row
/// since the last time <see cref='System.Data.DataRow.AcceptChanges'/> was called.</para>
/// </devdoc>
public void AcceptChanges ( ) {
IntPtr hscp ;
Bid . ScopeEnter ( out hscp , "<ds.DataRow.AcceptChanges|API> %d#\n" , ObjectID ) ;
try {
EndEdit ( ) ;
if ( this . RowState ! = DataRowState . Detached & & this . RowState ! = DataRowState . Deleted ) {
if ( _columns . ColumnsImplementingIChangeTrackingCount > 0 ) {
foreach ( DataColumn dc in _columns . ColumnsImplementingIChangeTracking ) {
object value = this [ dc ] ;
if ( DBNull . Value ! = value ) {
IChangeTracking tracking = ( IChangeTracking ) value ;
if ( tracking . IsChanged ) {
tracking . AcceptChanges ( ) ;
}
}
}
}
}
_table . CommitRow ( this ) ;
}
finally {
Bid . ScopeLeave ( ref hscp ) ;
}
}
/// <devdoc>
/// <para>Begins an edit operation on a <see cref='System.Data.DataRow'/>object.</para>
/// </devdoc>
[
EditorBrowsableAttribute ( EditorBrowsableState . Advanced ) ,
]
public void BeginEdit ( ) {
BeginEditInternal ( ) ;
}
private bool BeginEditInternal ( ) {
if ( inChangingEvent ) {
throw ExceptionBuilder . BeginEditInRowChanging ( ) ;
}
if ( tempRecord ! = - 1 ) {
if ( tempRecord < _table . recordManager . LastFreeRecord ) {
return false ; // we will not call EndEdit
}
else {
// partial fix for detached row after Table.Clear scenario
// in debug, it will have asserted earlier, but with this
// it will go get a new record for editing
tempRecord = - 1 ;
}
// shifted VerifyRecord to first make the correction, then verify
_table . recordManager . VerifyRecord ( tempRecord , this ) ;
}
if ( oldRecord ! = - 1 & & newRecord = = - 1 ) {
throw ExceptionBuilder . DeletedRowInaccessible ( ) ;
}
//
ResetLastChangedColumn ( ) ; // shouldn't have to do this
tempRecord = _table . NewRecord ( newRecord ) ;
Debug . Assert ( - 1 ! = tempRecord , "missing temp record" ) ;
Debug . Assert ( 0 = = _countColumnChange , "unexpected column change count" ) ;
Debug . Assert ( null = = _lastChangedColumn , "unexpected last column change" ) ;
return true ;
}
/// <devdoc>
/// <para>Cancels the current edit on the row.</para>
/// </devdoc>
[
EditorBrowsableAttribute ( EditorBrowsableState . Advanced ) ,
]
public void CancelEdit ( ) {
if ( inChangingEvent ) {
throw ExceptionBuilder . CancelEditInRowChanging ( ) ;
}
_table . FreeRecord ( ref tempRecord ) ;
Debug . Assert ( - 1 = = tempRecord , "unexpected temp record" ) ;
ResetLastChangedColumn ( ) ;
}
private void CheckColumn ( DataColumn column ) {
if ( column = = null ) {
throw ExceptionBuilder . ArgumentNull ( "column" ) ;
}
if ( column . Table ! = _table ) {
throw ExceptionBuilder . ColumnNotInTheTable ( column . ColumnName , _table . TableName ) ;
}
}
/// <devdoc>
/// Throws a RowNotInTableException if row isn't in table.
/// </devdoc>
internal void CheckInTable ( ) {
if ( rowID = = - 1 ) {
throw ExceptionBuilder . RowNotInTheTable ( ) ;
}
}
/// <devdoc>
/// <para>Deletes the row.</para>
/// </devdoc>
public void Delete ( ) {
if ( inDeletingEvent ) {
throw ExceptionBuilder . DeleteInRowDeleting ( ) ;
}
if ( newRecord = = - 1 )
return ;
_table . DeleteRow ( this ) ;
}
/// <devdoc>
/// <para>Ends the edit occurring on the row.</para>
/// </devdoc>
[
EditorBrowsableAttribute ( EditorBrowsableState . Advanced ) ,
]
public void EndEdit ( ) {
if ( inChangingEvent ) {
throw ExceptionBuilder . EndEditInRowChanging ( ) ;
}
if ( newRecord = = - 1 ) {
return ; // this is meaningless, detatched row case
}
if ( tempRecord ! = - 1 ) {
try {
// suppressing the ensure property changed because it's possible that no values have been modified
_table . SetNewRecord ( this , tempRecord , suppressEnsurePropertyChanged : true ) ;
}
finally {
// a constraint violation may be thrown during SetNewRecord
ResetLastChangedColumn ( ) ;
}
}
}
/// <devdoc>
/// <para>Sets the error description for a column specified by index.</para>
/// </devdoc>
public void SetColumnError ( int columnIndex , string error ) {
DataColumn column = _columns [ columnIndex ] ;
if ( column = = null )
throw ExceptionBuilder . ColumnOutOfRange ( columnIndex ) ;
SetColumnError ( column , error ) ;
}
/// <devdoc>
/// <para>Sets
/// the error description for a column specified by name.</para>
/// </devdoc>
public void SetColumnError ( string columnName , string error ) {
DataColumn column = GetDataColumn ( columnName ) ;
SetColumnError ( column , error ) ;
}
/// <devdoc>
/// <para>Sets the error description for a column specified as a <see cref='System.Data.DataColumn'/>.</para>
/// </devdoc>
public void SetColumnError ( DataColumn column , string error ) {
CheckColumn ( column ) ;
IntPtr hscp ;
Bid . ScopeEnter ( out hscp , "<ds.DataRow.SetColumnError|API> %d#, column=%d, error='%ls'\n" , ObjectID , column . ObjectID , error ) ;
try {
if ( this . error = = null ) this . error = new DataError ( ) ;
if ( GetColumnError ( column ) ! = error ) {
this . error . SetColumnError ( column , error ) ;
RowErrorChanged ( ) ;
}
}
finally {
Bid . ScopeLeave ( ref hscp ) ;
}
}
/// <devdoc>
/// <para>Gets the error description for the column specified
/// by index.</para>
/// </devdoc>
public string GetColumnError ( int columnIndex ) {
DataColumn column = _columns [ columnIndex ] ;
return GetColumnError ( column ) ;
}
/// <devdoc>
/// <para>Gets the error description for a column, specified by name.</para>
/// </devdoc>
public string GetColumnError ( string columnName ) {
DataColumn column = GetDataColumn ( columnName ) ;
return GetColumnError ( column ) ;
}
/// <devdoc>
/// <para>Gets the error description of
/// the specified <see cref='System.Data.DataColumn'/>.</para>
/// </devdoc>
public string GetColumnError ( DataColumn column ) {
CheckColumn ( column ) ;
if ( error = = null ) error = new DataError ( ) ;
return error . GetColumnError ( column ) ;
}
/// <summary>
/// Clears the errors for the row, including the <see cref='System.Data.DataRow.RowError'/>
/// and errors set with <see cref='System.Data.DataRow.SetColumnError(DataColumn, string)'/>
/// </summary>
public void ClearErrors ( ) {
if ( error ! = null ) {
error . Clear ( ) ;
RowErrorChanged ( ) ;
}
}
internal void ClearError ( DataColumn column ) {
if ( error ! = null ) {
error . Clear ( column ) ;
RowErrorChanged ( ) ;
}
}
/// <devdoc>
/// <para>Gets a value indicating whether there are errors in a columns collection.</para>
/// </devdoc>
public bool HasErrors {
get {
return ( error = = null ? false : error . HasErrors ) ;
}
}
/// <devdoc>
/// <para>Gets an array of columns that have errors.</para>
/// </devdoc>
public DataColumn [ ] GetColumnsInError ( ) {
if ( error = = null )
return DataTable . zeroColumns ;
else
return error . GetColumnsInError ( ) ;
}
public DataRow [ ] GetChildRows ( string relationName ) {
return GetChildRows ( _table . ChildRelations [ relationName ] , DataRowVersion . Default ) ;
}
public DataRow [ ] GetChildRows ( string relationName , DataRowVersion version ) {
return GetChildRows ( _table . ChildRelations [ relationName ] , version ) ;
}
/// <devdoc>
/// <para>Gets the child rows of this <see cref='System.Data.DataRow'/> using the
/// specified <see cref='System.Data.DataRelation'/>
/// .</para>
/// </devdoc>
public DataRow [ ] GetChildRows ( DataRelation relation ) {
return GetChildRows ( relation , DataRowVersion . Default ) ;
}
/// <devdoc>
/// <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>
/// </devdoc>
public DataRow [ ] GetChildRows ( DataRelation relation , DataRowVersion version ) {
if ( relation = = null )
return _table . NewRowArray ( 0 ) ;
//if (-1 == rowID)
// throw ExceptionBuilder.RowNotInTheTable();
if ( relation . DataSet ! = _table . DataSet )
throw ExceptionBuilder . RowNotInTheDataSet ( ) ;
if ( relation . ParentKey . Table ! = _table )
throw ExceptionBuilder . RelationForeignTable ( relation . ParentTable . TableName , _table . TableName ) ;
return DataRelation . GetChildRows ( relation . ParentKey , relation . ChildKey , this , version ) ;
}
internal DataColumn GetDataColumn ( string columnName ) {
DataColumn column = _columns [ columnName ] ;
if ( null ! = column ) {
return column ;
}
throw ExceptionBuilder . ColumnNotInTheTable ( columnName , _table . TableName ) ;
}
public DataRow GetParentRow ( string relationName ) {
return GetParentRow ( _table . ParentRelations [ relationName ] , DataRowVersion . Default ) ;
}
public DataRow GetParentRow ( string relationName , DataRowVersion version ) {
return GetParentRow ( _table . ParentRelations [ relationName ] , version ) ;
}
/// <devdoc>
/// <para>Gets the parent row of this <see cref='System.Data.DataRow'/> using the specified <see cref='System.Data.DataRelation'/> .</para>
/// </devdoc>
public DataRow GetParentRow ( DataRelation relation ) {
return GetParentRow ( relation , DataRowVersion . Default ) ;
}
/// <devdoc>
/// <para>Gets the parent row of this <see cref='System.Data.DataRow'/>
/// using the specified <see cref='System.Data.DataRelation'/> and <see cref='System.Data.DataRowVersion'/>.</para>
/// </devdoc>
public DataRow GetParentRow ( DataRelation relation , DataRowVersion version ) {
if ( relation = = null )
return null ;
//if (-1 == rowID)
// throw ExceptionBuilder.RowNotInTheTable();
if ( relation . DataSet ! = _table . DataSet )
throw ExceptionBuilder . RelationForeignRow ( ) ;
if ( relation . ChildKey . Table ! = _table )
throw ExceptionBuilder . GetParentRowTableMismatch ( relation . ChildTable . TableName , _table . TableName ) ;
return DataRelation . GetParentRow ( relation . ParentKey , relation . ChildKey , this , version ) ;
}
// a multiple nested child table's row can have only one non-null FK per row. So table has multiple
// parents, but a row can have only one parent. Same nested row cannot below to 2 parent rows.
internal DataRow GetNestedParentRow ( DataRowVersion version ) {
// 1) Walk over all FKs and get the non-null. 2) Get the relation. 3) Get the parent Row.
DataRelation [ ] nestedParentRelations = _table . NestedParentRelations ;
foreach ( DataRelation rel in nestedParentRelations ) {
if ( rel = = null ) // don't like this but done for backward code compatability
continue ;
if ( rel . ParentTable = = _table ) // self-nested table
this . CheckForLoops ( rel ) ;
DataRow row = this . GetParentRow ( rel , version ) ;
if ( row ! = null ) {
return row ;
}
}
return null ; // Rule 1: At all times, only ONE FK "(in a row) can be non-Null
}
// No Nested in 1-many
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public DataRow [ ] GetParentRows ( string relationName ) {
return GetParentRows ( _table . ParentRelations [ relationName ] , DataRowVersion . Default ) ;
}
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public DataRow [ ] GetParentRows ( string relationName , DataRowVersion version ) {
return GetParentRows ( _table . ParentRelations [ relationName ] , version ) ;
}
/// <devdoc>
/// <para>
/// Gets the parent rows of this <see cref='System.Data.DataRow'/> using the specified <see cref='System.Data.DataRelation'/> .
/// </para>
/// </devdoc>
public DataRow [ ] GetParentRows ( DataRelation relation ) {
return GetParentRows ( relation , DataRowVersion . Default ) ;
}
/// <devdoc>
/// <para>
/// Gets the parent rows of this <see cref='System.Data.DataRow'/> using the specified <see cref='System.Data.DataRelation'/> .
/// </para>
/// </devdoc>
public DataRow [ ] GetParentRows ( DataRelation relation , DataRowVersion version ) {
if ( relation = = null )
return _table . NewRowArray ( 0 ) ;
//if (-1 == rowID)
// throw ExceptionBuilder.RowNotInTheTable();
if ( relation . DataSet ! = _table . DataSet )
throw ExceptionBuilder . RowNotInTheDataSet ( ) ;
if ( relation . ChildKey . Table ! = _table )
throw ExceptionBuilder . GetParentRowTableMismatch ( relation . ChildTable . TableName , _table . TableName ) ;
return DataRelation . GetParentRows ( relation . ParentKey , relation . ChildKey , this , version ) ;
}
internal object [ ] GetColumnValues ( DataColumn [ ] columns ) {
return GetColumnValues ( columns , DataRowVersion . Default ) ;
}
internal object [ ] GetColumnValues ( DataColumn [ ] columns , DataRowVersion version ) {
DataKey key = new DataKey ( columns , false ) ; // temporary key, don't copy columns
return GetKeyValues ( key , version ) ;
}
internal object [ ] GetKeyValues ( DataKey key ) {
int record = GetDefaultRecord ( ) ;
return key . GetKeyValues ( record ) ;
}
internal object [ ] GetKeyValues ( DataKey key , DataRowVersion version ) {
int record = GetRecordFromVersion ( version ) ;
return key . GetKeyValues ( record ) ;
}
internal int GetCurrentRecordNo ( ) {
if ( newRecord = = - 1 )
throw ExceptionBuilder . NoCurrentData ( ) ;
return newRecord ;
}
internal int GetDefaultRecord ( ) {
if ( tempRecord ! = - 1 )
return tempRecord ;
if ( newRecord ! = - 1 ) {
return newRecord ;
}
// If row has oldRecord - this is deleted row.
if ( oldRecord = = - 1 )
throw ExceptionBuilder . RowRemovedFromTheTable ( ) ;
else
throw ExceptionBuilder . DeletedRowInaccessible ( ) ;
}
internal int GetOriginalRecordNo ( ) {
if ( oldRecord = = - 1 )
throw ExceptionBuilder . NoOriginalData ( ) ;
return oldRecord ;
}
private int GetProposedRecordNo ( ) {
if ( tempRecord = = - 1 )
throw ExceptionBuilder . NoProposedData ( ) ;
return tempRecord ;
}
internal int GetRecordFromVersion ( DataRowVersion version ) {
switch ( version ) {
case DataRowVersion . Original :
return GetOriginalRecordNo ( ) ;
case DataRowVersion . Current :
return GetCurrentRecordNo ( ) ;
case DataRowVersion . Proposed :
return GetProposedRecordNo ( ) ;
case DataRowVersion . Default :
return GetDefaultRecord ( ) ;
default :
throw ExceptionBuilder . InvalidRowVersion ( ) ;
}
}
internal DataRowVersion GetDefaultRowVersion ( DataViewRowState viewState ) {
if ( oldRecord = = newRecord ) {
if ( oldRecord = = - 1 ) {
// should be DataView.addNewRow
return DataRowVersion . Default ;
}
Debug . Assert ( 0 ! = ( DataViewRowState . Unchanged & viewState ) , "not DataViewRowState.Unchanged" ) ;
return DataRowVersion . Default ;
}
else if ( oldRecord = = - 1 ) {
Debug . Assert ( 0 ! = ( DataViewRowState . Added & viewState ) , "not DataViewRowState.Added" ) ;
return DataRowVersion . Default ;
}
else if ( newRecord = = - 1 ) {
Debug . Assert ( _action = = DataRowAction . Rollback | | 0 ! = ( DataViewRowState . Deleted & viewState ) , "not DataViewRowState.Deleted" ) ;
return DataRowVersion . Original ;
}
else if ( 0 ! = ( DataViewRowState . ModifiedCurrent & viewState ) ) {
return DataRowVersion . Default ;
}
Debug . Assert ( 0 ! = ( DataViewRowState . ModifiedOriginal & viewState ) , "not DataViewRowState.ModifiedOriginal" ) ;
return DataRowVersion . Original ;
}
internal DataViewRowState GetRecordState ( int record ) {
if ( record = = - 1 )
return DataViewRowState . None ;
if ( record = = oldRecord & & record = = newRecord )
return DataViewRowState . Unchanged ;
if ( record = = oldRecord )
return ( newRecord ! = - 1 ) ? DataViewRowState . ModifiedOriginal : DataViewRowState . Deleted ;
if ( record = = newRecord )
return ( oldRecord ! = - 1 ) ? DataViewRowState . ModifiedCurrent : DataViewRowState . Added ;
return DataViewRowState . None ;
}
internal bool HasKeyChanged ( DataKey key ) {
return HasKeyChanged ( key , DataRowVersion . Current , DataRowVersion . Proposed ) ;
}
internal bool HasKeyChanged ( DataKey key , DataRowVersion version1 , DataRowVersion version2 ) {
if ( ! HasVersion ( version1 ) | | ! HasVersion ( version2 ) )
return true ;
return ! key . RecordsEqual ( GetRecordFromVersion ( version1 ) , GetRecordFromVersion ( version2 ) ) ;
}
/// <devdoc>
/// <para>
/// Gets a value indicating whether a specified version exists.
/// </para>
/// </devdoc>
public bool HasVersion ( DataRowVersion version ) {
switch ( version ) {
case DataRowVersion . Original :
return ( oldRecord ! = - 1 ) ;
case DataRowVersion . Current :
return ( newRecord ! = - 1 ) ;
case DataRowVersion . Proposed :
return ( tempRecord ! = - 1 ) ;
case DataRowVersion . Default :
return ( tempRecord ! = - 1 | | newRecord ! = - 1 ) ;
default :
throw ExceptionBuilder . InvalidRowVersion ( ) ;
}
}
internal bool HasChanges ( ) {
if ( ! HasVersion ( DataRowVersion . Original ) | | ! HasVersion ( DataRowVersion . Current ) ) {
return true ; // if does not have original, its added row, if does not have current, its deleted row so it has changes
}
foreach ( DataColumn dc in Table . Columns ) {
if ( dc . Compare ( oldRecord , newRecord ) ! = 0 ) {
return true ;
}
}
return false ;
}
internal bool HaveValuesChanged ( DataColumn [ ] columns ) {
return HaveValuesChanged ( columns , DataRowVersion . Current , DataRowVersion . Proposed ) ;
}
internal bool HaveValuesChanged ( DataColumn [ ] columns , DataRowVersion version1 , DataRowVersion version2 ) {
for ( int i = 0 ; i < columns . Length ; i + + ) {
CheckColumn ( columns [ i ] ) ;
}
DataKey key = new DataKey ( columns , false ) ; // temporary key, don't copy columns
return HasKeyChanged ( key , version1 , version2 ) ;
}
/// <devdoc>
/// <para>
/// Gets
/// a value indicating whether the column at the specified index contains a
/// null value.
/// </para>
/// </devdoc>
public bool IsNull ( int columnIndex ) {
DataColumn column = _columns [ columnIndex ] ;
int record = GetDefaultRecord ( ) ;
return column . IsNull ( record ) ;
}
/// <devdoc>
/// <para>
/// Gets a value indicating whether the named column contains a null value.
/// </para>
/// </devdoc>
public bool IsNull ( string columnName ) {
DataColumn column = GetDataColumn ( columnName ) ;
int record = GetDefaultRecord ( ) ;
return column . IsNull ( record ) ;
}
/// <devdoc>
/// <para>
/// Gets a value indicating whether the specified <see cref='System.Data.DataColumn'/>
/// contains a null value.
/// </para>
/// </devdoc>
public bool IsNull ( DataColumn column ) {
CheckColumn ( column ) ;
int record = GetDefaultRecord ( ) ;
return column . IsNull ( record ) ;
}
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public bool IsNull ( DataColumn column , DataRowVersion version ) {
CheckColumn ( column ) ;
int record = GetRecordFromVersion ( version ) ;
return column . IsNull ( record ) ;
}
/// <devdoc>
/// <para>
/// Rejects all changes made to the row since <see cref='System.Data.DataRow.AcceptChanges'/>
/// was last called.
/// </para>
/// </devdoc>
public void RejectChanges ( ) {
IntPtr hscp ;
Bid . ScopeEnter ( out hscp , "<ds.DataRow.RejectChanges|API> %d#\n" , ObjectID ) ;
try {
if ( this . RowState ! = DataRowState . Detached ) {
if ( _columns . ColumnsImplementingIChangeTrackingCount ! = _columns . ColumnsImplementingIRevertibleChangeTrackingCount ) {
foreach ( DataColumn dc in _columns . ColumnsImplementingIChangeTracking ) {
if ( ! dc . ImplementsIRevertibleChangeTracking ) {
object value = null ;
if ( this . RowState ! = DataRowState . Deleted )
value = this [ dc ] ;
else
value = this [ dc , DataRowVersion . Original ] ;
if ( DBNull . Value ! = value ) {
if ( ( ( IChangeTracking ) value ) . IsChanged ) {
throw ExceptionBuilder . UDTImplementsIChangeTrackingButnotIRevertible ( dc . DataType . AssemblyQualifiedName ) ;
}
}
}
}
}
foreach ( DataColumn dc in _columns . ColumnsImplementingIChangeTracking ) {
object value = null ;
if ( this . RowState ! = DataRowState . Deleted )
value = this [ dc ] ;
else
value = this [ dc , DataRowVersion . Original ] ;
if ( DBNull . Value ! = value ) {
IChangeTracking tracking = ( IChangeTracking ) value ;
if ( tracking . IsChanged ) {
( ( IRevertibleChangeTracking ) value ) . RejectChanges ( ) ;
}
}
}
}
_table . RollbackRow ( this ) ;
}
finally {
Bid . ScopeLeave ( ref hscp ) ;
}
}
internal void ResetLastChangedColumn ( ) {
_lastChangedColumn = null ;
_countColumnChange = 0 ;
}
internal void SetKeyValues ( DataKey key , object [ ] keyValues ) {
bool fFirstCall = true ;
bool immediate = ( tempRecord = = - 1 ) ;
for ( int i = 0 ; i < keyValues . Length ; i + + ) {
object value = this [ key . ColumnsReference [ i ] ] ;
if ( ! value . Equals ( keyValues [ i ] ) ) {
if ( immediate & & fFirstCall ) {
fFirstCall = false ;
BeginEditInternal ( ) ;
}
this [ key . ColumnsReference [ i ] ] = keyValues [ i ] ;
}
}
if ( ! fFirstCall )
EndEdit ( ) ;
}
/// <devdoc>
/// <para>
/// Sets the specified column's value to a null value.
/// </para>
/// </devdoc>
protected void SetNull ( DataColumn column ) {
this [ column ] = DBNull . Value ;
}
internal void SetNestedParentRow ( DataRow parentRow , bool setNonNested ) {
if ( parentRow = = null ) {
SetParentRowToDBNull ( ) ;
return ;
}
foreach ( DataRelation relation in _table . ParentRelations ) {
if ( relation . Nested | | setNonNested ) {
if ( relation . ParentKey . Table = = parentRow . _table ) {
object [ ] parentKeyValues = parentRow . GetKeyValues ( relation . ParentKey ) ;
this . SetKeyValues ( relation . ChildKey , parentKeyValues ) ;
if ( relation . Nested ) {
if ( parentRow . _table = = _table )
this . CheckForLoops ( relation ) ;
else
this . GetParentRow ( relation ) ;
}
}
}
}
}
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public void SetParentRow ( DataRow parentRow ) {
SetNestedParentRow ( parentRow , true ) ;
}
/// <devdoc>
/// <para>
/// Sets current row's parent row with specified relation.
/// </para>
/// </devdoc>
public void SetParentRow ( DataRow parentRow , DataRelation relation ) {
if ( relation = = null ) {
SetParentRow ( parentRow ) ;
return ;
}
if ( parentRow = = null ) {
SetParentRowToDBNull ( relation ) ;
return ;
}
//if (-1 == rowID)
// throw ExceptionBuilder.ChildRowNotInTheTable();
//if (-1 == parentRow.rowID)
// throw ExceptionBuilder.ParentRowNotInTheTable();
if ( _table . DataSet ! = parentRow . _table . DataSet )
throw ExceptionBuilder . ParentRowNotInTheDataSet ( ) ;
if ( relation . ChildKey . Table ! = _table )
throw ExceptionBuilder . SetParentRowTableMismatch ( relation . ChildKey . Table . TableName , _table . TableName ) ;
if ( relation . ParentKey . Table ! = parentRow . _table )
throw ExceptionBuilder . SetParentRowTableMismatch ( relation . ParentKey . Table . TableName , parentRow . _table . TableName ) ;
object [ ] parentKeyValues = parentRow . GetKeyValues ( relation . ParentKey ) ;
this . SetKeyValues ( relation . ChildKey , parentKeyValues ) ;
}
internal void SetParentRowToDBNull ( ) {
//if (-1 == rowID)
// throw ExceptionBuilder.ChildRowNotInTheTable();
foreach ( DataRelation relation in _table . ParentRelations )
SetParentRowToDBNull ( relation ) ;
}
internal void SetParentRowToDBNull ( DataRelation relation ) {
Debug . Assert ( relation ! = null , "The relation should not be null here." ) ;
//if (-1 == rowID)
// throw ExceptionBuilder.ChildRowNotInTheTable();
if ( relation . ChildKey . Table ! = _table )
throw ExceptionBuilder . SetParentRowTableMismatch ( relation . ChildKey . Table . TableName , _table . TableName ) ;
object [ ] parentKeyValues = new object [ 1 ] ;
parentKeyValues [ 0 ] = DBNull . Value ;
this . SetKeyValues ( relation . ChildKey , parentKeyValues ) ;
}
public void SetAdded ( ) {
if ( this . RowState = = DataRowState . Unchanged ) {
_table . SetOldRecord ( this , - 1 ) ;
}
else {
throw ExceptionBuilder . SetAddedAndModifiedCalledOnnonUnchanged ( ) ;
}
}
public void SetModified ( ) {
if ( this . RowState = = DataRowState . Unchanged ) {
tempRecord = _table . NewRecord ( newRecord ) ;
if ( tempRecord ! = - 1 ) {
// suppressing the ensure property changed because no values have changed
_table . SetNewRecord ( this , tempRecord , suppressEnsurePropertyChanged : true ) ;
}
}
else {
throw ExceptionBuilder . SetAddedAndModifiedCalledOnnonUnchanged ( ) ;
}
}
/ *
RecordList contains the empty column storage needed . We need to copy the existing record values into this storage .
* /
internal int CopyValuesIntoStore ( ArrayList storeList , ArrayList nullbitList , int storeIndex ) {
int recordCount = 0 ;
if ( oldRecord ! = - 1 ) { //Copy original record for the row in Unchanged, Modified, Deleted state.
for ( int i = 0 ; i < _columns . Count ; i + + ) {
_columns [ i ] . CopyValueIntoStore ( oldRecord , storeList [ i ] , ( BitArray ) nullbitList [ i ] , storeIndex ) ;
}
recordCount + + ;
storeIndex + + ;
}
DataRowState state = RowState ;
if ( ( DataRowState . Added = = state ) | | ( DataRowState . Modified = = state ) ) { //Copy current record for the row in Added, Modified state.
for ( int i = 0 ; i < _columns . Count ; i + + ) {
_columns [ i ] . CopyValueIntoStore ( newRecord , storeList [ i ] , ( BitArray ) nullbitList [ i ] , storeIndex ) ;
}
recordCount + + ;
storeIndex + + ;
}
if ( - 1 ! = tempRecord ) { //Copy temp record for the row in edit mode.
for ( int i = 0 ; i < _columns . Count ; i + + ) {
_columns [ i ] . CopyValueIntoStore ( tempRecord , storeList [ i ] , ( BitArray ) nullbitList [ i ] , storeIndex ) ;
}
recordCount + + ;
storeIndex + + ;
}
return recordCount ;
}
[Conditional("DEBUG")]
private void VerifyValueFromStorage ( DataColumn column , DataRowVersion version , object valueFromStorage ) {
// Dev11 900390: ignore deleted rows by adding "newRecord != -1" condition - we do not evaluate computed rows if they are deleted
if ( column . DataExpression ! = null & & ! inChangingEvent & & tempRecord = = - 1 & & newRecord ! = - 1 )
{
// for unchanged rows, check current if original is asked for.
// this is because by design, there is only single storage for an unchanged row.
if ( version = = DataRowVersion . Original & & oldRecord = = newRecord ) {
version = DataRowVersion . Current ;
}
// There are various known issues detected by this assert for non-default versions,
// for example DevDiv2 bug 73753
// Since changes consitutute breaking change (either way customer will get another result),
// we decided not to fix them in Dev 11
Debug . Assert ( valueFromStorage . Equals ( column . DataExpression . Evaluate ( this , version ) ) ,
"Value from storage does lazily computed expression value" ) ;
}
}
}
public sealed class DataRowBuilder {
internal readonly DataTable _table ;
internal int _record ;
internal DataRowBuilder ( DataTable table , int record ) {
_table = table ;
_record = record ;
}
}
}