//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // // [....] // [....] //------------------------------------------------------------------------------ namespace System.Data { using System; using System.Collections; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.Xml; /// /// Represents a row of data in a . /// 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); /// /// /// Initializes a new instance of the DataRow. /// /// /// Constructs a row from the builder. Only for internal usage.. /// /// 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(" %d#, value=%d\n", ObjectID, value); _rbTreeNodeId = value; } } /// /// Gets or sets the custom error description for a row. /// public string RowError { get { return(error == null ? String.Empty :error.Text); } set { Bid.Trace(" %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; } } /// /// Gets the current state of the row in regards to its relationship to the table. /// 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 } } /// /// Gets the /// for which this row has a schema. /// public DataTable Table { get { return _table; } } /// /// Gets or sets the data stored in the column specified by index. /// 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 } /// /// Gets or sets the data stored in the column specified by /// name. /// 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; } } /// /// Gets or sets /// the data stored in the specified . /// 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(); } } } /// /// Gets the data stored /// in the column, specified by index and version of the data to retrieve. /// 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]; } } /// /// Gets the specified version of data stored in /// the named column. /// 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]; } } /// /// Gets the specified version of data stored in the specified . /// 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]; } } /// /// Gets /// or sets all of the values for this row through an array. /// 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(); } } /// /// Commits all the changes made to this row /// since the last time was called. /// public void AcceptChanges() { IntPtr hscp; Bid.ScopeEnter(out hscp, " %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); } } /// /// Begins an edit operation on a object. /// [ 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; } /// /// Cancels the current edit on the row. /// [ 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); } } /// /// Throws a RowNotInTableException if row isn't in table. /// internal void CheckInTable() { if (rowID == -1) { throw ExceptionBuilder.RowNotInTheTable(); } } /// /// Deletes the row. /// public void Delete() { if (inDeletingEvent) { throw ExceptionBuilder.DeleteInRowDeleting(); } if (newRecord == -1) return; _table.DeleteRow(this); } /// /// Ends the edit occurring on the row. /// [ 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(); } } } /// /// Sets the error description for a column specified by index. /// public void SetColumnError(int columnIndex, string error) { DataColumn column = _columns[columnIndex]; if (column == null) throw ExceptionBuilder.ColumnOutOfRange(columnIndex); SetColumnError(column, error); } /// /// Sets /// the error description for a column specified by name. /// public void SetColumnError(string columnName, string error) { DataColumn column = GetDataColumn(columnName); SetColumnError(column, error); } /// /// Sets the error description for a column specified as a . /// public void SetColumnError(DataColumn column, string error) { CheckColumn(column); IntPtr hscp; Bid.ScopeEnter(out hscp, " %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); } } /// /// Gets the error description for the column specified /// by index. /// public string GetColumnError(int columnIndex) { DataColumn column = _columns[columnIndex]; return GetColumnError(column); } /// /// Gets the error description for a column, specified by name. /// public string GetColumnError(string columnName) { DataColumn column = GetDataColumn(columnName); return GetColumnError(column); } /// /// Gets the error description of /// the specified . /// public string GetColumnError(DataColumn column) { CheckColumn(column); if (error == null) error = new DataError(); return error.GetColumnError(column); } /// /// Clears the errors for the row, including the /// and errors set with /// public void ClearErrors() { if (error != null) { error.Clear(); RowErrorChanged(); } } internal void ClearError(DataColumn column) { if (error != null) { error.Clear(column); RowErrorChanged(); } } /// /// Gets a value indicating whether there are errors in a columns collection. /// public bool HasErrors { get { return(error == null ? false : error.HasErrors); } } /// /// Gets an array of columns that have errors. /// 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); } /// /// Gets the child rows of this using the /// specified /// . /// public DataRow[] GetChildRows(DataRelation relation) { return GetChildRows(relation, DataRowVersion.Default); } /// /// Gets the child rows of this using the specified and the specified /// 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); } /// /// Gets the parent row of this using the specified . /// public DataRow GetParentRow(DataRelation relation) { return GetParentRow(relation, DataRowVersion.Default); } /// /// Gets the parent row of this /// using the specified and . /// 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 /// /// [To be supplied.] /// public DataRow[] GetParentRows(string relationName) { return GetParentRows(_table.ParentRelations[relationName], DataRowVersion.Default); } /// /// [To be supplied.] /// public DataRow[] GetParentRows(string relationName, DataRowVersion version) { return GetParentRows(_table.ParentRelations[relationName], version); } /// /// /// Gets the parent rows of this using the specified . /// /// public DataRow[] GetParentRows(DataRelation relation) { return GetParentRows(relation, DataRowVersion.Default); } /// /// /// Gets the parent rows of this using the specified . /// /// 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)); } /// /// /// Gets a value indicating whether a specified version exists. /// /// 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); } /// /// /// Gets /// a value indicating whether the column at the specified index contains a /// null value. /// /// public bool IsNull(int columnIndex) { DataColumn column = _columns[columnIndex]; int record = GetDefaultRecord(); return column.IsNull(record); } /// /// /// Gets a value indicating whether the named column contains a null value. /// /// public bool IsNull(string columnName) { DataColumn column = GetDataColumn(columnName); int record = GetDefaultRecord(); return column.IsNull(record); } /// /// /// Gets a value indicating whether the specified /// contains a null value. /// /// public bool IsNull(DataColumn column) { CheckColumn(column); int record = GetDefaultRecord(); return column.IsNull(record); } /// /// [To be supplied.] /// public bool IsNull(DataColumn column, DataRowVersion version) { CheckColumn(column); int record = GetRecordFromVersion(version); return column.IsNull(record); } /// /// /// Rejects all changes made to the row since /// was last called. /// /// public void RejectChanges() { IntPtr hscp; Bid.ScopeEnter(out hscp, " %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(); } /// /// /// Sets the specified column's value to a null value. /// /// 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); } } } } } /// /// [To be supplied.] /// public void SetParentRow(DataRow parentRow) { SetNestedParentRow(parentRow, true); } /// /// /// Sets current row's parent row with specified relation. /// /// 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; } } }