-//\r
-// System.Data.DataRow.cs\r
-//\r
-// Author:\r
-// Rodrigo Moya <rodrigo@ximian.com>\r
-// Daniel Morgan <danmorg@sc.rr.com>\r
-// Tim Coleman <tim@timcoleman.com>\r
-// Ville Palo <vi64pa@koti.soon.fi>\r
-// Alan Tam Siu Lung <Tam@SiuLung.com>\r
-//\r
-// (C) Ximian, Inc 2002\r
-// (C) Daniel Morgan 2002, 2003\r
-// Copyright (C) 2002 Tim Coleman\r
-//\r
-\r
-//\r
-// Copyright (C) 2004 Novell, Inc (http://www.novell.com)\r
-//\r
-// Permission is hereby granted, free of charge, to any person obtaining\r
-// a copy of this software and associated documentation files (the\r
-// "Software"), to deal in the Software without restriction, including\r
-// without limitation the rights to use, copy, modify, merge, publish,\r
-// distribute, sublicense, and/or sell copies of the Software, and to\r
-// permit persons to whom the Software is furnished to do so, subject to\r
-// the following conditions:\r
-// \r
-// The above copyright notice and this permission notice shall be\r
-// included in all copies or substantial portions of the Software.\r
-// \r
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\r
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\r
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r
-//\r
-\r
-using System;\r
-using System.Collections;\r
-using System.Globalization;\r
-using System.Xml;\r
-\r
-using System.Data.Common;\r
-\r
-namespace System.Data {\r
- /// <summary>\r
- /// Represents a row of data in a DataTable.\r
- /// </summary>\r
- [Serializable]\r
- public class DataRow\r
- {\r
- #region Fields\r
-\r
- private DataTable _table;\r
-\r
- internal int _original = -1;\r
- internal int _current = -1;\r
- internal int _proposed = -1;\r
-\r
- private ArrayList _columnErrors;\r
- private string rowError;\r
- internal int xmlRowID = 0;\r
- internal bool _nullConstraintViolation;\r
- private string _nullConstraintMessage;\r
- private bool _hasParentCollection;\r
- private bool _inChangingEvent;\r
- private int _rowId;\r
-\r
- private XmlDataDocument.XmlDataElement mappedElement;\r
- internal bool _inExpressionEvaluation = false;\r
-\r
- #endregion // Fields\r
-\r
- #region Constructors\r
-\r
- /// <summary>\r
- /// This member supports the .NET Framework infrastructure and is not intended to be \r
- /// used directly from your code.\r
- /// </summary>\r
- protected internal DataRow (DataRowBuilder builder)\r
- {\r
- _table = builder.Table;\r
- // Get the row id from the builder.\r
- _rowId = builder._rowId;\r
-\r
- rowError = String.Empty;\r
-\r
- // create mapped XmlDataElement\r
- DataSet ds = _table.DataSet;\r
- if (ds != null && ds._xmlDataDocument != null)\r
- mappedElement = new XmlDataDocument.XmlDataElement (this, _table.Prefix, _table.TableName, _table.Namespace, ds._xmlDataDocument);\r
- }\r
-\r
- internal DataRow(DataTable table,int rowId)\r
- {\r
- _table = table;\r
- _rowId = rowId;\r
- }\r
-\r
- #endregion // Constructors\r
-\r
- #region Properties\r
-\r
- private ArrayList ColumnErrors\r
- {\r
- get {\r
- if (_columnErrors == null) {\r
- _columnErrors = new ArrayList();\r
- }\r
- return _columnErrors;\r
- }\r
-\r
- set {\r
- _columnErrors = value;\r
- }\r
- }\r
-\r
- /// <summary>\r
- /// Gets a value indicating whether there are errors in a row.\r
- /// </summary>\r
- public bool HasErrors {\r
- get {\r
- if (RowError != string.Empty)\r
- return true;\r
-\r
- foreach(String columnError in ColumnErrors) {\r
- if (columnError != null && columnError != string.Empty) {\r
- return true;\r
- }\r
- }\r
- return false;\r
- }\r
- }\r
-\r
- /// <summary>\r
- /// Gets or sets the data stored in the column specified by name.\r
- /// </summary>\r
- public object this[string columnName] {\r
- get { return this[columnName, DataRowVersion.Default]; }\r
- set {\r
- int columnIndex = _table.Columns.IndexOf (columnName);\r
- if (columnIndex == -1)\r
- throw new IndexOutOfRangeException ();\r
- this[columnIndex] = value;\r
- }\r
- }\r
-\r
- /// <summary>\r
- /// Gets or sets the data stored in specified DataColumn\r
- /// </summary>\r
- public object this[DataColumn column] {\r
-\r
- get {\r
- return this[column, DataRowVersion.Default];} \r
- set {\r
- int columnIndex = _table.Columns.IndexOf (column);\r
- if (columnIndex == -1)\r
- throw new ArgumentException ("The column does not belong to this table.");\r
- this[columnIndex] = value;\r
- }\r
- }\r
-\r
- /// <summary>\r
- /// Gets or sets the data stored in column specified by index.\r
- /// </summary>\r
- public object this[int columnIndex] {\r
- get { return this[columnIndex, DataRowVersion.Default]; }\r
- set {\r
- if (columnIndex < 0 || columnIndex > _table.Columns.Count)\r
- throw new IndexOutOfRangeException ();\r
- if (RowState == DataRowState.Deleted)\r
- throw new DeletedRowInaccessibleException ();\r
-\r
- DataColumn column = _table.Columns[columnIndex];\r
- _table.ChangingDataColumn (this, column, value);\r
- \r
- if (value == null && column.DataType != typeof(string)) {\r
- throw new ArgumentException("Cannot set column " + column.ColumnName + " to be null, Please use DBNull instead");\r
- }\r
- \r
- CheckValue (value, column);\r
-\r
- bool orginalEditing = Proposed >= 0;\r
- if (!orginalEditing) {\r
- BeginEdit ();\r
- }\r
- \r
- column[Proposed] = value;\r
- _table.ChangedDataColumn (this, column, value);\r
- if (!orginalEditing) {\r
- EndEdit ();\r
- }\r
- }\r
- }\r
-\r
- /// <summary>\r
- /// Gets the specified version of data stored in the named column.\r
- /// </summary>\r
- public object this[string columnName, DataRowVersion version] {\r
- get {\r
- int columnIndex = _table.Columns.IndexOf (columnName);\r
- if (columnIndex == -1)\r
- throw new IndexOutOfRangeException ();\r
- return this[columnIndex, version];\r
- }\r
- }\r
-\r
- /// <summary>\r
- /// Gets the specified version of data stored in the specified DataColumn.\r
- /// </summary>\r
- public object this[DataColumn column, DataRowVersion version] {\r
- get {\r
- if (column.Table != Table)\r
- throw new ArgumentException ("The column does not belong to this table.");\r
- int columnIndex = column.Ordinal;\r
- return this[columnIndex, version];\r
- }\r
- }\r
-\r
- /// <summary>\r
- /// Gets the data stored in the column, specified by index and version of the data to\r
- /// retrieve.\r
- /// </summary>\r
- public object this[int columnIndex, DataRowVersion version] {\r
- get {\r
- if (columnIndex < 0 || columnIndex > _table.Columns.Count)\r
- throw new IndexOutOfRangeException ();\r
- // Accessing deleted rows\r
- if (!_inExpressionEvaluation && RowState == DataRowState.Deleted && version != DataRowVersion.Original)\r
- throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");\r
- \r
- DataColumn column = _table.Columns[columnIndex];\r
- int recordIndex = IndexFromVersion(version);\r
-\r
- if (column.Expression != String.Empty) {\r
- object o = column.CompiledExpression.Eval (this);\r
- if (o != null && o != DBNull.Value) {\r
- o = Convert.ChangeType (o, column.DataType);\r
- }\r
- column[recordIndex] = o;\r
- return column[recordIndex];\r
- }\r
-\r
- if (RowState == DataRowState.Detached && version == DataRowVersion.Default && Proposed < 0)\r
- 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.");\r
- \r
- return column[recordIndex];\r
- }\r
- }\r
- \r
- /// <summary>\r
- /// Gets or sets all of the values for this row through an array.\r
- /// </summary>\r
- public object[] ItemArray {\r
- get { \r
- // row not in table\r
- if (RowState == DataRowState.Detached)\r
- 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.");\r
- // Accessing deleted rows\r
- if (RowState == DataRowState.Deleted)\r
- throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");\r
- \r
- object[] items = new object[_table.Columns.Count];\r
- foreach(DataColumn column in _table.Columns) {\r
- items[column.Ordinal] = column[Current];\r
- }\r
- return items;\r
- }\r
- set {\r
- if (value.Length > _table.Columns.Count)\r
- throw new ArgumentException ();\r
-\r
- if (RowState == DataRowState.Deleted)\r
- throw new DeletedRowInaccessibleException ();\r
- \r
- BeginEdit ();\r
-\r
- DataColumnChangeEventArgs e = new DataColumnChangeEventArgs();\r
- foreach(DataColumn column in _table.Columns) {\r
- int i = column.Ordinal;\r
- object newVal = (i < value.Length) ? value[i] : null;\r
-\r
- if (newVal == null)\r
- continue;\r
- \r
- e.Initialize(this, column, newVal);\r
- CheckValue (e.ProposedValue, column);\r
- _table.RaiseOnColumnChanging(e);\r
- column[Proposed] = e.ProposedValue;\r
- _table.RaiseOnColumnChanged(e);\r
- }\r
- \r
- EndEdit ();\r
- }\r
- }\r
-\r
- /// <summary>\r
- /// Gets the current state of the row in regards to its relationship to the\r
- /// DataRowCollection.\r
- /// </summary>\r
- public DataRowState RowState {\r
- get { \r
- //return rowState; \r
- if ((Original == -1) && (Current == -1))\r
- {\r
- return DataRowState.Detached;\r
- }\r
- if (Original == Current)\r
- {\r
- return DataRowState.Unchanged;\r
- }\r
- if (Original == -1)\r
- {\r
- return DataRowState.Added;\r
- }\r
- if (Current == -1)\r
- {\r
- return DataRowState.Deleted;\r
- }\r
- return DataRowState.Modified;\r
- }\r
- }\r
-\r
- /// <summary>\r
- /// Gets the DataTable for which this row has a schema.\r
- /// </summary>\r
- public DataTable Table {\r
- get { \r
- return _table; \r
- }\r
- }\r
-\r
- /// <summary>\r
- /// Gets and sets index of row. This is used from \r
- /// XmlDataDocument.\r
- // </summary>\r
- internal int XmlRowID {\r
- get { \r
- return xmlRowID; \r
- }\r
- set { \r
- xmlRowID = value; \r
- }\r
- }\r
- \r
- /// <summary>\r
- /// Gets and sets index of row.\r
- // </summary>\r
- internal int RowID {\r
- get { \r
- return _rowId; \r
- }\r
- set { \r
- _rowId = value; \r
- }\r
- }\r
-\r
- internal int Original\r
- {\r
- get {\r
- return _original;\r
- }\r
- set {\r
- if (Table != null) {\r
- //Table.RecordCache[_original] = null;\r
- Table.RecordCache[value] = this;\r
- }\r
- _original = value;\r
- }\r
- }\r
-\r
- internal int Current\r
- {\r
- get {\r
- return _current;\r
- }\r
- set {\r
- if (Table != null) {\r
- //Table.RecordCache[_current] = null;\r
- Table.RecordCache[value] = this;\r
- }\r
- _current = value;\r
- }\r
- }\r
-\r
- internal int Proposed\r
- {\r
- get {\r
- return _proposed;\r
- }\r
- set {\r
- if (Table != null) {\r
- //Table.RecordCache[_proposed] = null;\r
- Table.RecordCache[value] = this;\r
- }\r
- _proposed = value;\r
- }\r
- }\r
-\r
- #endregion\r
-\r
- #region Methods\r
-\r
- //FIXME?: Couldn't find a way to set the RowState when adding the DataRow\r
- //to a Datatable so I added this method. Delete if there is a better way.\r
- internal void AttachRow() {\r
- if (Proposed != -1) {\r
- if (Current >= 0) {\r
- Table.RecordCache.DisposeRecord(Current);\r
- }\r
- Current = Proposed;\r
- Proposed = -1;\r
- }\r
- }\r
-\r
- //FIXME?: Couldn't find a way to set the RowState when removing the DataRow\r
- //from a Datatable so I added this method. Delete if there is a better way.\r
- internal void DetachRow() {\r
- if (Proposed >= 0) {\r
- _table.RecordCache.DisposeRecord(Proposed);\r
- if (Proposed == Current) {\r
- Current = -1;\r
- }\r
- if (Proposed == Original) {\r
- Original = -1;\r
- }\r
- Proposed = -1;\r
- }\r
-\r
- if (Current >= 0) {\r
- _table.RecordCache.DisposeRecord(Current);\r
- if (Current == Original) {\r
- Original = -1;\r
- }\r
- Current = -1;\r
- }\r
-\r
- if (Original >= 0) {\r
- _table.RecordCache.DisposeRecord(Original);\r
- Original = -1;\r
- }\r
-\r
- _rowId = -1;\r
- _hasParentCollection = false;\r
- }\r
-\r
- internal void ImportRecord(int record)\r
- {\r
- if (HasVersion(DataRowVersion.Proposed)) {\r
- Table.RecordCache.DisposeRecord(Proposed);\r
- }\r
-\r
- Proposed = record;\r
-\r
- foreach(DataColumn column in Table.Columns.AutoIncrmentColumns) {\r
- column.UpdateAutoIncrementValue(column.DataContainer.GetInt64(Proposed));\r
- }\r
-\r
- }\r
-\r
- private void CheckValue (object v, DataColumn col) \r
- { \r
- if (_hasParentCollection && col.ReadOnly) {\r
- throw new ReadOnlyException ();\r
- }\r
-\r
- if (v == null || v == DBNull.Value) {\r
- if (col.AllowDBNull || col.AutoIncrement || col.DefaultValue != DBNull.Value) {\r
- return;\r
- }\r
-\r
- //Constraint violations during data load is raise in DataTable EndLoad\r
- this._nullConstraintViolation = true;\r
- if (this.Table._duringDataLoad) {\r
- this.Table._nullConstraintViolationDuringDataLoad = true;\r
- }\r
- _nullConstraintMessage = "Column '" + col.ColumnName + "' does not allow nulls.";\r
- \r
- }\r
- }\r
-\r
- /// <summary>\r
- /// Gets or sets the custom error description for a row.\r
- /// </summary>\r
- public string RowError {\r
- get { \r
- return rowError; \r
- }\r
- set { \r
- rowError = value; \r
- }\r
- }\r
-\r
- internal int IndexFromVersion(DataRowVersion version)\r
- {\r
- switch (version) {\r
- case DataRowVersion.Default:\r
- if (Proposed >= 0)\r
- return Proposed;\r
-\r
- if (Current >= 0)\r
- return Current;\r
-\r
- if (Original < 0)\r
- 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.");\r
-\r
- throw new DeletedRowInaccessibleException("Deleted row information cannot be accessed through the row.");\r
-\r
- case DataRowVersion.Proposed:\r
- return AssertValidVersionIndex(version, Proposed);\r
- case DataRowVersion.Current:\r
- return AssertValidVersionIndex(version, Current);\r
- case DataRowVersion.Original:\r
- return AssertValidVersionIndex(version, Original);\r
- default:\r
- throw new DataException ("Version must be Original, Current, or Proposed.");\r
- }\r
- }\r
-\r
- private int AssertValidVersionIndex(DataRowVersion version, int index) {\r
- if (index >= 0)\r
- return index;\r
-\r
- throw new VersionNotFoundException(String.Format("There is no {0} data to accces.", version));\r
- }\r
-\r
- internal DataRowVersion VersionFromIndex(int index) {\r
- if (index < 0)\r
- throw new ArgumentException("Index must not be negative.");\r
-\r
- // the order of ifs matters\r
- if (index == Current)\r
- return DataRowVersion.Current;\r
- if (index == Original)\r
- return DataRowVersion.Original;\r
- if (index == Proposed)\r
- return DataRowVersion.Proposed;\r
-\r
- throw new ArgumentException(String.Format("The index {0} does not belong to this row.", index) );\r
- }\r
-\r
- internal XmlDataDocument.XmlDataElement DataElement {\r
- get { return mappedElement; }\r
- set { mappedElement = value; }\r
- }\r
-\r
- internal void SetOriginalValue (string columnName, object val)\r
- {\r
- DataColumn column = _table.Columns[columnName];\r
- _table.ChangingDataColumn (this, column, val);\r
- \r
- if (Original < 0 || Original == Current) { \r
- Original = Table.RecordCache.NewRecord();\r
- }\r
- CheckValue (val, column);\r
- column[Original] = val;\r
- }\r
-\r
- /// <summary>\r
- /// Commits all the changes made to this row since the last time AcceptChanges was\r
- /// called.\r
- /// </summary>\r
- public void AcceptChanges () \r
- {\r
- EndEdit(); // in case it hasn't been called\r
- CheckChildRows(DataRowAction.Commit);\r
- switch (RowState) {\r
- case DataRowState.Unchanged:\r
- return;\r
- case DataRowState.Added:\r
- case DataRowState.Modified:\r
- if (Original >= 0) {\r
- Table.RecordCache.DisposeRecord(Original);\r
- }\r
- Original = Current;\r
- break;\r
- case DataRowState.Deleted:\r
- _table.Rows.RemoveInternal (this);\r
- DetachRow();\r
- break;\r
- case DataRowState.Detached:\r
- throw new RowNotInTableException("Cannot perform this operation on a row not in the table.");\r
- }\r
- }\r
-\r
- /// <summary>\r
- /// Begins an edit operation on a DataRow object.\r
- /// </summary>\r
- public void BeginEdit () \r
- {\r
- if (_inChangingEvent)\r
- throw new InRowChangingEventException("Cannot call BeginEdit inside an OnRowChanging event.");\r
- if (RowState == DataRowState.Deleted)\r
- throw new DeletedRowInaccessibleException ();\r
-\r
- if (!HasVersion (DataRowVersion.Proposed)) {\r
- Proposed = Table.RecordCache.NewRecord();\r
- int from = HasVersion(DataRowVersion.Current) ? Current : Table.DefaultValuesRowIndex;\r
- for(int i = 0; i < Table.Columns.Count; i++){\r
- DataColumn column = Table.Columns[i];\r
- column.DataContainer.CopyValue(from,Proposed);\r
- }\r
- }\r
- }\r
-\r
- /// <summary>\r
- /// Cancels the current edit on the row.\r
- /// </summary>\r
- public void CancelEdit () \r
- {\r
- if (_inChangingEvent) {\r
- throw new InRowChangingEventException("Cannot call CancelEdit inside an OnRowChanging event.");\r
- }\r
-\r
- if (HasVersion (DataRowVersion.Proposed)) {\r
- Table.RecordCache.DisposeRecord(Proposed);\r
- Proposed = -1;\r
- }\r
- }\r
-\r
- /// <summary>\r
- /// Clears the errors for the row, including the RowError and errors set with\r
- /// SetColumnError.\r
- /// </summary>\r
- public void ClearErrors () \r
- {\r
- rowError = String.Empty;\r
- ColumnErrors.Clear();\r
- }\r
-\r
- /// <summary>\r
- /// Deletes the DataRow.\r
- /// </summary>\r
- public void Delete () \r
- {\r
- _table.DeletingDataRow(this, DataRowAction.Delete);\r
- switch (RowState) {\r
- case DataRowState.Added:\r
- // check what to do with child rows\r
- CheckChildRows(DataRowAction.Delete);\r
- _table.DeleteRowFromIndexes (this);\r
- Table.Rows.RemoveInternal (this);\r
-\r
- // if row was in Added state we move it to Detached.\r
- DetachRow();\r
- break;\r
- case DataRowState.Deleted:\r
- break; \r
- default:\r
- // check what to do with child rows\r
- CheckChildRows(DataRowAction.Delete);\r
- _table.DeleteRowFromIndexes (this);\r
- break;\r
- }\r
- if (Current >= 0) {\r
- if (Current != Original) {\r
- _table.RecordCache.DisposeRecord(Current);\r
- }\r
- Current = -1;\r
- }\r
- _table.DeletedDataRow(this, DataRowAction.Delete);\r
- }\r
-\r
- // check the child rows of this row before deleting the row.\r
- private void CheckChildRows(DataRowAction action)\r
- {\r
- \r
- // in this method we find the row that this row is in a relation with them.\r
- // in shortly we find all child rows of this row.\r
- // then we function according to the DeleteRule of the foriegnkey.\r
-\r
-\r
- // 1. find if this row is attached to dataset.\r
- // 2. find if EnforceConstraints is true.\r
- // 3. find if there are any constraint on the table that the row is in.\r
- if (_table.DataSet != null && _table.DataSet.EnforceConstraints && _table.Constraints.Count > 0)\r
- {\r
- foreach (DataTable table in _table.DataSet.Tables)\r
- {\r
- // loop on all ForeignKeyConstrain of the table.\r
- foreach (Constraint constraint in table.Constraints) {\r
- if (constraint is ForeignKeyConstraint) { \r
- ForeignKeyConstraint fk = (ForeignKeyConstraint) constraint;\r
- if (fk.RelatedTable == _table) {\r
- switch (action) {\r
- case DataRowAction.Delete:\r
- CheckChildRows(fk, action, fk.DeleteRule);\r
- break;\r
- case DataRowAction.Commit:\r
- case DataRowAction.Rollback:\r
- if (fk.AcceptRejectRule != AcceptRejectRule.None)\r
- CheckChildRows(fk, action, Rule.Cascade);\r
- break;\r
- default:\r
- CheckChildRows(fk, action, fk.UpdateRule);\r
- break;\r
- }\r
- } \r
- } \r
- }\r
- }\r
- }\r
- }\r
-\r
- private void CheckChildRows(ForeignKeyConstraint fkc, DataRowAction action, Rule rule)\r
- { \r
- DataRow[] childRows = GetChildRows(fkc, DataRowVersion.Current);\r
- switch (rule)\r
- {\r
- case Rule.Cascade: // delete or change all relted rows.\r
- if (childRows != null)\r
- {\r
- for (int j = 0; j < childRows.Length; j++)\r
- {\r
- // if action is delete we delete all child rows\r
- switch(action) {\r
- case DataRowAction.Delete: {\r
- if (childRows[j].RowState != DataRowState.Deleted)\r
- childRows[j].Delete();\r
-\r
- break;\r
- }\r
- // if action is change we change the values in the child row\r
- case DataRowAction.Change: {\r
- // change only the values in the key columns\r
- // set the childcolumn value to the new parent row value\r
- for (int k = 0; k < fkc.Columns.Length; k++)\r
- childRows[j][fkc.Columns[k]] = this[fkc.RelatedColumns[k], DataRowVersion.Proposed];\r
-\r
- break;\r
- }\r
-\r
- case DataRowAction.Rollback: {\r
- if (childRows[j].RowState != DataRowState.Unchanged)\r
- childRows[j].RejectChanges ();\r
-\r
- break;\r
- }\r
- }\r
- }\r
- }\r
- break;\r
- case Rule.None: // throw an exception if there are any child rows.\r
- if (childRows != null)\r
- {\r
- for (int j = 0; j < childRows.Length; j++)\r
- {\r
- if (childRows[j].RowState != DataRowState.Deleted)\r
- {\r
- string changeStr = "Cannot change this row because constraints are enforced on relation " + fkc.ConstraintName +", and changing this row will strand child rows.";\r
- string delStr = "Cannot delete this row because constraints are enforced on relation " + fkc.ConstraintName +", and deleting this row will strand child rows.";\r
- string message = action == DataRowAction.Delete ? delStr : changeStr;\r
- throw new InvalidConstraintException(message);\r
- }\r
- }\r
- }\r
- break;\r
- case Rule.SetDefault: // set the values in the child rows to the defult value of the columns.\r
- if (childRows != null && childRows.Length > 0) {\r
- int defaultValuesRowIndex = childRows[0].Table.DefaultValuesRowIndex;\r
- foreach(DataRow childRow in childRows) {\r
- if (childRow.RowState != DataRowState.Deleted) {\r
- int defaultIdx = childRow.IndexFromVersion(DataRowVersion.Default);\r
- foreach(DataColumn column in fkc.Columns) {\r
- column.DataContainer.CopyValue(defaultValuesRowIndex,defaultIdx);\r
- }\r
- }\r
- }\r
- }\r
- break;\r
- case Rule.SetNull: // set the values in the child row to null.\r
- if (childRows != null)\r
- {\r
- for (int j = 0; j < childRows.Length; j++)\r
- {\r
- DataRow child = childRows[j];\r
- if (childRows[j].RowState != DataRowState.Deleted)\r
- {\r
- // set only the key columns to DBNull\r
- for (int k = 0; k < fkc.Columns.Length; k++)\r
- child.SetNull(fkc.Columns[k]);\r
- }\r
- }\r
- }\r
- break;\r
- }\r
-\r
- }\r
-\r
- /// <summary>\r
- /// Ends the edit occurring on the row.\r
- /// </summary>\r
- public void EndEdit () \r
- {\r
- if (_inChangingEvent)\r
- throw new InRowChangingEventException("Cannot call EndEdit inside an OnRowChanging event.");\r
-\r
- if (RowState == DataRowState.Detached)\r
- return;\r
- \r
- if (HasVersion (DataRowVersion.Proposed))\r
- {\r
- CheckReadOnlyStatus();\r
-\r
- _inChangingEvent = true;\r
- try\r
- {\r
- _table.ChangingDataRow(this, DataRowAction.Change);\r
- }\r
- finally\r
- {\r
- _inChangingEvent = false;\r
- }\r
- \r
- //Calling next method validates UniqueConstraints\r
- //and ForeignKeys.\r
- bool rowValidated = false;\r
- try\r
- {\r
- if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad) {\r
- _table.Rows.ValidateDataRowInternal(this);\r
- rowValidated = true;\r
- }\r
- }\r
- catch (Exception e)\r
- {\r
- Table.RecordCache.DisposeRecord(Proposed);\r
- Proposed = -1;\r
- throw e;\r
- }\r
-\r
- CheckChildRows(DataRowAction.Change);\r
- if (Original != Current) {\r
- Table.RecordCache.DisposeRecord(Current);\r
- }\r
-\r
- Current = Proposed;\r
- Proposed = -1;\r
-\r
- if (!rowValidated) {\r
- // keep indexes updated even if there was no need to validate row\r
- foreach(Index index in Table.Indexes) {\r
- index.Update(this,Current); //FIXME: why Current ?!\r
- }\r
- }\r
-\r
- // Note : row state must not be changed before all the job on indexes finished,\r
- // since the indexes works with recods rather than with rows and the decision\r
- // which of row records to choose depends on row state.\r
- _table.ChangedDataRow(this, DataRowAction.Change);\r
- }\r
- }\r
-\r
- /// <summary>\r
- /// Gets the child rows of this DataRow using the specified DataRelation.\r
- /// </summary>\r
- public DataRow[] GetChildRows (DataRelation relation) \r
- {\r
- return GetChildRows (relation, DataRowVersion.Default);\r
- }\r
-\r
- /// <summary>\r
- /// Gets the child rows of a DataRow using the specified RelationName of a\r
- /// DataRelation.\r
- /// </summary>\r
- public DataRow[] GetChildRows (string relationName) \r
- {\r
- return GetChildRows (Table.DataSet.Relations[relationName]);\r
- }\r
-\r
- /// <summary>\r
- /// Gets the child rows of a DataRow using the specified DataRelation, and\r
- /// DataRowVersion.\r
- /// </summary>\r
- public DataRow[] GetChildRows (DataRelation relation, DataRowVersion version) \r
- {\r
- if (relation == null)\r
- return Table.NewRowArray(0);\r
-\r
- if (this.Table == null)\r
- 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.");\r
-\r
- if (relation.DataSet != this.Table.DataSet)\r
- throw new ArgumentException();\r
-\r
- if (_table != relation.ParentTable)\r
- throw new InvalidConstraintException ("GetChildRow requires a row whose Table is " + relation.ParentTable + ", but the specified row's table is " + _table);\r
-\r
- if (relation.ChildKeyConstraint != null)\r
- return GetChildRows (relation.ChildKeyConstraint, version);\r
-\r
- ArrayList rows = new ArrayList();\r
- DataColumn[] parentColumns = relation.ParentColumns;\r
- DataColumn[] childColumns = relation.ChildColumns;\r
- int numColumn = parentColumns.Length;\r
- DataRow[] result = null;\r
-\r
- int versionIndex = IndexFromVersion(version);\r
- int tmpRecord = relation.ChildTable.RecordCache.NewRecord();\r
-\r
- try {\r
- for (int i = 0; i < numColumn; i++) {\r
- // according to MSDN: the DataType value for both columns must be identical.\r
- childColumns[i].DataContainer.CopyValue(parentColumns[i].DataContainer, versionIndex, tmpRecord);\r
- }\r
-\r
- Index index = relation.ChildTable.FindIndex(childColumns);\r
-\r
- if (index != null) {\r
- int[] records = index.FindAll(tmpRecord);\r
- result = relation.ChildTable.NewRowArray(records.Length);\r
- for(int i=0; i < records.Length; i++) {\r
- result[i] = relation.ChildTable.RecordCache[records[i]];\r
- }\r
- }\r
- else {\r
- foreach (DataRow row in relation.ChildTable.Rows) {\r
- bool allColumnsMatch = false;\r
- if (row.HasVersion(DataRowVersion.Default)) {\r
- allColumnsMatch = true;\r
- int childIndex = row.IndexFromVersion(DataRowVersion.Default);\r
- for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) {\r
- if (childColumns[columnCnt].DataContainer.CompareValues(childIndex, tmpRecord) != 0) {\r
- allColumnsMatch = false;\r
- break;\r
- }\r
- }\r
- }\r
- if (allColumnsMatch) rows.Add(row);\r
- }\r
- result = relation.ChildTable.NewRowArray(rows.Count);\r
- rows.CopyTo(result, 0);\r
- }\r
- }\r
- finally {\r
- relation.ChildTable.RecordCache.DisposeRecord(tmpRecord);\r
- } \r
-\r
- return result;\r
- }\r
-\r
- /// <summary>\r
- /// Gets the child rows of a DataRow using the specified RelationName of a\r
- /// DataRelation, and DataRowVersion.\r
- /// </summary>\r
- public DataRow[] GetChildRows (string relationName, DataRowVersion version) \r
- {\r
- return GetChildRows (Table.DataSet.Relations[relationName], version);\r
- }\r
-\r
- private DataRow[] GetChildRows (ForeignKeyConstraint fkc, DataRowVersion version) \r
- {\r
- ArrayList rows = new ArrayList();\r
- DataColumn[] parentColumns = fkc.RelatedColumns;\r
- DataColumn[] childColumns = fkc.Columns;\r
- int numColumn = parentColumns.Length;\r
-\r
- Index index = fkc.Index;\r
-\r
- int curIndex = IndexFromVersion(version);\r
- int tmpRecord = fkc.Table.RecordCache.NewRecord();\r
- for (int i = 0; i < numColumn; i++) {\r
- // according to MSDN: the DataType value for both columns must be identical.\r
- childColumns[i].DataContainer.CopyValue(parentColumns[i].DataContainer, curIndex, tmpRecord);\r
- }\r
-\r
- try {\r
- if (index != null) {\r
- // get the child rows from the index\r
- int[] childRecords = index.FindAll(tmpRecord);\r
- for (int i = 0; i < childRecords.Length; i++) {\r
- rows.Add (childColumns[i].Table.RecordCache[childRecords[i]]);\r
- }\r
- }\r
- else { // if there is no index we search manualy.\r
- foreach (DataRow row in fkc.Table.Rows) {\r
- bool allColumnsMatch = false;\r
- if (row.HasVersion(DataRowVersion.Default)) {\r
- allColumnsMatch = true;\r
- int childIndex = row.IndexFromVersion(DataRowVersion.Default);\r
- for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) {\r
- if (childColumns[columnCnt].DataContainer.CompareValues(childIndex, tmpRecord) != 0) {\r
- allColumnsMatch = false;\r
- break;\r
- }\r
- }\r
- }\r
- if (allColumnsMatch) {\r
- rows.Add(row);\r
- }\r
- }\r
- }\r
- }\r
- finally {\r
- fkc.Table.RecordCache.DisposeRecord(tmpRecord);\r
- }\r
-\r
- DataRow[] result = fkc.Table.NewRowArray(rows.Count);\r
- rows.CopyTo(result, 0);\r
- return result;\r
- }\r
-\r
- /// <summary>\r
- /// Gets the error description of the specified DataColumn.\r
- /// </summary>\r
- public string GetColumnError (DataColumn column) \r
- {\r
- if (column == null)\r
- throw new ArgumentNullException("column");\r
-\r
- int index = _table.Columns.IndexOf(column);\r
- if (index < 0)\r
- throw new ArgumentException(String.Format("Column '{0}' does not belong to table {1}.", column.ColumnName, Table.TableName));\r
-\r
- return GetColumnError (index);\r
- }\r
-\r
- /// <summary>\r
- /// Gets the error description for the column specified by index.\r
- /// </summary>\r
- public string GetColumnError (int columnIndex) \r
- {\r
- if (columnIndex < 0 || columnIndex >= Table.Columns.Count)\r
- throw new IndexOutOfRangeException ();\r
-\r
- string retVal = null;\r
- if (columnIndex < ColumnErrors.Count) {\r
- retVal = (String) ColumnErrors[columnIndex];\r
- }\r
- return (retVal != null) ? retVal : String.Empty;\r
- }\r
-\r
- /// <summary>\r
- /// Gets the error description for the column, specified by name.\r
- /// </summary>\r
- public string GetColumnError (string columnName) \r
- {\r
- return GetColumnError (_table.Columns.IndexOf(columnName));\r
- }\r
-\r
- /// <summary>\r
- /// Gets an array of columns that have errors.\r
- /// </summary>\r
- public DataColumn[] GetColumnsInError () \r
- {\r
- ArrayList dataColumns = new ArrayList ();\r
-\r
- int columnOrdinal = 0;\r
- foreach(String columnError in ColumnErrors) {\r
- if (columnError != null && columnError != String.Empty) {\r
- dataColumns.Add (_table.Columns[columnOrdinal]);\r
- }\r
- columnOrdinal++;\r
- }\r
-\r
- return (DataColumn[])(dataColumns.ToArray (typeof(DataColumn)));\r
- }\r
-\r
- /// <summary>\r
- /// Gets the parent row of a DataRow using the specified DataRelation.\r
- /// </summary>\r
- public DataRow GetParentRow (DataRelation relation) \r
- {\r
- return GetParentRow (relation, DataRowVersion.Default);\r
- }\r
-\r
- /// <summary>\r
- /// Gets the parent row of a DataRow using the specified RelationName of a\r
- /// DataRelation.\r
- /// </summary>\r
- public DataRow GetParentRow (string relationName) \r
- {\r
- return GetParentRow (relationName, DataRowVersion.Default);\r
- }\r
-\r
- /// <summary>\r
- /// Gets the parent row of a DataRow using the specified DataRelation, and\r
- /// DataRowVersion.\r
- /// </summary>\r
- public DataRow GetParentRow (DataRelation relation, DataRowVersion version) \r
- {\r
- DataRow[] rows = GetParentRows(relation, version);\r
- if (rows.Length == 0) return null;\r
- return rows[0];\r
- }\r
-\r
- /// <summary>\r
- /// Gets the parent row of a DataRow using the specified RelationName of a \r
- /// DataRelation, and DataRowVersion.\r
- /// </summary>\r
- public DataRow GetParentRow (string relationName, DataRowVersion version) \r
- {\r
- return GetParentRow (Table.DataSet.Relations[relationName], version);\r
- }\r
-\r
- /// <summary>\r
- /// Gets the parent rows of a DataRow using the specified DataRelation.\r
- /// </summary>\r
- public DataRow[] GetParentRows (DataRelation relation) \r
- {\r
- return GetParentRows (relation, DataRowVersion.Default);\r
- }\r
-\r
- /// <summary>\r
- /// Gets the parent rows of a DataRow using the specified RelationName of a \r
- /// DataRelation.\r
- /// </summary>\r
- public DataRow[] GetParentRows (string relationName) \r
- {\r
- return GetParentRows (relationName, DataRowVersion.Default);\r
- }\r
-\r
- /// <summary>\r
- /// Gets the parent rows of a DataRow using the specified DataRelation, and\r
- /// DataRowVersion.\r
- /// </summary>\r
- public DataRow[] GetParentRows (DataRelation relation, DataRowVersion version) \r
- {\r
- // TODO: Caching for better preformance\r
- if (relation == null)\r
- return Table.NewRowArray(0);\r
-\r
- if (this.Table == null)\r
- 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.");\r
-\r
- if (relation.DataSet != this.Table.DataSet)\r
- throw new ArgumentException();\r
-\r
- if (_table != relation.ChildTable)\r
- throw new InvalidConstraintException ("GetParentRows requires a row whose Table is " + relation.ChildTable + ", but the specified row's table is " + _table);\r
-\r
- ArrayList rows = new ArrayList();\r
- DataColumn[] parentColumns = relation.ParentColumns;\r
- DataColumn[] childColumns = relation.ChildColumns;\r
- int numColumn = parentColumns.Length;\r
-\r
- int curIndex = IndexFromVersion(version);\r
- int tmpRecord = relation.ParentTable.RecordCache.NewRecord();\r
- for (int i = 0; i < numColumn; i++) {\r
- // according to MSDN: the DataType value for both columns must be identical.\r
- parentColumns[i].DataContainer.CopyValue(childColumns[i].DataContainer, curIndex, tmpRecord);\r
- }\r
-\r
- try {\r
- Index index = relation.ParentTable.FindIndex(parentColumns);\r
- if (index != null) { // get the parent rows from the index\r
- int[] parentRecords = index.FindAll(tmpRecord);\r
- for (int i = 0; i < parentRecords.Length; i++) {\r
- rows.Add (parentColumns[i].Table.RecordCache[parentRecords[i]]);\r
- }\r
- }\r
- else { // no index so we have to search manualy.\r
- foreach (DataRow row in relation.ParentTable.Rows) {\r
- bool allColumnsMatch = false;\r
- if (row.HasVersion(DataRowVersion.Default)) {\r
- allColumnsMatch = true;\r
- int parentIndex = row.IndexFromVersion(DataRowVersion.Default);\r
- for (int columnCnt = 0; columnCnt < numColumn; columnCnt++) {\r
- if (parentColumns[columnCnt].DataContainer.CompareValues(parentIndex, tmpRecord) != 0) {\r
- allColumnsMatch = false;\r
- break;\r
- }\r
- }\r
- }\r
- if (allColumnsMatch) {\r
- rows.Add(row);\r
- }\r
- }\r
- }\r
- }\r
- finally {\r
- relation.ParentTable.RecordCache.DisposeRecord(tmpRecord);\r
- }\r
-\r
- DataRow[] result = relation.ParentTable.NewRowArray(rows.Count);\r
- rows.CopyTo(result, 0);\r
- return result;\r
- }\r
-\r
- /// <summary>\r
- /// Gets the parent rows of a DataRow using the specified RelationName of a \r
- /// DataRelation, and DataRowVersion.\r
- /// </summary>\r
- public DataRow[] GetParentRows (string relationName, DataRowVersion version) \r
- {\r
- return GetParentRows (Table.DataSet.Relations[relationName], version);\r
- }\r
-\r
- /// <summary>\r
- /// Gets a value indicating whether a specified version exists.\r
- /// </summary>\r
- public bool HasVersion (DataRowVersion version) \r
- {\r
- switch (version) {\r
- case DataRowVersion.Default:\r
- return (Proposed >= 0 || Current >= 0);\r
- case DataRowVersion.Proposed:\r
- return Proposed >= 0;\r
- case DataRowVersion.Current:\r
- return Current >= 0;\r
- case DataRowVersion.Original:\r
- return Original >= 0;\r
- default:\r
- return IndexFromVersion(version) >= 0;\r
- }\r
- }\r
-\r
- /// <summary>\r
- /// Gets a value indicating whether the specified DataColumn contains a null value.\r
- /// </summary>\r
- public bool IsNull (DataColumn column) \r
- {\r
- return IsNull(column, DataRowVersion.Default);\r
- }\r
-\r
- /// <summary>\r
- /// Gets a value indicating whether the column at the specified index contains a null\r
- /// value.\r
- /// </summary>\r
- public bool IsNull (int columnIndex) \r
- {\r
- return IsNull(Table.Columns[columnIndex]);\r
- }\r
-\r
- /// <summary>\r
- /// Gets a value indicating whether the named column contains a null value.\r
- /// </summary>\r
- public bool IsNull (string columnName) \r
- {\r
- return IsNull(Table.Columns[columnName]);\r
- }\r
-\r
- /// <summary>\r
- /// Gets a value indicating whether the specified DataColumn and DataRowVersion\r
- /// contains a null value.\r
- /// </summary>\r
- public bool IsNull (DataColumn column, DataRowVersion version) \r
- {\r
- object o = this[column,version];\r
- return column.DataContainer.IsNull(IndexFromVersion(version));\r
- }\r
-\r
- /// <summary>\r
- /// Returns a value indicating whether all of the row columns specified contain a null value.\r
- /// </summary>\r
- internal bool IsNullColumns(DataColumn[] columns)\r
- {\r
- bool allNull = true;\r
- for (int i = 0; i < columns.Length; i++) \r
- {\r
- if (!IsNull(columns[i])) \r
- {\r
- allNull = false;\r
- break;\r
- }\r
- }\r
- return allNull;\r
- }\r
-\r
- /// <summary>\r
- /// Rejects all changes made to the row since AcceptChanges was last called.\r
- /// </summary>\r
- public void RejectChanges () \r
- {\r
- if (RowState == DataRowState.Detached)\r
- 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.");\r
- // If original is null, then nothing has happened since AcceptChanges\r
- // was last called. We have no "original" to go back to.\r
- if (HasVersion(DataRowVersion.Original)) {\r
- if (Current >= 0 && Current != Original) {\r
- Table.RecordCache.DisposeRecord(Current);\r
- }\r
- CheckChildRows(DataRowAction.Rollback);\r
-\r
- Current = Original;\r
- \r
- _table.ChangedDataRow (this, DataRowAction.Rollback);\r
- CancelEdit ();\r
- switch (RowState)\r
- {\r
- case DataRowState.Added:\r
- _table.DeleteRowFromIndexes (this);\r
- _table.Rows.RemoveInternal (this);\r
- break;\r
- case DataRowState.Modified:\r
- if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)\r
- _table.Rows.ValidateDataRowInternal(this);\r
- break;\r
- case DataRowState.Deleted:\r
- if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)\r
- _table.Rows.ValidateDataRowInternal(this);\r
- break;\r
- } \r
- \r
- } \r
- else {\r
- // If rows are just loaded via Xml the original values are null.\r
- // So in this case we have to remove all columns.\r
- // FIXME: I'm not realy sure, does this break something else, but\r
- // if so: FIXME ;)\r
- \r
- if ((RowState & DataRowState.Added) > 0)\r
- {\r
- _table.DeleteRowFromIndexes (this);\r
- _table.Rows.RemoveInternal (this);\r
- // if row was in Added state we move it to Detached.\r
- DetachRow();\r
- }\r
- }\r
- }\r
-\r
- /// <summary>\r
- /// Sets the error description for a column specified as a DataColumn.\r
- /// </summary>\r
- public void SetColumnError (DataColumn column, string error) \r
- {\r
- SetColumnError (_table.Columns.IndexOf (column), error);\r
- }\r
-\r
- /// <summary>\r
- /// Sets the error description for a column specified by index.\r
- /// </summary>\r
- public void SetColumnError (int columnIndex, string error) \r
- {\r
- if (columnIndex < 0 || columnIndex >= Table.Columns.Count)\r
- throw new IndexOutOfRangeException ();\r
-\r
- while(ColumnErrors.Count < columnIndex) {\r
- ColumnErrors.Add(null);\r
- }\r
- ColumnErrors.Add(error);\r
- }\r
-\r
- /// <summary>\r
- /// Sets the error description for a column specified by name.\r
- /// </summary>\r
- public void SetColumnError (string columnName, string error) \r
- {\r
- SetColumnError (_table.Columns.IndexOf (columnName), error);\r
- }\r
-\r
- /// <summary>\r
- /// Sets the value of the specified DataColumn to a null value.\r
- /// </summary>\r
- protected void SetNull (DataColumn column) \r
- {\r
- this[column] = DBNull.Value;\r
- }\r
-\r
- /// <summary>\r
- /// Sets the parent row of a DataRow with specified new parent DataRow.\r
- /// </summary>\r
- public void SetParentRow (DataRow parentRow) \r
- {\r
- SetParentRow(parentRow, null);\r
- }\r
-\r
- /// <summary>\r
- /// Sets the parent row of a DataRow with specified new parent DataRow and\r
- /// DataRelation.\r
- /// </summary>\r
- public void SetParentRow (DataRow parentRow, DataRelation relation) \r
- {\r
- if (_table == null || parentRow.Table == null)\r
- 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.");\r
-\r
- if (parentRow != null && _table.DataSet != parentRow.Table.DataSet)\r
- throw new ArgumentException();\r
- \r
- BeginEdit();\r
-\r
- IEnumerable relations; \r
- if (relation == null) {\r
- relations = _table.ParentRelations;\r
- }\r
- else {\r
- relations = new DataRelation[] { relation };\r
- }\r
-\r
- foreach (DataRelation rel in relations)\r
- {\r
- DataColumn[] childCols = rel.ChildColumns;\r
- DataColumn[] parentCols = rel.ParentColumns;\r
- \r
- for (int i = 0; i < parentCols.Length; i++)\r
- {\r
- if (parentRow == null) {\r
- childCols[i].DataContainer[Proposed] = DBNull.Value;\r
- }\r
- else {\r
- int defaultIdx = parentRow.IndexFromVersion(DataRowVersion.Default);\r
- childCols[i].DataContainer.CopyValue(parentCols[i].DataContainer,defaultIdx,Proposed);\r
- }\r
- }\r
- \r
- }\r
-\r
- EndEdit();\r
- }\r
- \r
- //Copy all values of this DataRow to the row parameter.\r
- internal void CopyValuesToRow(DataRow row)\r
- {\r
- if (row == null)\r
- throw new ArgumentNullException("row");\r
- if (row == this)\r
- throw new ArgumentException("'row' is the same as this object");\r
-\r
- foreach(DataColumn column in Table.Columns) {\r
- DataColumn targetColumn = row.Table.Columns[column.ColumnName];\r
- //if a column with the same name exists in both rows copy the values\r
- if(targetColumn != null) {\r
- int index = targetColumn.Ordinal;\r
- if (HasVersion(DataRowVersion.Original)) {\r
- if (row.Original < 0) {\r
- row.Original = row.Table.RecordCache.NewRecord();\r
- }\r
- object val = column[Original];\r
- row.CheckValue(val, targetColumn);\r
- targetColumn[row.Original] = val;\r
- }\r
- else {\r
- if (row.Original > 0) {\r
- row.Table.RecordCache.DisposeRecord(row.Original);\r
- row.Original = -1;\r
- }\r
- }\r
-\r
- if (HasVersion(DataRowVersion.Current)) {\r
- if (row.Current < 0) {\r
- row.Current = row.Table.RecordCache.NewRecord();\r
- }\r
- object val = column[Current];\r
- row.CheckValue(val, targetColumn);\r
- targetColumn[row.Current] = val;\r
- }\r
- else {\r
- if (row.Current > 0) {\r
- row.Table.RecordCache.DisposeRecord(row.Current);\r
- row.Current = -1;\r
- }\r
- }\r
-\r
- if (HasVersion(DataRowVersion.Proposed)) {\r
- if (row.Proposed < 0) {\r
- row.Proposed = row.Table.RecordCache.NewRecord();\r
- }\r
- object val = column[row.Proposed];\r
- row.CheckValue(val, targetColumn);\r
- targetColumn[row.Proposed] = val;\r
- }\r
- else {\r
- if (row.Proposed > 0) {\r
- row.Table.RecordCache.DisposeRecord(row.Proposed);\r
- row.Proposed = -1;\r
- }\r
- }\r
- }\r
- }\r
- if (HasErrors) {\r
- CopyErrors(row);\r
- }\r
- }\r
-\r
- internal void CopyErrors(DataRow row)\r
- {\r
- row.RowError = RowError;\r
- DataColumn[] errorColumns = GetColumnsInError();\r
- foreach(DataColumn col in errorColumns) {\r
- DataColumn targetColumn = row.Table.Columns[col.ColumnName];\r
- row.SetColumnError(targetColumn,GetColumnError(col));\r
- }\r
- }\r
-\r
- internal bool IsRowChanged(DataRowState rowState) {\r
- if((RowState & rowState) != 0)\r
- return true;\r
-\r
- //we need to find if child rows of this row changed.\r
- //if yes - we should return true\r
-\r
- // if the rowState is deleted we should get the original version of the row\r
- // else - we should get the current version of the row.\r
- DataRowVersion version = (rowState == DataRowState.Deleted) ? DataRowVersion.Original : DataRowVersion.Current;\r
- int count = Table.ChildRelations.Count;\r
- for (int i = 0; i < count; i++){\r
- DataRelation rel = Table.ChildRelations[i];\r
- DataRow[] childRows = GetChildRows(rel, version);\r
- for (int j = 0; j < childRows.Length; j++){\r
- if (childRows[j].IsRowChanged(rowState))\r
- return true;\r
- }\r
- }\r
-\r
- return false;\r
- }\r
-\r
- internal bool HasParentCollection\r
- {\r
- get\r
- {\r
- return _hasParentCollection;\r
- }\r
- set\r
- {\r
- _hasParentCollection = value;\r
- }\r
- }\r
-\r
- internal void CheckNullConstraints()\r
- {\r
- if (_nullConstraintViolation) {\r
- if (HasVersion(DataRowVersion.Proposed)) {\r
- foreach(DataColumn column in Table.Columns) {\r
- if (IsNull(column) && !column.AllowDBNull) {\r
- throw new NoNullAllowedException(_nullConstraintMessage);\r
- }\r
- }\r
- }\r
- _nullConstraintViolation = false;\r
- }\r
- }\r
- \r
- internal void CheckReadOnlyStatus() {\r
- int defaultIdx = IndexFromVersion(DataRowVersion.Default); \r
- foreach(DataColumn column in Table.Columns) {\r
- if ((column.DataContainer.CompareValues(defaultIdx,Proposed) != 0) && column.ReadOnly) {\r
- throw new ReadOnlyException();\r
- }\r
- }\r
- } \r
- \r
- #endregion // Methods\r
- }\r
-}\r
+//
+// System.Data.DataRow.cs
+//
+// Author:
+// 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>
+// Sureshkumar T <tsureshkumar@novell.com>
+//
+// (C) Ximian, Inc 2002
+// (C) Daniel Morgan 2002, 2003
+// Copyright (C) 2002 Tim Coleman
+//
+
+//
+// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Data.Common;
+using System.Collections;
+using System.Globalization;
+using System.Xml;
+
+using System.Data.Common;
+
+namespace System.Data {
+ /// <summary>
+ /// Represents a row of data in a DataTable.
+ /// </summary>
+ [Serializable]
+ public class DataRow
+ {
+ #region Fields
+
+ private DataTable _table;
+
+ internal int _original = -1;
+ internal int _current = -1;
+ internal int _proposed = -1;
+
+ private ArrayList _columnErrors;
+ private string rowError;
+ internal int xmlRowID = 0;
+ internal bool _nullConstraintViolation;
+ private string _nullConstraintMessage;
+ private bool _hasParentCollection;
+ private bool _inChangingEvent;
+ private int _rowId;
+
+ private XmlDataDocument.XmlDataElement mappedElement;
+ internal bool _inExpressionEvaluation = false;
+
+ #endregion // Fields
+
+ #region Constructors
+
+ /// <summary>
+ /// This member supports the .NET Framework infrastructure and is not intended to be
+ /// used directly from your code.
+ /// </summary>
+ protected internal DataRow (DataRowBuilder builder)
+ {
+ _table = builder.Table;
+ // Get the row id from the builder.
+ _rowId = builder._rowId;
+
+ rowError = String.Empty;
+
+ // 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);
+ }
+
+ internal DataRow(DataTable table,int rowId)
+ {
+ _table = table;
+ _rowId = rowId;
+ }
+
+ #endregion // Constructors
+
+ #region Properties
+
+ private ArrayList ColumnErrors
+ {
+ get {
+ if (_columnErrors == null) {
+ _columnErrors = new ArrayList();
+ }
+ return _columnErrors;
+ }
+
+ set {
+ _columnErrors = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether there are errors in a row.
+ /// </summary>
+ public bool HasErrors {
+ get {
+ if (RowError != string.Empty)
+ return true;
+
+ foreach(String columnError in ColumnErrors) {
+ if (columnError != null && columnError != string.Empty) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the data stored in the column specified by name.
+ /// </summary>
+ public object this[string columnName] {
+ get { return this[columnName, DataRowVersion.Default]; }
+ set {
+ int columnIndex = _table.Columns.IndexOf (columnName);
+ if (columnIndex == -1)
+ throw new IndexOutOfRangeException ();
+ this[columnIndex] = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the data stored in specified DataColumn
+ /// </summary>
+ public object this[DataColumn column] {
+
+ get {
+ return this[column, DataRowVersion.Default];}
+ set {
+ int columnIndex = _table.Columns.IndexOf (column);
+ if (columnIndex == -1)
+ throw new ArgumentException ("The column does not belong to this table.");
+ this[columnIndex] = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the data stored in column specified by index.
+ /// </summary>
+ public object this[int columnIndex] {
+ get { return this[columnIndex, DataRowVersion.Default]; }
+ set {
+ if (columnIndex < 0 || columnIndex > _table.Columns.Count)
+ throw new IndexOutOfRangeException ();
+ if (RowState == DataRowState.Deleted)
+ throw new DeletedRowInaccessibleException ();
+
+ DataColumn column = _table.Columns[columnIndex];
+ _table.ChangingDataColumn (this, column, value);
+
+ if (value == null && column.DataType != typeof(string)) {
+ throw new ArgumentException("Cannot set column " + column.ColumnName + " to be null, Please use DBNull instead");
+ }
+
+ CheckValue (value, column);
+
+ bool orginalEditing = Proposed >= 0;
+ if (!orginalEditing) {
+ BeginEdit ();
+ }
+
+ column[Proposed] = value;
+ _table.ChangedDataColumn (this, column, value);
+ if (!orginalEditing) {
+ EndEdit ();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets the specified version of data stored in the named column.
+ /// </summary>
+ public object this[string columnName, DataRowVersion version] {
+ get {
+ int columnIndex = _table.Columns.IndexOf (columnName);
+ if (columnIndex == -1)
+ throw new IndexOutOfRangeException ();
+ return this[columnIndex, version];
+ }
+ }
+
+ /// <summary>
+ /// Gets the specified version of data stored in the specified DataColumn.
+ /// </summary>
+ public object this[DataColumn column, DataRowVersion version] {
+ get {
+ if (column.Table != Table)
+ throw new ArgumentException ("The column does not belong to this table.");
+ int columnIndex = column.Ordinal;
+ return this[columnIndex, version];
+ }
+ }
+
+ /// <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.
+ /// </summary>
+ public object this[int columnIndex, DataRowVersion version] {
+ 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];
+ int recordIndex = IndexFromVersion(version);
+
+ 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.");
+
+ return column[recordIndex];
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets all of the values for this row through an array.
+ /// </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)
+ throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
+
+ object[] items = new object[_table.Columns.Count];
+ foreach(DataColumn column in _table.Columns) {
+ items[column.Ordinal] = column[Current];
+ }
+ return items;
+ }
+ set {
+ if (value.Length > _table.Columns.Count)
+ throw new ArgumentException ();
+
+ if (RowState == DataRowState.Deleted)
+ throw new DeletedRowInaccessibleException ();
+
+ BeginEdit ();
+
+ DataColumnChangeEventArgs e = new DataColumnChangeEventArgs();
+ foreach(DataColumn column in _table.Columns) {
+ int i = column.Ordinal;
+ object newVal = (i < value.Length) ? value[i] : null;
+
+ if (newVal == null)
+ continue;
+
+ e.Initialize(this, column, newVal);
+ CheckValue (e.ProposedValue, column);
+ _table.RaiseOnColumnChanging(e);
+ column[Proposed] = e.ProposedValue;
+ _table.RaiseOnColumnChanged(e);
+ }
+
+ EndEdit ();
+ }
+ }
+
+ /// <summary>
+ /// Gets the current state of the row in regards to its relationship to the
+ /// DataRowCollection.
+ /// </summary>
+ public DataRowState RowState {
+ get {
+ //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;
+ }
+ }
+
+ /// <summary>
+ /// Gets the DataTable for which this row has a schema.
+ /// </summary>
+ public DataTable Table {
+ get {
+ return _table;
+ }
+ }
+
+ /// <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;
+ }
+ }
+
+ 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 (Proposed != -1) {
+ if (Current >= 0) {
+ Table.RecordCache.DisposeRecord(Current);
+ }
+ Current = Proposed;
+ Proposed = -1;
+ }
+ }
+
+ //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);
+ 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;
+ }
+
+ 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)
+ {
+ if (_hasParentCollection && col.ReadOnly) {
+ throw new ReadOnlyException ();
+ }
+
+ if (v == null || v == DBNull.Value) {
+ if (col.AllowDBNull || col.AutoIncrement || col.DefaultValue != DBNull.Value) {
+ return;
+ }
+
+ //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.";
+
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the custom error description for a row.
+ /// </summary>
+ public string RowError {
+ get {
+ return rowError;
+ }
+ set {
+ rowError = value;
+ }
+ }
+
+ internal int IndexFromVersion(DataRowVersion version)
+ {
+ 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.");
+ }
+ }
+
+ 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 {
+ get { return mappedElement; }
+ set { mappedElement = value; }
+ }
+
+ internal void SetOriginalValue (string columnName, object val)
+ {
+ DataColumn column = _table.Columns[columnName];
+ _table.ChangingDataColumn (this, column, val);
+
+ if (Original < 0 || Original == Current) {
+ Original = Table.RecordCache.NewRecord();
+ }
+ CheckValue (val, column);
+ column[Original] = val;
+ }
+
+ /// <summary>
+ /// Commits all the changes made to this row since the last time AcceptChanges was
+ /// called.
+ /// </summary>
+ public void AcceptChanges ()
+ {
+ EndEdit(); // in case it hasn't been called
+
+ _table.ChangingDataRow (this, DataRowAction.Commit);
+ CheckChildRows(DataRowAction.Commit);
+ switch (RowState) {
+ case DataRowState.Unchanged:
+ return;
+ case DataRowState.Added:
+ case DataRowState.Modified:
+ if (Original >= 0) {
+ Table.RecordCache.DisposeRecord(Original);
+ }
+ Original = Current;
+ 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.");
+ }
+
+ _table.ChangedDataRow (this, DataRowAction.Commit);
+ }
+
+ /// <summary>
+ /// Begins an edit operation on a DataRow object.
+ /// </summary>
+ 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)) {
+ 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);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Cancels the current edit on the row.
+ /// </summary>
+ public void CancelEdit ()
+ {
+ if (_inChangingEvent) {
+ throw new InRowChangingEventException("Cannot call CancelEdit inside an OnRowChanging event.");
+ }
+
+ if (HasVersion (DataRowVersion.Proposed)) {
+ Table.RecordCache.DisposeRecord(Proposed);
+ Proposed = -1;
+ }
+ }
+
+ /// <summary>
+ /// Clears the errors for the row, including the RowError and errors set with
+ /// SetColumnError.
+ /// </summary>
+ public void ClearErrors ()
+ {
+ rowError = String.Empty;
+ ColumnErrors.Clear();
+ }
+
+ /// <summary>
+ /// Deletes the DataRow.
+ /// </summary>
+ public void Delete ()
+ {
+ _table.DeletingDataRow(this, DataRowAction.Delete);
+ switch (RowState) {
+ case DataRowState.Added:
+ // 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:
+ break;
+ default:
+ // check what to do with child rows
+ CheckChildRows(DataRowAction.Delete);
+ _table.DeleteRowFromIndexes (this);
+ break;
+ }
+ if (Current >= 0) {
+ if (Current != Original) {
+ _table.RecordCache.DisposeRecord(Current);
+ }
+ Current = -1;
+ }
+ _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 (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.Current);
+ 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
+ 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
+ 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];
+
+ break;
+ }
+
+ case DataRowAction.Rollback: {
+ if (childRows[j].RowState != DataRowState.Unchanged)
+ childRows[j].RejectChanges ();
+
+ break;
+ }
+ }
+ }
+ }
+ 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 && childRows.Length > 0) {
+ int defaultValuesRowIndex = childRows[0].Table.DefaultValuesRowIndex;
+ foreach(DataRow childRow in childRows) {
+ if (childRow.RowState != DataRowState.Deleted) {
+ int defaultIdx = childRow.IndexFromVersion(DataRowVersion.Default);
+ foreach(DataColumn column in fkc.Columns) {
+ column.DataContainer.CopyValue(defaultValuesRowIndex,defaultIdx);
+ }
+ }
+ }
+ }
+ 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>
+ public void EndEdit ()
+ {
+ if (_inChangingEvent)
+ throw new InRowChangingEventException("Cannot call EndEdit inside an OnRowChanging event.");
+
+ if (RowState == DataRowState.Detached)
+ return;
+
+ if (HasVersion (DataRowVersion.Proposed))
+ {
+ CheckReadOnlyStatus();
+
+ _inChangingEvent = true;
+ try
+ {
+ _table.ChangingDataRow(this, DataRowAction.Change);
+ }
+ finally
+ {
+ _inChangingEvent = false;
+ }
+
+ //Calling next method validates UniqueConstraints
+ //and ForeignKeys.
+ bool rowValidated = false;
+ try
+ {
+ if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad) {
+ _table.Rows.ValidateDataRowInternal(this);
+ rowValidated = true;
+ }
+ }
+ catch (Exception e)
+ {
+ Table.RecordCache.DisposeRecord(Proposed);
+ Proposed = -1;
+ throw e;
+ }
+
+ CheckChildRows(DataRowAction.Change);
+ if (Original != Current) {
+ Table.RecordCache.DisposeRecord(Current);
+ }
+
+ Current = Proposed;
+ Proposed = -1;
+
+ if (!rowValidated) {
+ // keep indexes updated even if there was no need to validate row
+ foreach(Index index in Table.Indexes) {
+ index.Update(this,Current); //FIXME: why Current ?!
+ }
+ }
+
+ // 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);
+ }
+ }
+
+ /// <summary>
+ /// Gets the child rows of this DataRow using the specified DataRelation.
+ /// </summary>
+ public DataRow[] GetChildRows (DataRelation relation)
+ {
+ return GetChildRows (relation, DataRowVersion.Default);
+ }
+
+ /// <summary>
+ /// Gets the child rows of a DataRow using the specified RelationName of a
+ /// DataRelation.
+ /// </summary>
+ public DataRow[] GetChildRows (string relationName)
+ {
+ return GetChildRows (Table.DataSet.Relations[relationName]);
+ }
+
+ /// <summary>
+ /// Gets the child rows of a DataRow using the specified DataRelation, and
+ /// DataRowVersion.
+ /// </summary>
+ public DataRow[] GetChildRows (DataRelation relation, DataRowVersion version)
+ {
+ if (relation == null)
+ 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.");
+
+ if (relation.DataSet != this.Table.DataSet)
+ throw new ArgumentException();
+
+ if (_table != relation.ParentTable)
+ throw new InvalidConstraintException ("GetChildRow requires a row whose Table is " + relation.ParentTable + ", but the specified row's table is " + _table);
+
+ 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;
+ 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);
+ }
+ result = relation.ChildTable.NewRowArray(rows.Count);
+ rows.CopyTo(result, 0);
+ }
+ }
+ finally {
+ relation.ChildTable.RecordCache.DisposeRecord(tmpRecord);
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Gets the child rows of a DataRow using the specified RelationName of a
+ /// DataRelation, and DataRowVersion.
+ /// </summary>
+ public DataRow[] GetChildRows (string relationName, DataRowVersion version)
+ {
+ 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;
+
+ 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
+ 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.
+ foreach (DataRow row in fkc.Table.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);
+ }
+ }
+ }
+ }
+ finally {
+ fkc.Table.RecordCache.DisposeRecord(tmpRecord);
+ }
+
+ DataRow[] result = fkc.Table.NewRowArray(rows.Count);
+ rows.CopyTo(result, 0);
+ return result;
+ }
+
+ /// <summary>
+ /// Gets the error description of the specified DataColumn.
+ /// </summary>
+ public string GetColumnError (DataColumn 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>
+ /// Gets the error description for the column specified by index.
+ /// </summary>
+ public string GetColumnError (int columnIndex)
+ {
+ if (columnIndex < 0 || columnIndex >= Table.Columns.Count)
+ throw new IndexOutOfRangeException ();
+
+ string retVal = null;
+ if (columnIndex < ColumnErrors.Count) {
+ retVal = (String) ColumnErrors[columnIndex];
+ }
+ return (retVal != null) ? retVal : String.Empty;
+ }
+
+ /// <summary>
+ /// Gets the error description for the column, specified by name.
+ /// </summary>
+ public string GetColumnError (string columnName)
+ {
+ return GetColumnError (_table.Columns.IndexOf(columnName));
+ }
+
+ /// <summary>
+ /// Gets an array of columns that have errors.
+ /// </summary>
+ public DataColumn[] GetColumnsInError ()
+ {
+ ArrayList dataColumns = new ArrayList ();
+
+ int columnOrdinal = 0;
+ foreach(String columnError in ColumnErrors) {
+ if (columnError != null && columnError != String.Empty) {
+ dataColumns.Add (_table.Columns[columnOrdinal]);
+ }
+ columnOrdinal++;
+ }
+
+ return (DataColumn[])(dataColumns.ToArray (typeof(DataColumn)));
+ }
+
+ /// <summary>
+ /// Gets the parent row of a DataRow using the specified DataRelation.
+ /// </summary>
+ public DataRow GetParentRow (DataRelation relation)
+ {
+ return GetParentRow (relation, DataRowVersion.Default);
+ }
+
+ /// <summary>
+ /// Gets the parent row of a DataRow using the specified RelationName of a
+ /// DataRelation.
+ /// </summary>
+ public DataRow GetParentRow (string relationName)
+ {
+ return GetParentRow (relationName, DataRowVersion.Default);
+ }
+
+ /// <summary>
+ /// Gets the parent row of a DataRow using the specified DataRelation, and
+ /// DataRowVersion.
+ /// </summary>
+ public DataRow GetParentRow (DataRelation relation, DataRowVersion version)
+ {
+ 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>
+ public DataRow GetParentRow (string relationName, DataRowVersion version)
+ {
+ return GetParentRow (Table.DataSet.Relations[relationName], version);
+ }
+
+ /// <summary>
+ /// Gets the parent rows of a DataRow using the specified DataRelation.
+ /// </summary>
+ public DataRow[] GetParentRows (DataRelation relation)
+ {
+ return GetParentRows (relation, DataRowVersion.Default);
+ }
+
+ /// <summary>
+ /// Gets the parent rows of a DataRow using the specified RelationName of a
+ /// DataRelation.
+ /// </summary>
+ public DataRow[] GetParentRows (string relationName)
+ {
+ return GetParentRows (relationName, DataRowVersion.Default);
+ }
+
+ /// <summary>
+ /// Gets the parent rows of a DataRow using the specified DataRelation, and
+ /// DataRowVersion.
+ /// </summary>
+ public DataRow[] GetParentRows (DataRelation relation, DataRowVersion version)
+ {
+ // TODO: Caching for better preformance
+ if (relation == null)
+ 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.");
+
+ if (relation.DataSet != this.Table.DataSet)
+ throw new ArgumentException();
+
+ if (_table != relation.ChildTable)
+ throw new InvalidConstraintException ("GetParentRows requires a row whose Table is " + relation.ChildTable + ", but the specified row's table is " + _table);
+
+ ArrayList rows = new ArrayList();
+ DataColumn[] parentColumns = relation.ParentColumns;
+ DataColumn[] childColumns = relation.ChildColumns;
+ int numColumn = parentColumns.Length;
+
+ int curIndex = IndexFromVersion(version);
+ int tmpRecord = relation.ParentTable.RecordCache.NewRecord();
+ 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)) {
+ allColumnsMatch = true;
+ int parentIndex = row.IndexFromVersion(DataRowVersion.Default);
+ for (int columnCnt = 0; columnCnt < numColumn; columnCnt++) {
+ if (parentColumns[columnCnt].DataContainer.CompareValues(parentIndex, tmpRecord) != 0) {
+ allColumnsMatch = false;
+ break;
+ }
+ }
+ }
+ if (allColumnsMatch) {
+ rows.Add(row);
+ }
+ }
+ }
+ }
+ finally {
+ relation.ParentTable.RecordCache.DisposeRecord(tmpRecord);
+ }
+
+ 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>
+ public DataRow[] GetParentRows (string relationName, DataRowVersion version)
+ {
+ return GetParentRows (Table.DataSet.Relations[relationName], version);
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether a specified version exists.
+ /// </summary>
+ public bool HasVersion (DataRowVersion version)
+ {
+ switch (version) {
+ case DataRowVersion.Default:
+ return (Proposed >= 0 || Current >= 0);
+ case DataRowVersion.Proposed:
+ return Proposed >= 0;
+ case DataRowVersion.Current:
+ return Current >= 0;
+ case DataRowVersion.Original:
+ return Original >= 0;
+ default:
+ return IndexFromVersion(version) >= 0;
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether the specified DataColumn contains a null value.
+ /// </summary>
+ public bool IsNull (DataColumn column)
+ {
+ return IsNull(column, DataRowVersion.Default);
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether the column at the specified index contains a null
+ /// value.
+ /// </summary>
+ public bool IsNull (int columnIndex)
+ {
+ return IsNull(Table.Columns[columnIndex]);
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether the named column contains a null value.
+ /// </summary>
+ public bool IsNull (string columnName)
+ {
+ return IsNull(Table.Columns[columnName]);
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether the specified DataColumn and DataRowVersion
+ /// contains a null value.
+ /// </summary>
+ public bool IsNull (DataColumn column, DataRowVersion version)
+ {
+ object o = this[column,version];
+ return column.DataContainer.IsNull(IndexFromVersion(version));
+ }
+
+ /// <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>
+ /// Rejects all changes made to the row since AcceptChanges was last called.
+ /// </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 (HasVersion(DataRowVersion.Original)) {
+ if (Current >= 0 && Current != Original) {
+ Table.RecordCache.DisposeRecord(Current);
+ }
+ CheckChildRows(DataRowAction.Rollback);
+
+ 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);
+ break;
+ case DataRowState.Deleted:
+ 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 error description for a column specified as a DataColumn.
+ /// </summary>
+ public void SetColumnError (DataColumn column, string error)
+ {
+ SetColumnError (_table.Columns.IndexOf (column), error);
+ }
+
+ /// <summary>
+ /// Sets the error description for a column specified by index.
+ /// </summary>
+ public void SetColumnError (int columnIndex, string error)
+ {
+ if (columnIndex < 0 || columnIndex >= Table.Columns.Count)
+ throw new IndexOutOfRangeException ();
+
+ while(ColumnErrors.Count < columnIndex) {
+ ColumnErrors.Add(null);
+ }
+ ColumnErrors.Add(error);
+ }
+
+ /// <summary>
+ /// Sets the error description for a column specified by name.
+ /// </summary>
+ public void SetColumnError (string columnName, string error)
+ {
+ SetColumnError (_table.Columns.IndexOf (columnName), error);
+ }
+
+ /// <summary>
+ /// Sets the value of the specified DataColumn to a null value.
+ /// </summary>
+ protected void SetNull (DataColumn column)
+ {
+ this[column] = DBNull.Value;
+ }
+
+ /// <summary>
+ /// Sets the parent row of a DataRow with specified new parent DataRow.
+ /// </summary>
+ public void SetParentRow (DataRow parentRow)
+ {
+ SetParentRow(parentRow, null);
+ }
+
+ /// <summary>
+ /// Sets the parent row of a DataRow with specified new parent DataRow and
+ /// DataRelation.
+ /// </summary>
+ public void SetParentRow (DataRow parentRow, DataRelation relation)
+ {
+ if (_table == null || parentRow.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.");
+
+ if (parentRow != null && _table.DataSet != parentRow.Table.DataSet)
+ throw new ArgumentException();
+
+ BeginEdit();
+
+ IEnumerable relations;
+ if (relation == null) {
+ relations = _table.ParentRelations;
+ }
+ else {
+ relations = new DataRelation[] { relation };
+ }
+
+ foreach (DataRelation rel in relations)
+ {
+ DataColumn[] childCols = rel.ChildColumns;
+ DataColumn[] parentCols = rel.ParentColumns;
+
+ for (int i = 0; i < parentCols.Length; 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 DataRow 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");
+
+ 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];
+ row.CheckValue(val, targetColumn);
+ targetColumn[row.Original] = val;
+ }
+ else {
+ if (row.Original > 0) {
+ row.Table.RecordCache.DisposeRecord(row.Original);
+ row.Original = -1;
+ }
+ }
+
+ if (HasVersion(DataRowVersion.Current)) {
+ if (row.Current < 0) {
+ row.Current = row.Table.RecordCache.NewRecord();
+ }
+ object val = column[Current];
+ row.CheckValue(val, targetColumn);
+ targetColumn[row.Current] = val;
+ }
+ 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();
+ }
+ object val = column[row.Proposed];
+ row.CheckValue(val, targetColumn);
+ targetColumn[row.Proposed] = val;
+ }
+ else {
+ if (row.Proposed > 0) {
+ row.Table.RecordCache.DisposeRecord(row.Proposed);
+ row.Proposed = -1;
+ }
+ }
+ }
+ }
+ if (HasErrors) {
+ CopyErrors(row);
+ }
+ }
+
+ internal void CopyErrors(DataRow row)
+ {
+ row.RowError = RowError;
+ 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) {
+ 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;
+ }
+ }
+
+ internal void CheckNullConstraints()
+ {
+ if (_nullConstraintViolation) {
+ if (HasVersion(DataRowVersion.Proposed)) {
+ foreach(DataColumn column in Table.Columns) {
+ if (IsNull(column) && !column.AllowDBNull) {
+ throw new NoNullAllowedException(_nullConstraintMessage);
+ }
+ }
+ }
+ _nullConstraintViolation = false;
+ }
+ }
+
+ internal void CheckReadOnlyStatus() {
+ int defaultIdx = IndexFromVersion(DataRowVersion.Default);
+ foreach(DataColumn column in Table.Columns) {
+ 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>
+ [MonoTODO ("Raise necessary Events")]
+ internal void Load (object [] values, LoadOption loadOption, bool is_new)
+ {
+ DataRowAction action = DataRowAction.Change;
+
+ int temp = Table.RecordCache.NewRecord ();
+ for (int i = 0 ; i < Table.Columns.Count; i++)
+ SetValue (i, values [i], temp);
+
+ if (is_new) { // new row
+ if (editing || RowState == DataRowState.Detached)
+ Proposed = temp;
+ else
+ Current = temp;
+ return;
+ }
+
+ if (loadOption == LoadOption.OverwriteChanges
+ || (loadOption == LoadOption.PreserveChanges
+ && rowState == DataRowState.Unchanged)) {
+ Original = temp;
+ if (editing)
+ Proposed = temp;
+ else
+ Current = temp;
+ rowState = DataRowState.Unchanged;
+ action = DataRowAction.ChangeCurrentAndOriginal;
+ return;
+ }
+
+ if (loadOption == LoadOption.PreserveChanges) {
+ if (rowState != DataRowState.Deleted) {
+ Original = temp;
+ rowState = DataRowState.Modified;
+ action = DataRowAction.ChangeOriginal;
+ }
+ return;
+ }
+
+ bool not_used = true;
+ // Upsert
+ if (rowState != DataRowState.Deleted) {
+ int index = editing ? _proposed : _current;
+ if (! RecordCache.CompareRecords (Table, index, temp)) {
+ if (editing)
+ Proposed = temp;
+ else
+ Current = temp;
+ not_used = false;
+ if (rowState == DataRowState.Unchanged)
+ rowState = DataRowState.Modified;
+ }
+ }
+
+ if (not_used)
+ Table.RecordCache.DisposeRecord (temp);
+ }
+#endif // NET_2_0
+ }
+}