// Tim Coleman <tim@timcoleman.com>
// Ville Palo <vi64pa@koti.soon.fi>
// Alan Tam Siu Lung <Tam@SiuLung.com>
+// Sureshkumar T <tsureshkumar@novell.com>
//
// (C) Ximian, Inc 2002
// (C) Daniel Morgan 2002, 2003
//
using System;
+using System.Data.Common;
using System.Collections;
using System.Globalization;
using System.Xml;
+#if NET_2_0
+using System.ComponentModel;
+#endif
namespace System.Data {
/// <summary>
/// Represents a row of data in a DataTable.
/// </summary>
+#if !NET_2_0
[Serializable]
+#endif
public class DataRow
{
#region Fields
private ArrayList _columnErrors;
private string rowError;
- private DataRowState rowState;
internal int xmlRowID = 0;
internal bool _nullConstraintViolation;
private string _nullConstraintMessage;
- private bool editing = false;
private bool _hasParentCollection;
private bool _inChangingEvent;
private int _rowId;
// Get the row id from the builder.
_rowId = builder._rowId;
- _proposed = _table.RecordCache.NewRecord();
- // Initialise the data columns of the row with the dafault values, if any
- // TODO : should proposed version be available immediately after record creation ?
- foreach(DataColumn column in _table.Columns) {
- column.DataContainer.CopyValue(_table.DefaultValuesRowIndex,_proposed);
- }
-
rowError = String.Empty;
- //on first creating a DataRow it is always detached.
- rowState = DataRowState.Detached;
-
- ArrayList aiColumns = _table.Columns.AutoIncrmentColumns;
- foreach (DataColumn dc in aiColumns) {
- this [dc] = dc.AutoIncrementValue();
- }
-
// create mapped XmlDataElement
DataSet ds = _table.DataSet;
if (ds != null && ds._xmlDataDocument != null)
set {
if (columnIndex < 0 || columnIndex > _table.Columns.Count)
throw new IndexOutOfRangeException ();
- if (rowState == DataRowState.Deleted)
+ if (RowState == DataRowState.Deleted)
throw new DeletedRowInaccessibleException ();
DataColumn column = _table.Columns[columnIndex];
}
CheckValue (value, column);
-
- bool orginalEditing = editing;
+ bool orginalEditing = Proposed >= 0;
if (!orginalEditing) {
BeginEdit ();
}
- column[_proposed] = value;
+ column[Proposed] = value;
_table.ChangedDataColumn (this, column, value);
if (!orginalEditing) {
EndEdit ();
}
}
+ /// <summary>
+ /// Set a value for the column into the offset specified by the version.<br>
+ /// If the value is auto increment or null, necessary auto increment value
+ /// or the default value will be used.
+ /// </summary>
+ internal void SetValue (int column, object value, int version)
+ {
+ DataColumn dc = Table.Columns[column];
+
+ if (value == null && ! dc.AutoIncrement) // set default value / auto increment
+ value = dc.DefaultValue;
+
+ Table.ChangingDataColumn (this, dc, value);
+ CheckValue (value, dc);
+ if ( ! dc.AutoIncrement)
+ dc [version] = value;
+ else if (_proposed >= 0 && _proposed != version) // proposed holds the AI
+ dc [version] = dc [_proposed];
+ }
+
/// <summary>
/// Gets the data stored in the column, specified by index and version of the data to
/// retrieve.
get {
if (columnIndex < 0 || columnIndex > _table.Columns.Count)
throw new IndexOutOfRangeException ();
- // Accessing deleted rows
- if (!_inExpressionEvaluation && rowState == DataRowState.Deleted && version != DataRowVersion.Original)
- throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
DataColumn column = _table.Columns[columnIndex];
- if (column.Expression != String.Empty) {
- object o = column.CompiledExpression.Eval (this);
- return Convert.ChangeType (o, column.DataType);
- }
-
int recordIndex = IndexFromVersion(version);
- if (recordIndex >= 0) {
+ if (column.Expression != String.Empty) {
+ object o = column.CompiledExpression.Eval (this);
+ if (o != null && o != DBNull.Value) {
+ o = Convert.ChangeType (o, column.DataType);
+ }
+ column[recordIndex] = o;
return column[recordIndex];
}
- if (rowState == DataRowState.Detached && version == DataRowVersion.Default && _proposed < 0)
- throw new RowNotInTableException("This row has been removed from a table and does not have any data. BeginEdit() will allow creation of new data in this row.");
-
- throw new VersionNotFoundException (Locale.GetText ("There is no " + version.ToString () + " data to access."));
+ return column[recordIndex];
}
}
/// </summary>
public object[] ItemArray {
get {
- // row not in table
- if (rowState == DataRowState.Detached)
- throw new RowNotInTableException("This row has been removed from a table and does not have any data. BeginEdit() will allow creation of new data in this row.");
// Accessing deleted rows
- if (rowState == DataRowState.Deleted)
+ if (RowState == DataRowState.Deleted)
throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
+
+ int index = 0;
+ if (RowState == DataRowState.Detached)
+ // Check if datarow is removed from the table.
+ if (Proposed < 0)
+ throw new RowNotInTableException(
+ "This row has been removed from a table and does not have any data."
+ +" BeginEdit() will allow creation of new data in this row.");
+ else
+ index = Proposed;
+ else
+ index = Current;
object[] items = new object[_table.Columns.Count];
- foreach(DataColumn column in _table.Columns) {
- items[column.Ordinal] = column[_current];
- }
+
+ foreach(DataColumn column in _table.Columns)
+ items[column.Ordinal] = column[index];
return items;
}
set {
if (value.Length > _table.Columns.Count)
throw new ArgumentException ();
- if (rowState == DataRowState.Deleted)
+ if (RowState == DataRowState.Deleted)
throw new DeletedRowInaccessibleException ();
- bool orginalEditing = editing;
- if (!orginalEditing) {
- BeginEdit ();
- }
- object newVal = null;
+ BeginEdit ();
+
DataColumnChangeEventArgs e = new DataColumnChangeEventArgs();
foreach(DataColumn column in _table.Columns) {
int i = column.Ordinal;
- newVal = (i < value.Length) ? value[i] : null;
+ object newVal = (i < value.Length) ? value[i] : null;
+
+ if (newVal == null)
+ continue;
e.Initialize(this, column, newVal);
- _table.RaiseOnColumnChanged(e);
CheckValue (e.ProposedValue, column);
- column[_proposed] = e.ProposedValue;
- }
- if (!orginalEditing) {
- EndEdit ();
+ _table.RaiseOnColumnChanging(e);
+ column[Proposed] = e.ProposedValue;
+ _table.RaiseOnColumnChanged(e);
}
+
+ EndEdit ();
}
}
/// </summary>
public DataRowState RowState {
get {
- return rowState;
+ //return rowState;
+ if ((Original == -1) && (Current == -1))
+ return DataRowState.Detached;
+ if (Original == Current)
+ return DataRowState.Unchanged;
+ if (Original == -1)
+ return DataRowState.Added;
+ if (Current == -1)
+ return DataRowState.Deleted;
+ return DataRowState.Modified;
}
}
}
}
+ internal int Original
+ {
+ get {
+ return _original;
+ }
+ set {
+ if (Table != null) {
+ //Table.RecordCache[_original] = null;
+ Table.RecordCache[value] = this;
+ }
+ _original = value;
+ }
+ }
+
+ internal int Current
+ {
+ get {
+ return _current;
+ }
+ set {
+ if (Table != null) {
+ //Table.RecordCache[_current] = null;
+ Table.RecordCache[value] = this;
+ }
+ _current = value;
+ }
+ }
+
+ internal int Proposed
+ {
+ get {
+ return _proposed;
+ }
+ set {
+ if (Table != null) {
+ //Table.RecordCache[_proposed] = null;
+ Table.RecordCache[value] = this;
+ }
+ _proposed = value;
+ }
+ }
+
#endregion
#region Methods
//FIXME?: Couldn't find a way to set the RowState when adding the DataRow
//to a Datatable so I added this method. Delete if there is a better way.
internal void AttachRow() {
- if (_current >= 0) {
- Table.RecordCache.DisposeRecord(_current);
+ if (Proposed != -1) {
+ if (Current >= 0) {
+ Table.RecordCache.DisposeRecord(Current);
+ }
+ Current = Proposed;
+ Proposed = -1;
}
- _current = _proposed;
- _proposed = -1;
- rowState = DataRowState.Added;
}
//FIXME?: Couldn't find a way to set the RowState when removing the DataRow
//from a Datatable so I added this method. Delete if there is a better way.
internal void DetachRow() {
- if (_proposed >= 0) {
- _table.RecordCache.DisposeRecord(_proposed);
- _proposed = -1;
+ if (Proposed >= 0) {
+ _table.RecordCache.DisposeRecord(Proposed);
+ if (Proposed == Current) {
+ Current = -1;
+ }
+ if (Proposed == Original) {
+ Original = -1;
+ }
+ Proposed = -1;
+ }
+
+ if (Current >= 0) {
+ _table.RecordCache.DisposeRecord(Current);
+ if (Current == Original) {
+ Original = -1;
+ }
+ Current = -1;
}
+
+ if (Original >= 0) {
+ _table.RecordCache.DisposeRecord(Original);
+ Original = -1;
+ }
+
_rowId = -1;
_hasParentCollection = false;
- rowState = DataRowState.Detached;
+ }
+
+ internal void ImportRecord(int record)
+ {
+ if (HasVersion(DataRowVersion.Proposed)) {
+ Table.RecordCache.DisposeRecord(Proposed);
+ }
+
+ Proposed = record;
+
+ foreach(DataColumn column in Table.Columns.AutoIncrmentColumns) {
+ column.UpdateAutoIncrementValue(column.DataContainer.GetInt64(Proposed));
+ }
+
}
private void CheckValue (object v, DataColumn col)
}
}
- internal void SetValuesFromDataRecord(IDataRecord record, int[] mapping)
- {
- if ( mapping.Length > Table.Columns.Count)
- throw new ArgumentException ();
-
-// bool orginalEditing = editing;
-// if (!orginalEditing) {
-// BeginEdit ();
-// }
-
- if (!HasVersion(DataRowVersion.Proposed)) {
- _proposed = Table.RecordCache.NewRecord();
- }
-
- try {
- for(int i=0; i < mapping.Length; i++) {
- DataColumn column = Table.Columns[i];
- column.DataContainer.SetItemFromDataRecord(_proposed, record,mapping[i]);
- if ( column.AutoIncrement ) {
- column.UpdateAutoIncrementValue(column.DataContainer.GetInt64(_proposed));
- }
- }
- }
- catch (Exception e){
- Table.RecordCache.DisposeRecord(_proposed);
- _proposed = -1;
- throw e;
- }
-
-// if (!orginalEditing) {
-// EndEdit ();
-// }
- }
-
/// <summary>
/// Gets or sets the custom error description for a row.
/// </summary>
internal int IndexFromVersion(DataRowVersion version)
{
- if (HasVersion(version))
- {
- int recordIndex;
- switch (version) {
- case DataRowVersion.Default:
- if (editing || rowState == DataRowState.Detached) {
- recordIndex = _proposed;
- }
- else {
- recordIndex = _current;
- }
- break;
- case DataRowVersion.Proposed:
- recordIndex = _proposed;
- break;
- case DataRowVersion.Current:
- recordIndex = _current;
- break;
- case DataRowVersion.Original:
- recordIndex = _original;
- break;
- default:
- throw new ArgumentException ();
- }
- return recordIndex;
+ switch (version) {
+ case DataRowVersion.Default:
+ if (Proposed >= 0)
+ return Proposed;
+
+ if (Current >= 0)
+ return Current;
+
+ if (Original < 0)
+ throw new RowNotInTableException("This row has been removed from a table and does not have any data. BeginEdit() will allow creation of new data in this row.");
+
+ throw new DeletedRowInaccessibleException("Deleted row information cannot be accessed through the row.");
+
+ case DataRowVersion.Proposed:
+ return AssertValidVersionIndex(version, Proposed);
+ case DataRowVersion.Current:
+ return AssertValidVersionIndex(version, Current);
+ case DataRowVersion.Original:
+ return AssertValidVersionIndex(version, Original);
+ default:
+ throw new DataException ("Version must be Original, Current, or Proposed.");
}
- return -1;
+ }
+
+ private int AssertValidVersionIndex(DataRowVersion version, int index) {
+ if (index >= 0)
+ return index;
+
+ throw new VersionNotFoundException(String.Format("There is no {0} data to accces.", version));
+ }
+
+ internal DataRowVersion VersionFromIndex(int index) {
+ if (index < 0)
+ throw new ArgumentException("Index must not be negative.");
+
+ // the order of ifs matters
+ if (index == Current)
+ return DataRowVersion.Current;
+ if (index == Original)
+ return DataRowVersion.Original;
+ if (index == Proposed)
+ return DataRowVersion.Proposed;
+
+ throw new ArgumentException(String.Format("The index {0} does not belong to this row.", index) );
}
internal XmlDataDocument.XmlDataElement DataElement {
DataColumn column = _table.Columns[columnName];
_table.ChangingDataColumn (this, column, val);
- if (_original < 0 || _original == _current) {
- // This really creates a new record version if one does not exist
- _original = Table.RecordCache.NewRecord();
+ if (Original < 0 || Original == Current) {
+ Original = Table.RecordCache.NewRecord();
}
CheckValue (val, column);
- column[_original] = val;
- rowState = DataRowState.Modified;
+ column[Original] = val;
}
/// <summary>
public void AcceptChanges ()
{
EndEdit(); // in case it hasn't been called
- switch (rowState) {
- case DataRowState.Unchanged:
+
+ _table.ChangingDataRow (this, DataRowAction.Commit);
+ CheckChildRows(DataRowAction.Commit);
+ switch (RowState) {
+ case DataRowState.Unchanged:
return;
case DataRowState.Added:
case DataRowState.Modified:
- rowState = DataRowState.Unchanged;
+ int original = Original;
+ DataRowState oldState = RowState;
+ if (Original >= 0) {
+ Table.RecordCache.DisposeRecord(Original);
+ }
+ Original = Current;
+ foreach (Index index in Table.Indexes)
+ index.Update(this, original, DataRowVersion.Original, oldState);
break;
case DataRowState.Deleted:
+ Table.DeleteRowFromIndexes(this);
_table.Rows.RemoveInternal (this);
DetachRow();
break;
case DataRowState.Detached:
throw new RowNotInTableException("Cannot perform this operation on a row not in the table.");
}
- // Accept from detached
- if (_original >= 0) {
- Table.RecordCache.DisposeRecord(_original);
- }
- _original = _current;
+
+ _table.ChangedDataRow (this, DataRowAction.Commit);
}
/// <summary>
/// Begins an edit operation on a DataRow object.
/// </summary>
+#if NET_2_0
+ [EditorBrowsable (EditorBrowsableState.Advanced)]
+#endif
public void BeginEdit ()
{
if (_inChangingEvent)
throw new InRowChangingEventException("Cannot call BeginEdit inside an OnRowChanging event.");
- if (rowState == DataRowState.Deleted)
+ if (RowState == DataRowState.Deleted)
throw new DeletedRowInaccessibleException ();
+
if (!HasVersion (DataRowVersion.Proposed)) {
- _proposed = Table.RecordCache.NewRecord();
- foreach(DataColumn column in Table.Columns) {
- column.DataContainer.CopyValue(_current,_proposed);
+ Proposed = Table.RecordCache.NewRecord();
+ int from = HasVersion(DataRowVersion.Current) ? Current : Table.DefaultValuesRowIndex;
+ for(int i = 0; i < Table.Columns.Count; i++){
+ DataColumn column = Table.Columns[i];
+ column.DataContainer.CopyValue(from,Proposed);
}
}
- // setting editing to true stops validations on the row
- editing = true;
}
/// <summary>
/// Cancels the current edit on the row.
/// </summary>
+#if NET_2_0
+ [EditorBrowsable (EditorBrowsableState.Advanced)]
+#endif
public void CancelEdit ()
{
- if (_inChangingEvent)
+ if (_inChangingEvent) {
throw new InRowChangingEventException("Cannot call CancelEdit inside an OnRowChanging event.");
- editing = false;
+ }
+
if (HasVersion (DataRowVersion.Proposed)) {
- Table.RecordCache.DisposeRecord(_proposed);
- _proposed = -1;
- if (rowState == DataRowState.Modified) {
- rowState = DataRowState.Unchanged;
- }
+ int oldRecord = Proposed;
+ DataRowState oldState = RowState;
+ Table.RecordCache.DisposeRecord(Proposed);
+ Proposed = -1;
+
+ foreach(Index index in Table.Indexes)
+ index.Update(this,oldRecord, DataRowVersion.Proposed, oldState);
}
}
public void Delete ()
{
_table.DeletingDataRow(this, DataRowAction.Delete);
- switch (rowState) {
+ switch (RowState) {
case DataRowState.Added:
// check what to do with child rows
CheckChildRows(DataRowAction.Delete);
DetachRow();
break;
case DataRowState.Deleted:
- break;
+ case DataRowState.Detached:
+ break;
default:
// check what to do with child rows
CheckChildRows(DataRowAction.Delete);
- _table.DeleteRowFromIndexes (this);
- rowState = DataRowState.Deleted;
break;
}
+ if (Current >= 0) {
+ int current = Current;
+ DataRowState oldState = RowState;
+ if (Current != Original) {
+ _table.RecordCache.DisposeRecord(Current);
+ }
+ Current = -1;
+ foreach(Index index in Table.Indexes)
+ index.Update(this, current, DataRowVersion.Current, oldState);
+ }
_table.DeletedDataRow(this, DataRowAction.Delete);
}
foreach (DataTable table in _table.DataSet.Tables)
{
// loop on all ForeignKeyConstrain of the table.
- foreach (ForeignKeyConstraint fk in table.Constraints.ForeignKeyConstraints)
- {
- if (fk.RelatedTable == _table)
- {
- Rule rule;
- if (action == DataRowAction.Delete)
- rule = fk.DeleteRule;
- else
- rule = fk.UpdateRule;
- CheckChildRows(fk, action, rule);
+ foreach (Constraint constraint in table.Constraints) {
+ if (constraint is ForeignKeyConstraint) {
+ ForeignKeyConstraint fk = (ForeignKeyConstraint) constraint;
+ if (fk.RelatedTable == _table) {
+ switch (action) {
+ case DataRowAction.Delete:
+ CheckChildRows(fk, action, fk.DeleteRule);
+ break;
+ case DataRowAction.Commit:
+ case DataRowAction.Rollback:
+ if (fk.AcceptRejectRule != AcceptRejectRule.None)
+ CheckChildRows(fk, action, Rule.Cascade);
+ break;
+ default:
+ CheckChildRows(fk, action, fk.UpdateRule);
+ break;
+ }
+ }
}
}
}
private void CheckChildRows(ForeignKeyConstraint fkc, DataRowAction action, Rule rule)
{
- DataRow[] childRows = GetChildRows(fkc, DataRowVersion.Default);
+ DataRow[] childRows = GetChildRows(fkc, DataRowVersion.Current);
switch (rule)
{
case Rule.Cascade: // delete or change all relted rows.
for (int j = 0; j < childRows.Length; j++)
{
// if action is delete we delete all child rows
- if (action == DataRowAction.Delete)
- {
+ switch(action) {
+ case DataRowAction.Delete: {
if (childRows[j].RowState != DataRowState.Deleted)
childRows[j].Delete();
+
+ break;
}
// if action is change we change the values in the child row
- else if (action == DataRowAction.Change)
- {
+ case DataRowAction.Change: {
// change only the values in the key columns
// set the childcolumn value to the new parent row value
- for (int k = 0; k < fkc.Columns.Length; k++)
- childRows[j][fkc.Columns[k]] = this[fkc.RelatedColumns[k], DataRowVersion.Proposed];
+ for (int k = 0; k < fkc.Columns.Length; k++)
+ if (!fkc.RelatedColumns [k].DataContainer [Original].Equals (fkc.RelatedColumns [k].DataContainer [Proposed]))
+ childRows[j][fkc.Columns[k]] = this[fkc.RelatedColumns[k], DataRowVersion.Proposed];
+
+ break;
+ }
+
+ case DataRowAction.Rollback: {
+ if (childRows[j].RowState != DataRowState.Unchanged)
+ childRows[j].RejectChanges ();
+ break;
+ }
}
}
}
/// <summary>
/// Ends the edit occurring on the row.
/// </summary>
+#if NET_2_0
+ [EditorBrowsable (EditorBrowsableState.Advanced)]
+#endif
public void EndEdit ()
{
if (_inChangingEvent)
throw new InRowChangingEventException("Cannot call EndEdit inside an OnRowChanging event.");
- if (rowState == DataRowState.Detached)
- {
- editing = false;
+
+ if (RowState == DataRowState.Detached)
return;
- }
- CheckReadOnlyStatus();
if (HasVersion (DataRowVersion.Proposed))
{
+ CheckReadOnlyStatus();
+
_inChangingEvent = true;
try
{
{
_inChangingEvent = false;
}
- if (rowState == DataRowState.Unchanged)
- rowState = DataRowState.Modified;
- //Calling next method validates UniqueConstraints
- //and ForeignKeys.
- try
- {
- if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
- _table.Rows.ValidateDataRowInternal(this);
- }
- catch (Exception e)
- {
- editing = false;
- Table.RecordCache.DisposeRecord(_proposed);
- _proposed = -1;
- throw e;
+ DataRowState oldState = RowState;
+
+ int oldRecord = Current;
+ Current = Proposed;
+ Proposed = -1;
+
+ if (!Table._duringDataLoad) {
+ foreach(Index index in Table.Indexes) {
+ index.Update(this,oldRecord, DataRowVersion.Current, oldState);
+ }
}
- // Now we are going to check all child rows of current row.
- // In the case the cascade is true the child rows will look up for
- // parent row. since lookup in index is always on current,
- // we have to move proposed version of current row to current
- // in the case of check child row failure we are rolling
- // current row state back.
- int backup = _current;
- _current = _proposed;
- bool editing_backup = editing;
- editing = false;
try {
- // check all child rows.
+ AssertConstraints();
+
+ // restore previous state to let the cascade update to find the rows
+ Proposed = Current;
+ Current = oldRecord;
+
CheckChildRows(DataRowAction.Change);
- _proposed = -1;
- if (_original != backup) {
- Table.RecordCache.DisposeRecord(backup);
+
+ // apply new state
+ Current = Proposed;
+ Proposed = -1;
+ }
+ catch {
+ int proposed = Proposed >= 0 ? Proposed : Current;
+ Current = oldRecord;
+ if (!Table._duringDataLoad) {
+ foreach(Index index in Table.Indexes) {
+ index.Update(this,proposed, DataRowVersion.Current, RowState);
+ }
}
+ throw;
}
- catch (Exception ex) {
- // if check child rows failed - rollback to previous state
- // i.e. restore proposed and current versions
- _proposed = _current;
- _current = backup;
- editing = editing_backup;
- // since we failed - propagate an exception
- throw ex;
+
+ if (Original != oldRecord) {
+ Table.RecordCache.DisposeRecord(oldRecord);
}
+
+ // Note : row state must not be changed before all the job on indexes finished,
+ // since the indexes works with recods rather than with rows and the decision
+ // which of row records to choose depends on row state.
_table.ChangedDataRow(this, DataRowAction.Change);
}
}
public DataRow[] GetChildRows (DataRelation relation, DataRowVersion version)
{
if (relation == null)
- return new DataRow[0];
+ return Table.NewRowArray(0);
- //if (this.Table == null || RowState == DataRowState.Detached)
if (this.Table == null)
throw new RowNotInTableException("This row has been removed from a table and does not have any data. BeginEdit() will allow creation of new data in this row.");
DataColumn[] parentColumns = relation.ParentColumns;
DataColumn[] childColumns = relation.ChildColumns;
int numColumn = parentColumns.Length;
- if (HasVersion(version))
- {
- object[] vals = new object[parentColumns.Length];
- for (int i = 0; i < vals.Length; i++)
- vals[i] = this[parentColumns[i], version];
-
- foreach (DataRow row in relation.ChildTable.Rows)
- {
- bool allColumnsMatch = false;
- if (row.HasVersion(DataRowVersion.Default))
- {
- allColumnsMatch = true;
- for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt)
- {
- if (!vals[columnCnt].Equals(
- row[childColumns[columnCnt], DataRowVersion.Default]))
- {
+ DataRow[] result = null;
+
+ int versionIndex = IndexFromVersion(version);
+ int tmpRecord = relation.ChildTable.RecordCache.NewRecord();
+
+ try {
+ for (int i = 0; i < numColumn; i++) {
+ // according to MSDN: the DataType value for both columns must be identical.
+ childColumns[i].DataContainer.CopyValue(parentColumns[i].DataContainer, versionIndex, tmpRecord);
+ }
+
+ Index index = relation.ChildTable.FindIndex(childColumns);
+
+ if (index != null) {
+ int[] records = index.FindAll(tmpRecord);
+ result = relation.ChildTable.NewRowArray(records.Length);
+ for(int i=0; i < records.Length; i++) {
+ result[i] = relation.ChildTable.RecordCache[records[i]];
+ }
+ }
+ else {
+ foreach (DataRow row in relation.ChildTable.Rows) {
+ bool allColumnsMatch = false;
+ if (row.HasVersion(DataRowVersion.Default)) {
+ allColumnsMatch = true;
+ int childIndex = row.IndexFromVersion(DataRowVersion.Default);
+ for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) {
+ if (childColumns[columnCnt].DataContainer.CompareValues(childIndex, tmpRecord) != 0) {
allColumnsMatch = false;
break;
}
}
if (allColumnsMatch) rows.Add(row);
}
- }else
- throw new VersionNotFoundException("There is no " + version + " data to accces.");
+ result = relation.ChildTable.NewRowArray(rows.Count);
+ rows.CopyTo(result, 0);
+ }
+ }
+ finally {
+ relation.ChildTable.RecordCache.DisposeRecord(tmpRecord);
+ }
- DataRow[] result = relation.ChildTable.NewRowArray(rows.Count);
- rows.CopyTo(result, 0);
return result;
}
DataColumn[] parentColumns = fkc.RelatedColumns;
DataColumn[] childColumns = fkc.Columns;
int numColumn = parentColumns.Length;
- if (HasVersion(version)) {
+
Index index = fkc.Index;
+
+ int curIndex = IndexFromVersion(version);
+ int tmpRecord = fkc.Table.RecordCache.NewRecord();
+ for (int i = 0; i < numColumn; i++) {
+ // according to MSDN: the DataType value for both columns must be identical.
+ childColumns[i].DataContainer.CopyValue(parentColumns[i].DataContainer, curIndex, tmpRecord);
+ }
+
+ try {
if (index != null) {
// get the child rows from the index
- Node[] childNodes = index.FindAllSimple (parentColumns, IndexFromVersion(version));
- for (int i = 0; i < childNodes.Length; i++) {
- rows.Add (childNodes[i].Row);
+ int[] childRecords = index.FindAll(tmpRecord);
+ for (int i = 0; i < childRecords.Length; i++) {
+ rows.Add (childColumns[i].Table.RecordCache[childRecords[i]]);
}
}
else { // if there is no index we search manualy.
- int curIndex = IndexFromVersion(DataRowVersion.Default);
- int tmpRecord = fkc.Table.RecordCache.NewRecord();
-
- try {
- for (int i = 0; i < numColumn; i++) {
- // according to MSDN: the DataType value for both columns must be identical.
- childColumns[i].DataContainer.CopyValue(parentColumns[i].DataContainer, curIndex, tmpRecord);
- }
-
foreach (DataRow row in fkc.Table.Rows) {
bool allColumnsMatch = false;
if (row.HasVersion(DataRowVersion.Default)) {
}
}
}
+ }
finally {
fkc.Table.RecordCache.DisposeRecord(tmpRecord);
}
- }
- }else
- throw new VersionNotFoundException("There is no " + version + " data to accces.");
DataRow[] result = fkc.Table.NewRowArray(rows.Count);
rows.CopyTo(result, 0);
/// </summary>
public string GetColumnError (DataColumn column)
{
- return GetColumnError (_table.Columns.IndexOf(column));
+ if (column == null)
+ throw new ArgumentNullException("column");
+
+ int index = _table.Columns.IndexOf(column);
+ if (index < 0)
+ throw new ArgumentException(String.Format("Column '{0}' does not belong to table {1}.", column.ColumnName, Table.TableName));
+
+ return GetColumnError (index);
}
/// <summary>
{
// TODO: Caching for better preformance
if (relation == null)
- return new DataRow[0];
+ return Table.NewRowArray(0);
if (this.Table == null)
throw new RowNotInTableException("This row has been removed from a table and does not have any data. BeginEdit() will allow creation of new data in this row.");
DataColumn[] parentColumns = relation.ParentColumns;
DataColumn[] childColumns = relation.ChildColumns;
int numColumn = parentColumns.Length;
- if (HasVersion(version)) {
- Index indx = relation.ParentTable.GetIndexByColumns (parentColumns);
- if (indx != null &&
- (Table == null || Table.DataSet == null ||
- Table.DataSet.EnforceConstraints)) { // get the child rows from the index
- Node[] childNodes = indx.FindAllSimple(childColumns, IndexFromVersion(version));
- for (int i = 0; i < childNodes.Length; i++) {
- rows.Add (childNodes[i].Row);
- }
- }
- else { // no index so we have to search manualy.
- int curIndex = IndexFromVersion(DataRowVersion.Default);
+
+ int curIndex = IndexFromVersion(version);
int tmpRecord = relation.ParentTable.RecordCache.NewRecord();
- try {
for (int i = 0; i < numColumn; i++) {
// according to MSDN: the DataType value for both columns must be identical.
parentColumns[i].DataContainer.CopyValue(childColumns[i].DataContainer, curIndex, tmpRecord);
}
+ try {
+ Index index = relation.ParentTable.FindIndex(parentColumns);
+ if (index != null) { // get the parent rows from the index
+ int[] parentRecords = index.FindAll(tmpRecord);
+ for (int i = 0; i < parentRecords.Length; i++) {
+ rows.Add (parentColumns[i].Table.RecordCache[parentRecords[i]]);
+ }
+ }
+ else { // no index so we have to search manualy.
foreach (DataRow row in relation.ParentTable.Rows) {
bool allColumnsMatch = false;
if (row.HasVersion(DataRowVersion.Default)) {
}
}
}
+ }
finally {
relation.ParentTable.RecordCache.DisposeRecord(tmpRecord);
}
- }
- }else
- throw new VersionNotFoundException("There is no " + version + " data to accces.");
DataRow[] result = relation.ParentTable.NewRowArray(rows.Count);
rows.CopyTo(result, 0);
{
switch (version) {
case DataRowVersion.Default:
- if (rowState == DataRowState.Deleted && !_inExpressionEvaluation)
- return false;
- if (rowState == DataRowState.Detached)
- return _proposed >= 0;
- return true;
+ return (Proposed >= 0 || Current >= 0);
case DataRowVersion.Proposed:
- if (rowState == DataRowState.Deleted && !_inExpressionEvaluation)
- return false;
- return _proposed >= 0;
+ return Proposed >= 0;
case DataRowVersion.Current:
- if ((rowState == DataRowState.Deleted && !_inExpressionEvaluation) || rowState == DataRowState.Detached)
- return false;
- return _current >= 0;
+ return Current >= 0;
case DataRowVersion.Original:
- if (rowState == DataRowState.Detached)
- return false;
- return _original >= 0;
+ return Original >= 0;
+ default:
+ return IndexFromVersion(version) >= 0;
}
- return false;
}
/// <summary>
/// </summary>
public bool IsNull (DataColumn column, DataRowVersion version)
{
+ object o = this[column,version];
return column.DataContainer.IsNull(IndexFromVersion(version));
}
throw new RowNotInTableException("This row has been removed from a table and does not have any data. BeginEdit() will allow creation of new data in this row.");
// If original is null, then nothing has happened since AcceptChanges
// was last called. We have no "original" to go back to.
- if (HasVersion(DataRowVersion.Original)) {
- if (_current >= 0 ) {
- Table.RecordCache.DisposeRecord(_current);
- }
- _current = _original;
-
- _table.ChangedDataRow (this, DataRowAction.Rollback);
- CancelEdit ();
- switch (rowState)
- {
- case DataRowState.Added:
- _table.DeleteRowFromIndexes (this);
- _table.Rows.RemoveInternal (this);
- break;
- case DataRowState.Modified:
- if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
- _table.Rows.ValidateDataRowInternal(this);
- rowState = DataRowState.Unchanged;
- break;
- case DataRowState.Deleted:
- rowState = DataRowState.Unchanged;
- if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
- _table.Rows.ValidateDataRowInternal(this);
- break;
- }
-
- }
- else {
- // If rows are just loaded via Xml the original values are null.
- // So in this case we have to remove all columns.
- // FIXME: I'm not realy sure, does this break something else, but
- // if so: FIXME ;)
-
- if ((rowState & DataRowState.Added) > 0)
- {
- _table.DeleteRowFromIndexes (this);
- _table.Rows.RemoveInternal (this);
- // if row was in Added state we move it to Detached.
- DetachRow();
- }
+
+ _table.ChangedDataRow (this, DataRowAction.Rollback);
+ CancelEdit ();
+
+ //TODO : Need to Verify the constraints..
+ switch (RowState) {
+ case DataRowState.Added:
+ _table.DeleteRowFromIndexes (this);
+ _table.Rows.RemoveInternal (this);
+ DetachRow ();
+ break;
+ case DataRowState.Modified:
+ Table.RecordCache.DisposeRecord (Current);
+ CheckChildRows (DataRowAction.Rollback);
+ Table.DeleteRowFromIndexes(this);
+ Current = Original;
+ break;
+ case DataRowState.Deleted:
+ CheckChildRows (DataRowAction.Rollback);
+ Table.DeleteRowFromIndexes(this);
+ Current = Original;
+ break;
}
}
if (parentRow != null && _table.DataSet != parentRow.Table.DataSet)
throw new ArgumentException();
+
+ if (RowState == DataRowState.Detached && !HasVersion(DataRowVersion.Default)) {
+ // the row should have default data to access, i.e. we can do this for the newly created row, but not for the row once deleted from the table
+ throw new RowNotInTableException("This row has been removed from a table and does not have any data. BeginEdit() will allow creation of new data in this row.");
+ }
BeginEdit();
- if (relation == null)
- {
- foreach (DataRelation parentRel in _table.ParentRelations)
- {
- DataColumn[] childCols = parentRel.ChildKeyConstraint.Columns;
- DataColumn[] parentCols = parentRel.ChildKeyConstraint.RelatedColumns;
-
- for (int i = 0; i < parentCols.Length; i++)
- {
- if (parentRow == null)
- this[childCols[i].Ordinal] = DBNull.Value;
- else
- this[childCols[i].Ordinal] = parentRow[parentCols[i]];
- }
-
- }
+
+ IEnumerable relations;
+ if (relation == null) {
+ relations = _table.ParentRelations;
+ }
+ else {
+ relations = new DataRelation[] { relation };
}
- else
+
+ foreach (DataRelation rel in relations)
{
- DataColumn[] childCols = relation.ChildKeyConstraint.Columns;
- DataColumn[] parentCols = relation.ChildKeyConstraint.RelatedColumns;
-
+ DataColumn[] childCols = rel.ChildColumns;
+ DataColumn[] parentCols = rel.ParentColumns;
+
for (int i = 0; i < parentCols.Length; i++)
{
- if (parentRow == null)
- this[childCols[i].Ordinal] = DBNull.Value;
- else
- this[childCols[i].Ordinal] = parentRow[parentCols[i]];
+ if (parentRow == null) {
+ childCols[i].DataContainer[Proposed] = DBNull.Value;
+ }
+ else {
+ int defaultIdx = parentRow.IndexFromVersion(DataRowVersion.Default);
+ childCols[i].DataContainer.CopyValue(parentCols[i].DataContainer,defaultIdx,Proposed);
+ }
}
+
}
+
EndEdit();
}
- //Copy all values of this DataaRow to the row parameter.
+ //Copy all values of this DataRow to the row parameter.
internal void CopyValuesToRow(DataRow row)
{
if (row == null)
if (row == this)
throw new ArgumentException("'row' is the same as this object");
+ // create target records if missing.
+ if (HasVersion(DataRowVersion.Original)) {
+ if (row.Original < 0)
+ row.Original = row.Table.RecordCache.NewRecord();
+ else if (row.Original == row.Current) {
+ row.Original = row.Table.RecordCache.NewRecord();
+ row.Table.RecordCache.CopyRecord (row.Table, row.Current, row.Original);
+ }
+ } else {
+ if (row.Original > 0) {
+ if (row.Original != row.Current)
+ row.Table.RecordCache.DisposeRecord(row.Original);
+ row.Original = -1;
+ }
+ }
+
+ if (HasVersion(DataRowVersion.Current)) {
+ if (Current == Original) {
+ if (row.Current >= 0)
+ row.Table.RecordCache.DisposeRecord(row.Current);
+ row.Current = row.Original;
+ }else {
+ if (row.Current < 0)
+ row.Current = row.Table.RecordCache.NewRecord();
+ }
+ } else {
+ if (row.Current > 0) {
+ row.Table.RecordCache.DisposeRecord(row.Current);
+ row.Current = -1;
+ }
+ }
+
+ if (HasVersion(DataRowVersion.Proposed)) {
+ if (row.Proposed < 0)
+ row.Proposed = row.Table.RecordCache.NewRecord();
+ } else {
+ if (row.Proposed > 0) {
+ row.Table.RecordCache.DisposeRecord(row.Proposed);
+ row.Proposed = -1;
+ }
+ }
+
+ // copy source record values to target records
foreach(DataColumn column in Table.Columns) {
DataColumn targetColumn = row.Table.Columns[column.ColumnName];
//if a column with the same name exists in both rows copy the values
if(targetColumn != null) {
- int index = targetColumn.Ordinal;
if (HasVersion(DataRowVersion.Original)) {
- if (row._original < 0) {
- row._original = row.Table.RecordCache.NewRecord();
- }
- object val = column[_original];
+ object val = column[Original];
row.CheckValue(val, targetColumn);
- targetColumn[row._original] = val;
+ targetColumn[row.Original] = val;
}
- if (HasVersion(DataRowVersion.Current)) {
- if (row._current < 0) {
- row._current = row.Table.RecordCache.NewRecord();
- }
- object val = column[_current];
+
+ if (HasVersion(DataRowVersion.Current)
+ && Current != Original) {
+ object val = column[Current];
row.CheckValue(val, targetColumn);
- targetColumn[row._current] = val;
+ targetColumn[row.Current] = val;
}
+
if (HasVersion(DataRowVersion.Proposed)) {
- if (row._proposed < 0) {
- row._proposed = row.Table.RecordCache.NewRecord();
- }
- object val = column[row._proposed];
+ object val = column[row.Proposed];
row.CheckValue(val, targetColumn);
- targetColumn[row._proposed] = val;
+ targetColumn[row.Proposed] = val;
}
-
- //Saving the current value as the column value
- row[index] = targetColumn[row._current];
-
}
}
- CopyState(row);
+ if (HasErrors) {
+ CopyErrors(row);
+ }
}
- // Copy row state - rowState and errors
- internal void CopyState(DataRow row)
+ //Merge all values of this DataRow to the row parameter according to merge rules.
+ internal void MergeValuesToRow(DataRow row, bool preserveChanges)
+ {
+ if (row == null)
+ throw new ArgumentNullException("row");
+ if (row == this)
+ throw new ArgumentException("'row' is the same as this object");
+
+ // Original values are anyway copied
+ if (HasVersion(DataRowVersion.Original)) {
+ if (row.Original < 0)
+ row.Original = row.Table.RecordCache.NewRecord();
+ else if (row.Original == row.Current
+ && !(Original == Current && ! preserveChanges)) {
+ row.Original = row.Table.RecordCache.NewRecord();
+ row.Table.RecordCache.CopyRecord (row.Table, row.Current, row.Original);
+ }
+ } else {
+ if (row.Original == row.Current) { // if target has same current, better create new original
+ row.Original = row.Table.RecordCache.NewRecord();
+ row.Table.RecordCache.CopyRecord (row.Table, row.Current, row.Original);
+ }
+ }
+
+ // if i have current, push all
+ if (HasVersion(DataRowVersion.Current)) {
+ if (! preserveChanges && row.Current < 0)
+ row.Current = row.Table.RecordCache.NewRecord();
+ } else {
+ if (row.Current > 0 && ! preserveChanges) {
+ row.Table.RecordCache.DisposeRecord(row.Current);
+ row.Current = -1;
+ }
+ }
+
+ // copy source record values to target records
+ foreach(DataColumn column in Table.Columns) {
+ DataColumn targetColumn = row.Table.Columns[column.ColumnName];
+ //if a column with the same name exists in both rows copy the values
+ if(targetColumn != null) {
+ if (HasVersion(DataRowVersion.Original)) {
+ object val = column[Original];
+ row.CheckValue(val, targetColumn);
+ targetColumn[row.Original] = val;
+ }
+
+ if (HasVersion(DataRowVersion.Current)
+ && !preserveChanges) {
+ object val = column[Current];
+ row.CheckValue(val, targetColumn);
+ targetColumn[row.Current] = val;
+ }
+ }
+ }
+ if (HasErrors) {
+ CopyErrors(row);
+ }
+ }
+
+ internal void CopyErrors(DataRow row)
{
- row.rowState = RowState;
row.RowError = RowError;
- row.ColumnErrors = (ArrayList)ColumnErrors.Clone();
+ DataColumn[] errorColumns = GetColumnsInError();
+ foreach(DataColumn col in errorColumns) {
+ DataColumn targetColumn = row.Table.Columns[col.ColumnName];
+ row.SetColumnError(targetColumn,GetColumnError(col));
+ }
}
internal bool IsRowChanged(DataRowState rowState) {
}
}
+ internal void Validate() {
+ Table.AddRowToIndexes(this);
+ AssertConstraints();
+ }
+
+ void AssertConstraints() {
+ if (Table == null || Table._duringDataLoad)
+ return;
+
+ if (Table.DataSet != null && !Table.DataSet.EnforceConstraints)
+ return;
+ foreach(DataColumn column in Table.Columns) {
+ if (!column.AllowDBNull && IsNull(column)) {
+ throw new NoNullAllowedException(_nullConstraintMessage);
+ }
+ }
+
+ foreach(Constraint constraint in Table.Constraints) {
+ try {
+ constraint.AssertConstraint(this);
+ }
+ catch(Exception e) {
+ Table.DeleteRowFromIndexes(this);
+ throw e;
+ }
+ }
+ }
+
internal void CheckNullConstraints()
{
if (_nullConstraintViolation) {
}
}
- internal void CheckReadOnlyStatus()
- {
- if (HasVersion(DataRowVersion.Proposed)) {
+ internal void CheckReadOnlyStatus() {
int defaultIdx = IndexFromVersion(DataRowVersion.Default);
foreach(DataColumn column in Table.Columns) {
- if ((column.DataContainer.CompareValues(defaultIdx,_proposed) != 0) && column.ReadOnly) {
+ if ((column.DataContainer.CompareValues(defaultIdx,Proposed) != 0) && column.ReadOnly) {
throw new ReadOnlyException();
}
}
}
- }
#endregion // Methods
- }
-
+#if NET_2_0
+ /// <summary>
+ /// This method loads a given value into the existing row affecting versions,
+ /// state based on the LoadOption. The matrix of changes for this method are as
+ /// mentioned in the DataTable.Load (IDataReader, LoadOption) method.
+ /// </summary>
+ internal void Load (object [] values, LoadOption loadOption)
+ {
+ Index index = null;
+ int temp = -1;
+
+ if (loadOption == LoadOption.OverwriteChanges
+ || (loadOption == LoadOption.PreserveChanges
+ && RowState == DataRowState.Unchanged)) {
+ Table.ChangingDataRow (this, DataRowAction.ChangeCurrentAndOriginal);
+ temp = Table.CreateRecord (values);
+ Table.DeleteRowFromIndexes(this);
+ if (HasVersion (DataRowVersion.Original) && Current != Original)
+ Table.RecordCache.DisposeRecord (Original);
+ Original = temp;
+
+ if (HasVersion (DataRowVersion.Current))
+ Table.RecordCache.DisposeRecord (Current);
+ Current = temp;
+ Table.AddRowToIndexes(this);
+ Table.ChangedDataRow (this, DataRowAction.ChangeCurrentAndOriginal);
+ return;
+ }
+
+ if (loadOption == LoadOption.PreserveChanges) {
+ Table.ChangingDataRow (this, DataRowAction.ChangeOriginal);
+ temp = Table.CreateRecord (values);
+ if (HasVersion (DataRowVersion.Original) && Current != Original)
+ Table.RecordCache.DisposeRecord (Original);
+ Original = temp;
+ Table.ChangedDataRow (this, DataRowAction.ChangeOriginal);
+ return;
+ }
+
+ // Upsert
+ if (RowState != DataRowState.Deleted) {
+ int rindex = HasVersion (DataRowVersion.Proposed) ? Proposed : Current;
+ temp = Table.CreateRecord (values);
+ if (RowState == DataRowState.Added
+ || Table.CompareRecords (rindex, temp) != 0) {
+ Table.ChangingDataRow (this, DataRowAction.Change);
+ Table.DeleteRowFromIndexes(this);
+ if (HasVersion (DataRowVersion.Proposed)) {
+ Table.RecordCache.DisposeRecord (Proposed);
+ Proposed = -1;
+ }
+
+ if (Original != Current)
+ Table.RecordCache.DisposeRecord (Current);
+ Current = temp;
+ Table.AddRowToIndexes(this);
+ Table.ChangedDataRow (this, DataRowAction.Change);
+ } else {
+ Table.ChangingDataRow (this, DataRowAction.Nothing);
+ Table.RecordCache.DisposeRecord (temp);
+ Table.ChangedDataRow (this, DataRowAction.Nothing);
+ }
+ }
+ }
+#endif // NET_2_0
+ }
}