File: fx\src\data\System\Data\DataRow.cs
Project: ndp\System.Data.csproj (System.Data)
//------------------------------------------------------------------------------
// <copyright file="DataRow.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="true" primary="false">Microsoft</owner>
//------------------------------------------------------------------------------
 
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;
        }
    }
}