// Rodrigo Moya <rodrigo@ximian.com>
// Daniel Morgan <danmorg@sc.rr.com>
// Tim Coleman <tim@timcoleman.com>
+// Ville Palo <vi64pa@koti.soon.fi>
+// Alan Tam Siu Lung <Tam@SiuLung.com>
//
// (C) Ximian, Inc 2002
-// (C) Daniel Morgan 2002
+// (C) Daniel Morgan 2002, 2003
// Copyright (C) 2002 Tim Coleman
//
using System;
using System.Collections;
+using System.Globalization;
+using System.Xml;
namespace System.Data {
/// <summary>
private string[] 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;
+ internal bool _inExpressionEvaluation = false;
+
+ private XmlDataDocument.XmlDataElement mappedElement;
#endregion
protected internal DataRow (DataRowBuilder builder)
{
_table = builder.Table;
+ // Get the row id from the builder.
+ _rowId = builder._rowId;
original = null;
- proposed = null;
- current = new object[_table.Columns.Count];
-
+
+ proposed = new object[_table.Columns.Count];
+ // Initialise the data coloumns of the row with the dafault values, if any
+ for (int c = 0; c < _table.Columns.Count; c++)
+ {
+ if(_table.Columns [c].DefaultValue == null)
+ proposed[c] = DBNull.Value;
+ else
+ proposed [c] = _table.Columns[c].DefaultValue;
+ }
+
columnErrors = new string[_table.Columns.Count];
rowError = String.Empty;
- //rowState = DataRowState.Unchanged;
-
//on first creating a DataRow it is always detached.
rowState = DataRowState.Detached;
+
+ ArrayList aiColumns = _table.Columns.AutoIncrmentColumns;
+ foreach (string col in aiColumns) {
+ DataColumn dc = _table.Columns[col];
+ this [dc] = dc.AutoIncrementValue();
+ }
+ _table.Columns.CollectionChanged += new System.ComponentModel.CollectionChangeEventHandler(CollectionChanged);
+
+ // create mapped XmlDataElement
+ DataSet ds = _table.DataSet;
+ if (ds != null && ds._xmlDataDocument != null)
+ mappedElement = new XmlDataDocument.XmlDataElement (this, _table.Prefix, _table.TableName, _table.Namespace, ds._xmlDataDocument);
}
+
#endregion
#region Properties
/// Gets a value indicating whether there are errors in a row.
/// </summary>
public bool HasErrors {
- [MonoTODO]
get {
- throw new NotImplementedException ();
+ if (RowError != string.Empty)
+ return true;
+
+ for (int i= 0; i < columnErrors.Length; i++){
+ if (columnErrors[i] != null && columnErrors[i] != string.Empty)
+ return true;
+ }
+
+ return false;
}
}
/// Gets or sets the data stored in the column specified by name.
/// </summary>
public object this[string columnName] {
- [MonoTODO] //FIXME: will return different values depending on DataRowState
- get { return this[columnName, DataRowVersion.Current]; }
- [MonoTODO]
+ get { return this[columnName, DataRowVersion.Default]; }
set {
- DataColumn column = _table.Columns[columnName];
- if (column == null)
+ int columnIndex = _table.Columns.IndexOf (columnName);
+ if (columnIndex == -1)
throw new IndexOutOfRangeException ();
- this[column] = value;
+ this[columnIndex] = value;
}
}
/// Gets or sets the data stored in specified DataColumn
/// </summary>
public object this[DataColumn column] {
- [MonoTODO] //FIXME: will return different values depending on DataRowState
- get { return this[column, DataRowVersion.Current]; }
-
- [MonoTODO]
+
+ get {
+ return this[column, DataRowVersion.Default];}
set {
- bool objIsDBNull = value.Equals(DBNull.Value);
- if (column == null)
- throw new ArgumentNullException ();
int columnIndex = _table.Columns.IndexOf (column);
if (columnIndex == -1)
- throw new ArgumentException ();
- if(column.DataType != value.GetType ()) {
- if(objIsDBNull == true && column.AllowDBNull == false)
- throw new InvalidCastException ();
- //else if(objIsDBNull == false)
- // throw new InvalidCastException ();
- }
-
- if (rowState == DataRowState.Deleted)
- throw new DeletedRowInaccessibleException ();
-
- //MS Implementation doesn't seem to create the proposed or original
- //set of values when a datarow has just been created or added to the
- //DataTable and AcceptChanges() has not been called yet.
-
- if(rowState == DataRowState.Detached || rowState == DataRowState.Added) {
- if(objIsDBNull)
- current[columnIndex] = DBNull.Value;
- else
- current[columnIndex] = value;
- }
- else {
- BeginEdit (); // implicitly called
-
- rowState = DataRowState.Modified;
-
- if(objIsDBNull)
- proposed[columnIndex] = DBNull.Value;
- else
- proposed[columnIndex] = value;
- }
-
- //Don't know if this is the rigth thing to do,
- //but it fixes my test. I believe the MS docs only say this
- //method is implicitly called when calling AcceptChanges()
-
- // EndEdit (); // is this the right thing to do?
-
+ throw new ArgumentException ("The column does not belong to this table.");
+ this[columnIndex] = value;
}
}
/// Gets or sets the data stored in column specified by index.
/// </summary>
public object this[int columnIndex] {
- [MonoTODO] //FIXME: not always supposed to return current
- get { return this[columnIndex, DataRowVersion.Current]; }
- [MonoTODO]
+ get { return this[columnIndex, DataRowVersion.Default]; }
set {
- DataColumn column = _table.Columns[columnIndex]; //FIXME: will throw
- if (column == null)
+ if (columnIndex < 0 || columnIndex > _table.Columns.Count)
throw new IndexOutOfRangeException ();
- this[column] = value;
+ if (rowState == DataRowState.Deleted)
+ throw new DeletedRowInaccessibleException ();
+ DataColumn column = _table.Columns[columnIndex];
+ _table.ChangingDataColumn (this, column, value);
+
+
+ bool orginalEditing = editing;
+ if (!orginalEditing) BeginEdit ();
+ object v = SetColumnValue (value, column, columnIndex);
+ proposed[columnIndex] = v;
+ _table.ChangedDataColumn (this, column, v);
+ if (!orginalEditing) EndEdit ();
}
}
/// Gets the specified version of data stored in the named column.
/// </summary>
public object this[string columnName, DataRowVersion version] {
- [MonoTODO]
get {
- DataColumn column = _table.Columns[columnName]; //FIXME: will throw
- if (column == null)
+ int columnIndex = _table.Columns.IndexOf (columnName);
+ if (columnIndex == -1)
throw new IndexOutOfRangeException ();
- return this[column, version];
+ return this[columnIndex, version];
}
}
/// </summary>
public object this[DataColumn column, DataRowVersion version] {
get {
- if (column == null)
- throw new ArgumentNullException ();
-
- int columnIndex = _table.Columns.IndexOf (column);
-
- if (columnIndex == -1)
- throw new ArgumentException ();
-
- if (version == DataRowVersion.Default)
- return column.DefaultValue;
-
- if (!HasVersion (version))
- throw new VersionNotFoundException ();
-
- switch (version)
- {
- case DataRowVersion.Proposed:
- return proposed[columnIndex];
- case DataRowVersion.Current:
- return current[columnIndex];
- case DataRowVersion.Original:
- return original[columnIndex];
- default:
- throw new ArgumentException ();
- }
+ if (column.Table != Table)
+ throw new ArgumentException ("The column does not belong to this table.");
+ int columnIndex = column.Ordinal;
+ return this[columnIndex, version];
}
}
/// retrieve.
/// </summary>
public object this[int columnIndex, DataRowVersion version] {
- [MonoTODO]
get {
- DataColumn column = _table.Columns[columnIndex]; //FIXME: throws
- if (column == null)
+ if (columnIndex < 0 || columnIndex > _table.Columns.Count)
throw new IndexOutOfRangeException ();
- return this[column, version];
+ // Accessing deleted rows
+ if (!_inExpressionEvaluation && rowState == DataRowState.Deleted && version != DataRowVersion.Original)
+ throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
+
+ DataColumn col = _table.Columns[columnIndex];
+ if (col.Expression != String.Empty) {
+ object o = col.CompiledExpression.Eval (this);
+ return Convert.ChangeType (o, col.DataType);
+ }
+
+ if (HasVersion(version))
+ {
+ switch (version)
+ {
+ case DataRowVersion.Default:
+ if (editing || rowState == DataRowState.Detached)
+ return proposed[columnIndex];
+ return current[columnIndex];
+ case DataRowVersion.Proposed:
+ return proposed[columnIndex];
+ case DataRowVersion.Current:
+ return current[columnIndex];
+ case DataRowVersion.Original:
+ return original[columnIndex];
+ default:
+ throw new ArgumentException ();
+ }
+ }
+ if (rowState == DataRowState.Detached && version == DataRowVersion.Default && proposed == 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.");
+
+ throw new VersionNotFoundException (Locale.GetText ("There is no " + version.ToString () + " data to access."));
}
}
+
+ internal void SetOriginalValue (string columnName, object val)
+ {
+ int columnIndex = _table.Columns.IndexOf (columnName);
+ DataColumn column = _table.Columns[columnIndex];
+ _table.ChangingDataColumn (this, column, val);
+
+ if (original == null) original = new object [_table.Columns.Count];
+ val = SetColumnValue (val, column, columnIndex);
+ original[columnIndex] = val;
+ rowState = DataRowState.Modified;
+ }
/// <summary>
/// Gets or sets all of the values for this row through an array.
/// </summary>
- [MonoTODO]
public object[] ItemArray {
- get { return current; }
+ 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)
+ throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
+
+ return current;
+ }
set {
if (value.Length > _table.Columns.Count)
throw new ArgumentException ();
if (rowState == DataRowState.Deleted)
throw new DeletedRowInaccessibleException ();
+
+ object[] newItems = new object[_table.Columns.Count];
+ object v = null;
+ for (int i = 0; i < _table.Columns.Count; i++) {
- for (int i = 0; i < value.Length; i += 1)
- {
- if (_table.Columns[i].ReadOnly && value[i] != this[i])
- throw new ReadOnlyException ();
+ if (i < value.Length)
+ v = value[i];
+ else
+ v = null;
+
+ newItems[i] = SetColumnValue (v, _table.Columns[i], i);
+ }
+
+ bool orginalEditing = editing;
+ if (!orginalEditing) BeginEdit ();
+ proposed = newItems;
+ if (!orginalEditing) EndEdit ();
+ }
+ }
+
+ private object SetColumnValue (object v, DataColumn col, int index)
+ {
+ object newval = null;
+
+ if (_hasParentCollection && col.ReadOnly && v != this[index])
+ throw new ReadOnlyException ();
+
+ if (v == null)
+ {
+ if (col.DataType.ToString().Equals("System.Guid"))
+ throw new ArgumentException("Cannot set column to be null, Please use DBNull instead");
- if (value[i] == null)
+ if(col.DefaultValue != DBNull.Value)
+ {
+ newval = col.DefaultValue;
+ }
+ else if(col.AutoIncrement == true && CanAccess(index,DataRowVersion.Default))
+ {
+ // AutoIncrement column is already filled,
+ // so it just need to return existing value.
+ newval = this [index];
+ }
+ else
+ {
+ if (!col.AllowDBNull)
{
- if (!_table.Columns[i].AllowDBNull)
- throw new NoNullAllowedException ();
- continue;
+ //Constraint violations during data load is raise in DataTable EndLoad
+ this._nullConstraintViolation = true;
+ if (this.Table._duringDataLoad) {
+ this.Table._nullConstraintViolationDuringDataLoad = true;
+ }
+ _nullConstraintMessage = "Column '" + col.ColumnName + "' does not allow nulls.";
}
-
- //FIXME: int strings can be converted to ints
- if (_table.Columns[i].DataType != value[i].GetType())
- throw new InvalidCastException ();
- }
- //FIXME: BeginEdit() not correct
- BeginEdit (); // implicitly called
- rowState = DataRowState.Modified;
+ newval = DBNull.Value;
+ }
+ }
+ else if (v == DBNull.Value)
+ {
+
+ if (!col.AllowDBNull)
+ {
+ //Constraint violations during data load is raise in DataTable EndLoad
+ this._nullConstraintViolation = true;
+ if (this.Table._duringDataLoad) {
+ this.Table._nullConstraintViolationDuringDataLoad = true;
+ }
+ _nullConstraintMessage = "Column '" + col.ColumnName + "' does not allow nulls.";
+ }
+
+ newval = DBNull.Value;
+ }
+ else
+ {
+ Type vType = v.GetType(); // data type of value
+ Type cType = col.DataType; // column data type
+ if (cType != vType)
+ {
+ TypeCode typeCode = Type.GetTypeCode(cType);
+ switch(typeCode) {
+ case TypeCode.Boolean :
+ v = Convert.ToBoolean (v);
+ break;
+ case TypeCode.Byte :
+ v = Convert.ToByte (v);
+ break;
+ case TypeCode.Char :
+ v = Convert.ToChar (v);
+ break;
+ case TypeCode.DateTime :
+ v = Convert.ToDateTime (v);
+ break;
+ case TypeCode.Decimal :
+ v = Convert.ToDecimal (v);
+ break;
+ case TypeCode.Double :
+ v = Convert.ToDouble (v);
+ break;
+ case TypeCode.Int16 :
+ v = Convert.ToInt16 (v);
+ break;
+ case TypeCode.Int32 :
+ v = Convert.ToInt32 (v);
+ break;
+ case TypeCode.Int64 :
+ v = Convert.ToInt64 (v);
+ break;
+ case TypeCode.SByte :
+ v = Convert.ToSByte (v);
+ break;
+ case TypeCode.Single :
+ v = Convert.ToSingle (v);
+ break;
+ case TypeCode.String :
+ v = Convert.ToString (v);
+ break;
+ case TypeCode.UInt16 :
+ v = Convert.ToUInt16 (v);
+ break;
+ case TypeCode.UInt32 :
+ v = Convert.ToUInt32 (v);
+ break;
+ case TypeCode.UInt64 :
+ v = Convert.ToUInt64 (v);
+ break;
+ default :
+
+ switch(cType.ToString()) {
+ case "System.TimeSpan" :
+ v = (System.TimeSpan) v;
+ break;
+ case "System.Type" :
+ v = (System.Type) v;
+ break;
+ case "System.Object" :
+ //v = (System.Object) v;
+ break;
+ default:
+ if (!cType.IsArray)
+ throw new InvalidCastException("Type not supported.");
+ break;
+ }
+ break;
+ }
+ vType = v.GetType();
+ }
- //FIXME: this isn't correct. a shorter array can set the first few values
- //and not touch the rest. So not all the values will get replaced
- proposed = value;
+ // The MaxLength property is ignored for non-text columns
+ if ((Type.GetTypeCode(vType) == TypeCode.String) && (col.MaxLength != -1) &&
+ (this.Table.Columns[index].MaxLength < ((string)v).Length)) {
+ throw new ArgumentException("Cannot set column '" + col.ColumnName + "' to '" + v + "'. The value violates the MaxLength limit of this column.");
+ }
+ newval = v;
+ if(col.AutoIncrement == true) {
+ long inc = Convert.ToInt64(v);
+ col.UpdateAutoIncrementValue (inc);
+ }
}
+ col.DataHasBeenSet = true;
+ return newval;
}
/// <summary>
//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 DataRowState RowStateInternal {
- set { rowState = value;}
+ internal void AttachRow() {
+ current = proposed;
+ proposed = null;
+ 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() {
+ proposed = null;
+ _rowId = -1;
+ _hasParentCollection = false;
+ rowState = DataRowState.Detached;
}
/// <summary>
get { return _table; }
}
+ internal XmlDataDocument.XmlDataElement DataElement {
+ get { return mappedElement; }
+ set { mappedElement = value; }
+ }
+
+ /// <summary>
+ /// Gets and sets index of row. This is used from
+ /// XmlDataDocument.
+ // </summary>
+ internal int XmlRowID {
+ get { return xmlRowID; }
+ set { xmlRowID = value; }
+ }
+
+ /// <summary>
+ /// Gets and sets index of row.
+ // </summary>
+ internal int RowID {
+ get { return _rowId; }
+ set { _rowId = value; }
+ }
+
#endregion
#region Methods
/// Commits all the changes made to this row since the last time AcceptChanges was
/// called.
/// </summary>
- [MonoTODO]
public void AcceptChanges ()
{
-
- if(rowState == DataRowState.Added)
- {
- //Instantiate original and proposed values so that we can call
- //EndEdit()
- this.BeginEdit();
- }
-
- this.EndEdit ();
-
- switch (rowState)
- {
- case DataRowState.Added:
- case DataRowState.Detached:
- case DataRowState.Modified:
- rowState = DataRowState.Unchanged;
- break;
- case DataRowState.Deleted:
- _table.Rows.Remove (this); //FIXME: this should occur in end edit
- break;
+ EndEdit(); // in case it hasn't been called
+ switch (rowState) {
+ case DataRowState.Added:
+ case DataRowState.Modified:
+ rowState = DataRowState.Unchanged;
+ break;
+ case DataRowState.Deleted:
+ _table.Rows.RemoveInternal (this);
+ DetachRow();
+ break;
+ case DataRowState.Detached:
+ throw new RowNotInTableException("Cannot perform this operation on a row not in the table.");
}
-
- //MS implementation assigns the Proposed values
- //to both current and original and keeps original after calling AcceptChanges
- //Copy proposed to original in this.EndEdit()
- //original = null;
+ // Accept from detached
+ if (original == null)
+ original = new object[_table.Columns.Count];
+ Array.Copy (current, original, _table.Columns.Count);
}
/// <summary>
/// Begins an edit operation on a DataRow object.
/// </summary>
- [MonoTODO]
- public void BeginEdit()
+ public void BeginEdit ()
{
+
+ if (_inChangingEvent)
+ throw new InRowChangingEventException("Cannot call BeginEdit inside an OnRowChanging event.");
if (rowState == DataRowState.Deleted)
throw new DeletedRowInaccessibleException ();
-
- if (!HasVersion (DataRowVersion.Proposed))
- {
+ if (!HasVersion (DataRowVersion.Proposed)) {
proposed = new object[_table.Columns.Count];
- Array.Copy (current, proposed, _table.Columns.Count);
- }
- //TODO: Suspend validation
-
- //FIXME: this doesn't happen on begin edit
- if (!HasVersion (DataRowVersion.Original))
- {
- original = new object[_table.Columns.Count];
- Array.Copy (current, original, _table.Columns.Count);
+ Array.Copy (current, proposed, current.Length);
}
+ // setting editing to true stops validations on the row
+ editing = true;
}
/// <summary>
/// Cancels the current edit on the row.
/// </summary>
- [MonoTODO]
public void CancelEdit ()
{
- //FIXME: original doesn't get erased on CancelEdit
- //TODO: Events
- if (HasVersion (DataRowVersion.Proposed))
- {
- original = null;
+ if (_inChangingEvent)
+ throw new InRowChangingEventException("Cannot call CancelEdit inside an OnRowChanging event.");
+ editing = false;
+ if (HasVersion (DataRowVersion.Proposed)) {
proposed = null;
- rowState = DataRowState.Unchanged;
+ if (rowState == DataRowState.Modified)
+ rowState = DataRowState.Unchanged;
}
}
/// <summary>
/// Deletes the DataRow.
/// </summary>
- [MonoTODO]
public void Delete ()
{
+ _table.DeletingDataRow(this, DataRowAction.Delete);
switch (rowState) {
case DataRowState.Added:
- Table.Rows.Remove (this);
+ // check what to do with child rows
+ CheckChildRows(DataRowAction.Delete);
+ _table.DeleteRowFromIndexes (this);
+ Table.Rows.RemoveInternal (this);
+
+ // if row was in Added state we move it to Detached.
+ DetachRow();
break;
case DataRowState.Deleted:
- throw new DeletedRowInaccessibleException ();
+ break;
default:
- //TODO: Events, Constraints
+ // check what to do with child rows
+ CheckChildRows(DataRowAction.Delete);
+ _table.DeleteRowFromIndexes (this);
rowState = DataRowState.Deleted;
break;
}
+ _table.DeletedDataRow(this, DataRowAction.Delete);
+ }
+
+ // check the child rows of this row before deleting the row.
+ private void CheckChildRows(DataRowAction action)
+ {
+
+ // in this method we find the row that this row is in a relation with them.
+ // in shortly we find all child rows of this row.
+ // then we function according to the DeleteRule of the foriegnkey.
+
+
+ // 1. find if this row is attached to dataset.
+ // 2. find if EnforceConstraints is true.
+ // 3. find if there are any constraint on the table that the row is in.
+ if (_table.DataSet != null && _table.DataSet.EnforceConstraints && _table.Constraints.Count > 0)
+ {
+ 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);
+ }
+ }
+ }
+ }
+ }
+
+ private void CheckChildRows(ForeignKeyConstraint fkc, DataRowAction action, Rule rule)
+ {
+ DataRow[] childRows = GetChildRows(fkc, DataRowVersion.Default);
+ switch (rule)
+ {
+ case Rule.Cascade: // delete or change all relted rows.
+ if (childRows != null)
+ {
+ for (int j = 0; j < childRows.Length; j++)
+ {
+ // if action is delete we delete all child rows
+ if (action == DataRowAction.Delete)
+ {
+ if (childRows[j].RowState != DataRowState.Deleted)
+ childRows[j].Delete();
+ }
+ // if action is change we change the values in the child row
+ else if (action == 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];
+ }
+ }
+ }
+ break;
+ case Rule.None: // throw an exception if there are any child rows.
+ if (childRows != null)
+ {
+ for (int j = 0; j < childRows.Length; j++)
+ {
+ if (childRows[j].RowState != DataRowState.Deleted)
+ {
+ string changeStr = "Cannot change this row because constraints are enforced on relation " + fkc.ConstraintName +", and changing this row will strand child rows.";
+ string delStr = "Cannot delete this row because constraints are enforced on relation " + fkc.ConstraintName +", and deleting this row will strand child rows.";
+ string message = action == DataRowAction.Delete ? delStr : changeStr;
+ throw new InvalidConstraintException(message);
+ }
+ }
+ }
+ break;
+ case Rule.SetDefault: // set the values in the child rows to the defult value of the columns.
+ if (childRows != null)
+ {
+ for (int j = 0; j < childRows.Length; j++)
+ {
+ DataRow child = childRows[j];
+ if (childRows[j].RowState != DataRowState.Deleted)
+ {
+ //set only the key columns to default
+ for (int k = 0; k < fkc.Columns.Length; k++)
+ child[fkc.Columns[k]] = fkc.Columns[k].DefaultValue;
+ }
+ }
+ }
+ break;
+ case Rule.SetNull: // set the values in the child row to null.
+ if (childRows != null)
+ {
+ for (int j = 0; j < childRows.Length; j++)
+ {
+ DataRow child = childRows[j];
+ if (childRows[j].RowState != DataRowState.Deleted)
+ {
+ // set only the key columns to DBNull
+ for (int k = 0; k < fkc.Columns.Length; k++)
+ child.SetNull(fkc.Columns[k]);
+ }
+ }
+ }
+ break;
+ }
+
}
/// <summary>
/// Ends the edit occurring on the row.
/// </summary>
- [MonoTODO]
public void EndEdit ()
{
+ if (_inChangingEvent)
+ throw new InRowChangingEventException("Cannot call EndEdit inside an OnRowChanging event.");
+ if (rowState == DataRowState.Detached)
+ {
+ editing = false;
+ return;
+ }
+
+ CheckReadOnlyStatus();
if (HasVersion (DataRowVersion.Proposed))
{
- rowState = DataRowState.Modified;
+ _inChangingEvent = true;
+ try
+ {
+ _table.ChangingDataRow(this, DataRowAction.Change);
+ }
+ finally
+ {
+ _inChangingEvent = false;
+ }
+ if (rowState == DataRowState.Unchanged)
+ rowState = DataRowState.Modified;
//Calling next method validates UniqueConstraints
//and ForeignKeys.
- _table.Rows.ValidateDataRowInternal(this);
-
- Array.Copy (proposed, current, _table.Columns.Count);
-
- //FIXME: MS implementation assigns the proposed values to
- //the original values. Should this be done here or on the
- //AcceptChanges() method?
- Array.Copy (proposed, original, _table.Columns.Count);
+ try
+ {
+ if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
+ _table.Rows.ValidateDataRowInternal(this);
+ }
+ catch (Exception e)
+ {
+ editing = false;
+ proposed = null;
+ throw e;
+ }
- proposed = null;
+ // 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.
+ object[] backup = current;
+ current = proposed;
+ bool editing_backup = editing;
+ editing = false;
+ try {
+ // check all child rows.
+ CheckChildRows(DataRowAction.Change);
+ proposed = null;
+ }
+ 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;
+ }
+ _table.ChangedDataRow(this, DataRowAction.Change);
}
}
/// <summary>
/// Gets the child rows of this DataRow using the specified DataRelation.
/// </summary>
- [MonoTODO]
public DataRow[] GetChildRows (DataRelation relation)
{
- throw new NotImplementedException ();
+ return GetChildRows (relation, DataRowVersion.Current);
}
/// <summary>
/// Gets the child rows of a DataRow using the specified RelationName of a
/// DataRelation.
/// </summary>
- [MonoTODO]
public DataRow[] GetChildRows (string relationName)
{
- throw new NotImplementedException ();
+ return GetChildRows (Table.DataSet.Relations[relationName]);
}
/// <summary>
/// Gets the child rows of a DataRow using the specified DataRelation, and
/// DataRowVersion.
/// </summary>
- [MonoTODO]
public DataRow[] GetChildRows (DataRelation relation, DataRowVersion version)
{
- throw new NotImplementedException ();
+ if (relation == null)
+ return new DataRow[0];
+
+ if (this.Table == null || 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.");
+
+ if (relation.DataSet != this.Table.DataSet)
+ throw new ArgumentException();
+
+ if (relation.ChildKeyConstraint != null)
+ return GetChildRows (relation.ChildKeyConstraint, version);
+
+ ArrayList rows = new ArrayList();
+ 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]))
+ {
+ allColumnsMatch = false;
+ break;
+ }
+ }
+ }
+ if (allColumnsMatch) rows.Add(row);
+ }
+ }
+ DataRow[] result = relation.ChildTable.NewRowArray(rows.Count);
+ rows.CopyTo(result, 0);
+ return result;
}
/// <summary>
/// Gets the child rows of a DataRow using the specified RelationName of a
/// DataRelation, and DataRowVersion.
/// </summary>
- [MonoTODO]
public DataRow[] GetChildRows (string relationName, DataRowVersion version)
{
- throw new NotImplementedException ();
+ return GetChildRows (Table.DataSet.Relations[relationName], version);
+ }
+
+ private DataRow[] GetChildRows (ForeignKeyConstraint fkc, DataRowVersion version)
+ {
+ ArrayList rows = new ArrayList();
+ DataColumn[] parentColumns = fkc.RelatedColumns;
+ DataColumn[] childColumns = fkc.Columns;
+ 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];
+
+ Index index = fkc.Index;
+ if (index != null) {
+ // get the child rows from the index
+ Node[] childNodes = index.FindAllSimple (vals);
+ for (int i = 0; i < childNodes.Length; i++) {
+ rows.Add (childNodes[i].Row);
+ }
+ }
+ else { // if there is no index we search manualy.
+ foreach (DataRow row in fkc.Table.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]))
+ {
+ allColumnsMatch = false;
+ break;
+ }
+ }
+ }
+ if (allColumnsMatch) rows.Add(row);
+ }
+ }
+ }
+
+ DataRow[] result = fkc.Table.NewRowArray(rows.Count);
+ rows.CopyTo(result, 0);
+ return result;
}
/// <summary>
if (columnIndex < 0 || columnIndex >= columnErrors.Length)
throw new IndexOutOfRangeException ();
- return columnErrors[columnIndex];
+ string retVal = columnErrors[columnIndex];
+ if (retVal == null)
+ retVal = string.Empty;
+ return retVal;
}
/// <summary>
for (int i = 0; i < columnErrors.Length; i += 1)
{
- if (columnErrors[i] != String.Empty)
+ if (columnErrors[i] != null && columnErrors[i] != String.Empty)
dataColumns.Add (_table.Columns[i]);
}
- return (DataColumn[])(dataColumns.ToArray ());
+ return (DataColumn[])(dataColumns.ToArray (typeof(DataColumn)));
}
/// <summary>
/// Gets the parent row of a DataRow using the specified DataRelation.
/// </summary>
- [MonoTODO]
public DataRow GetParentRow (DataRelation relation)
{
- throw new NotImplementedException ();
+ return GetParentRow (relation, DataRowVersion.Current);
}
/// <summary>
/// Gets the parent row of a DataRow using the specified RelationName of a
/// DataRelation.
/// </summary>
- [MonoTODO]
public DataRow GetParentRow (string relationName)
{
- throw new NotImplementedException ();
+ return GetParentRow (relationName, DataRowVersion.Current);
}
/// <summary>
/// Gets the parent row of a DataRow using the specified DataRelation, and
/// DataRowVersion.
/// </summary>
- [MonoTODO]
public DataRow GetParentRow (DataRelation relation, DataRowVersion version)
{
- throw new NotImplementedException ();
+ DataRow[] rows = GetParentRows(relation, version);
+ if (rows.Length == 0) return null;
+ return rows[0];
}
/// <summary>
/// Gets the parent row of a DataRow using the specified RelationName of a
/// DataRelation, and DataRowVersion.
/// </summary>
- [MonoTODO]
public DataRow GetParentRow (string relationName, DataRowVersion version)
{
- throw new NotImplementedException ();
+ return GetParentRow (Table.DataSet.Relations[relationName], version);
}
/// <summary>
/// Gets the parent rows of a DataRow using the specified DataRelation.
/// </summary>
- [MonoTODO]
public DataRow[] GetParentRows (DataRelation relation)
{
- throw new NotImplementedException ();
+ return GetParentRows (relation, DataRowVersion.Current);
}
/// <summary>
/// Gets the parent rows of a DataRow using the specified RelationName of a
/// DataRelation.
/// </summary>
- [MonoTODO]
public DataRow[] GetParentRows (string relationName)
{
- throw new NotImplementedException ();
+ return GetParentRows (relationName, DataRowVersion.Current);
}
/// <summary>
/// Gets the parent rows of a DataRow using the specified DataRelation, and
/// DataRowVersion.
/// </summary>
- [MonoTODO]
public DataRow[] GetParentRows (DataRelation relation, DataRowVersion version)
{
- throw new NotImplementedException ();
+ // TODO: Caching for better preformance
+ if (relation == null)
+ return new DataRow[0];
+
+ if (this.Table == null || 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.");
+
+ if (relation.DataSet != this.Table.DataSet)
+ throw new ArgumentException();
+
+ ArrayList rows = new ArrayList();
+ DataColumn[] parentColumns = relation.ParentColumns;
+ DataColumn[] childColumns = relation.ChildColumns;
+ int numColumn = parentColumns.Length;
+ if (HasVersion(version))
+ {
+ object[] vals = new object[childColumns.Length];
+ for (int i = 0; i < vals.Length; i++)
+ vals[i] = this[childColumns[i], version];
+
+ Index indx = relation.ParentTable.GetIndexByColumns (parentColumns);
+ if (indx != null) { // get the child rows from the index
+ Node[] childNodes = indx.FindAllSimple (vals);
+ for (int i = 0; i < childNodes.Length; i++) {
+ rows.Add (childNodes[i].Row);
+ }
+ }
+ else { // no index so we have to search manualy.
+ foreach (DataRow row in relation.ParentTable.Rows)
+ {
+ bool allColumnsMatch = false;
+ if (row.HasVersion(DataRowVersion.Default))
+ {
+ allColumnsMatch = true;
+ for (int columnCnt = 0; columnCnt < numColumn; columnCnt++)
+ {
+ if (!this[childColumns[columnCnt], version].Equals(
+ row[parentColumns[columnCnt], DataRowVersion.Default]))
+ {
+ allColumnsMatch = false;
+ break;
+ }
+ }
+ }
+ if (allColumnsMatch) rows.Add(row);
+ }
+ }
+ }
+
+ DataRow[] result = relation.ParentTable.NewRowArray(rows.Count);
+ rows.CopyTo(result, 0);
+ return result;
}
/// <summary>
/// Gets the parent rows of a DataRow using the specified RelationName of a
/// DataRelation, and DataRowVersion.
/// </summary>
- [MonoTODO]
public DataRow[] GetParentRows (string relationName, DataRowVersion version)
{
- throw new NotImplementedException ();
+ return GetParentRows (Table.DataSet.Relations[relationName], version);
}
/// <summary>
switch (version)
{
case DataRowVersion.Default:
+ if (rowState == DataRowState.Deleted && !_inExpressionEvaluation)
+ return false;
+ if (rowState == DataRowState.Detached)
+ return proposed != null;
return true;
case DataRowVersion.Proposed:
+ if (rowState == DataRowState.Deleted && !_inExpressionEvaluation)
+ return false;
return (proposed != null);
case DataRowVersion.Current:
+ if ((rowState == DataRowState.Deleted && !_inExpressionEvaluation) || rowState == DataRowState.Detached)
+ return false;
return (current != null);
case DataRowVersion.Original:
+ if (rowState == DataRowState.Detached)
+ return false;
return (original != null);
}
return false;
/// </summary>
public bool IsNull (DataColumn column)
{
- return (this[column] == null);
+ object o = this[column];
+ return (o == DBNull.Value);
}
/// <summary>
/// </summary>
public bool IsNull (int columnIndex)
{
- return (this[columnIndex] == null);
+ object o = this[columnIndex];
+ return (o == DBNull.Value);
}
/// <summary>
/// </summary>
public bool IsNull (string columnName)
{
- return (this[columnName] == null);
+ object o = this[columnName];
+ return (o == DBNull.Value);
}
/// <summary>
/// </summary>
public bool IsNull (DataColumn column, DataRowVersion version)
{
- return (this[column, version] == null);
+ object o = this[column, version];
+ return (o == DBNull.Value);
+ }
+
+ /// <summary>
+ /// Returns a value indicating whether all of the row columns specified contain a null value.
+ /// </summary>
+ internal bool IsNullColumns(DataColumn[] columns)
+ {
+ bool allNull = true;
+ for (int i = 0; i < columns.Length; i++)
+ {
+ if (!IsNull(columns[i]))
+ {
+ allNull = false;
+ break;
+ }
+ }
+ return allNull;
}
/// <summary>
/// </summary>
public void RejectChanges ()
{
+ 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.");
// If original is null, then nothing has happened since AcceptChanges
// was last called. We have no "original" to go back to.
if (original != null)
{
Array.Copy (original, current, _table.Columns.Count);
+
+ _table.ChangedDataRow (this, DataRowAction.Rollback);
CancelEdit ();
switch (rowState)
{
case DataRowState.Added:
- _table.Rows.Remove (this);
+ _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();
}
}
}
/// <summary>
/// Sets the value of the specified DataColumn to a null value.
/// </summary>
- [MonoTODO]
protected void SetNull (DataColumn column)
{
- throw new NotImplementedException ();
+ this[column] = DBNull.Value;
}
/// <summary>
/// Sets the parent row of a DataRow with specified new parent DataRow.
/// </summary>
- [MonoTODO]
public void SetParentRow (DataRow parentRow)
{
- throw new NotImplementedException ();
+ SetParentRow(parentRow, null);
}
/// <summary>
/// Sets the parent row of a DataRow with specified new parent DataRow and
/// DataRelation.
/// </summary>
- [MonoTODO]
public void SetParentRow (DataRow parentRow, DataRelation relation)
{
- throw new NotImplementedException ();
+ if (_table == null || parentRow.Table == null || 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.");
+
+ if (parentRow != null && _table.DataSet != parentRow.Table.DataSet)
+ throw new ArgumentException();
+
+ 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]];
+ }
+
+ }
+ }
+ else
+ {
+ DataColumn[] childCols = relation.ChildKeyConstraint.Columns;
+ DataColumn[] parentCols = relation.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]];
+ }
+ }
+ EndEdit();
+ }
+
+ //Copy all values of this DataaRow to the row parameter.
+ internal void CopyValuesToRow(DataRow row)
+ {
+
+ if (row == null)
+ throw new ArgumentNullException("row");
+ if (row == this)
+ throw new ArgumentException("'row' is the same as this object");
+
+ DataColumnCollection columns = Table.Columns;
+
+ for(int i = 0; i < columns.Count; i++){
+
+ string columnName = columns[i].ColumnName;
+ DataColumn column = row.Table.Columns[columnName];
+ //if a column with the same name exists in both rows copy the values
+ if(column != null) {
+ int index = column.Ordinal;
+ if (HasVersion(DataRowVersion.Original))
+ {
+ if (row.original == null)
+ row.original = new object[row.Table.Columns.Count];
+ row.original[index] = row.SetColumnValue(original[i], column, index);
+ }
+ if (HasVersion(DataRowVersion.Current))
+ {
+ if (row.current == null)
+ row.current = new object[row.Table.Columns.Count];
+ row.current[index] = row.SetColumnValue(current[i], column, index);
+ }
+ if (HasVersion(DataRowVersion.Proposed))
+ {
+ if (row.proposed == null)
+ row.proposed = new object[row.Table.Columns.Count];
+ row.proposed[index] = row.SetColumnValue(proposed[i], column, index);
+ }
+
+ //Saving the current value as the column value
+ row[index] = row.current[index];
+
+ }
+ }
+
+ row.rowState = RowState;
+ row.RowError = RowError;
+ row.columnErrors = columnErrors;
+ }
+
+
+ private void CollectionChanged(object sender, System.ComponentModel.CollectionChangeEventArgs args)
+ {
+ // if a column is added we hava to add an additional value the
+ // the priginal, current and propoed arrays.
+ // this scenario can happened in merge operation.
+
+ if (args.Action == System.ComponentModel.CollectionChangeAction.Add)
+ {
+ object[] tmp;
+ int index = this.Table.Columns.Count - 1;
+ if (current != null)
+ {
+ tmp = new object [index + 1];
+ Array.Copy (current, tmp, current.Length);
+ tmp[tmp.Length - 1] = SetColumnValue(null, this.Table.Columns[index], index);
+ current = tmp;
+ }
+ if (proposed != null)
+ {
+ tmp = new object [index + 1];
+ Array.Copy (proposed, tmp, proposed.Length);
+ tmp[tmp.Length - 1] = SetColumnValue(null, this.Table.Columns[index], index);
+ proposed = tmp;
+ }
+ if(original != null)
+ {
+ tmp = new object [index + 1];
+ Array.Copy (original, tmp, original.Length);
+ tmp[tmp.Length - 1] = SetColumnValue(null, this.Table.Columns[index], index);
+ original = tmp;
+ }
+
+ }
+ }
+
+ internal void onColumnRemoved(int columnIndex)
+ {
+ // when column removed we have to compress row values in the way
+ // they will correspond to new column ordinals
+
+ object[] tmp;
+ if (current != null)
+ {
+ tmp = new object[current.Length - 1];
+ // copy values before removed column
+ if (columnIndex > 0)
+ Array.Copy (current, 0, tmp, 0, columnIndex);
+ // copy values after removed column
+ if(columnIndex < current.Length - 1)
+ Array.Copy(current, columnIndex + 1, tmp, columnIndex, current.Length - 1 - columnIndex);
+
+ current = tmp;
+ }
+ if (proposed != null)
+ {
+ tmp = new object[proposed.Length - 1];
+ // copy values before removed column
+ if (columnIndex > 0)
+ Array.Copy (proposed, 0, tmp, 0, columnIndex);
+ // copy values after removed column
+ if(columnIndex < proposed.Length - 1)
+ Array.Copy(proposed, columnIndex + 1, tmp, columnIndex, proposed.Length - 1 - columnIndex);
+
+ proposed = tmp;
+ }
+ if (original != null)
+ {
+ tmp = new object[original.Length - 1];
+ // copy values before removed column
+ if (columnIndex > 0)
+ Array.Copy (original, 0, tmp, 0, columnIndex);
+ // copy values after removed column
+ if(columnIndex < original.Length - 1)
+ Array.Copy(original, columnIndex + 1, tmp, columnIndex, original.Length - 1 - columnIndex);
+
+ original = tmp;
+ }
}
+ internal bool IsRowChanged(DataRowState rowState) {
+ if((RowState & rowState) != 0)
+ return true;
+
+ //we need to find if child rows of this row changed.
+ //if yes - we should return true
+
+ // if the rowState is deleted we should get the original version of the row
+ // else - we should get the current version of the row.
+ DataRowVersion version = (rowState == DataRowState.Deleted) ? DataRowVersion.Original : DataRowVersion.Current;
+ int count = Table.ChildRelations.Count;
+ for (int i = 0; i < count; i++){
+ DataRelation rel = Table.ChildRelations[i];
+ DataRow[] childRows = GetChildRows(rel, version);
+ for (int j = 0; j < childRows.Length; j++){
+ if (childRows[j].IsRowChanged(rowState))
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ internal bool HasParentCollection
+ {
+ get
+ {
+ return _hasParentCollection;
+ }
+ set
+ {
+ _hasParentCollection = value;
+ }
+ }
+
+ // checks existance of value in version pecified
+ // note : this one relies on the same algorithm as this[int,DataRowVersion]
+ private bool CanAccess(int columnIndex, DataRowVersion version)
+ {
+ if (columnIndex < 0 || columnIndex > _table.Columns.Count)
+ return false;
+ // Accessing deleted rows
+ if (rowState == DataRowState.Deleted && version != DataRowVersion.Original)
+ return false;
+
+ if (HasVersion(version))
+ {
+ object[] versionArr;
+ switch (version)
+ {
+ case DataRowVersion.Default:
+ if (editing || rowState == DataRowState.Detached) {
+ versionArr = proposed;
+ }
+ else {
+ versionArr = current;
+ }
+ break;
+ case DataRowVersion.Proposed:
+ versionArr = proposed;
+ break;
+ case DataRowVersion.Current:
+ versionArr = current;
+ break;
+ case DataRowVersion.Original:
+ versionArr = original;
+ break;
+ default:
+ return false;
+ }
+
+ return (versionArr != null && columnIndex < versionArr.Length);
+ }
+
+ return false;
+ }
+
+ internal void CheckNullConstraints()
+ {
+ if (_nullConstraintViolation) {
+ if (proposed != null) {
+ for (int i = 0; i < proposed.Length; i++) {
+ if (this[i] == DBNull.Value && !_table.Columns[i].AllowDBNull)
+ throw new NoNullAllowedException(_nullConstraintMessage);
+ }
+ }
+ _nullConstraintViolation = false;
+ }
+ }
+ internal void CheckReadOnlyStatus()
+ {
+ if (proposed == null)
+ return;
+
+ for (int i = 0; i < proposed.Length; i++) {
+ if (this[i] != proposed[i] && _table.Columns[i].ReadOnly)
+ throw new ReadOnlyException();
+ }
+
+ }
+
#endregion // Methods
}
+
+
+
}