536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
1329 lines
53 KiB
C#
1329 lines
53 KiB
C#
//------------------------------------------------------------------------------
|
|
// <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;
|
|
}
|
|
}
|
|
}
|