2 // System.Data.DataRow.cs
5 // Rodrigo Moya <rodrigo@ximian.com>
6 // Daniel Morgan <danmorg@sc.rr.com>
7 // Tim Coleman <tim@timcoleman.com>
8 // Ville Palo <vi64pa@koti.soon.fi>
9 // Alan Tam Siu Lung <Tam@SiuLung.com>
11 // (C) Ximian, Inc 2002
12 // (C) Daniel Morgan 2002, 2003
13 // Copyright (C) 2002 Tim Coleman
17 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
19 // Permission is hereby granted, free of charge, to any person obtaining
20 // a copy of this software and associated documentation files (the
21 // "Software"), to deal in the Software without restriction, including
22 // without limitation the rights to use, copy, modify, merge, publish,
23 // distribute, sublicense, and/or sell copies of the Software, and to
24 // permit persons to whom the Software is furnished to do so, subject to
25 // the following conditions:
27 // The above copyright notice and this permission notice shall be
28 // included in all copies or substantial portions of the Software.
30 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
31 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
32 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
33 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
34 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
35 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
36 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
40 using System.Collections;
41 using System.Globalization;
44 namespace System.Data {
46 /// Represents a row of data in a DataTable.
53 private DataTable _table;
55 private int _original = -1;
56 private int _current = -1;
57 private int _proposed = -1;
59 private string[] columnErrors;
60 private string rowError;
61 private DataRowState rowState;
62 internal int xmlRowID = 0;
63 internal bool _nullConstraintViolation;
64 private string _nullConstraintMessage;
65 private bool editing = false;
66 private bool _hasParentCollection;
67 private bool _inChangingEvent;
70 private XmlDataDocument.XmlDataElement mappedElement;
71 internal bool _inExpressionEvaluation = false;
78 /// This member supports the .NET Framework infrastructure and is not intended to be
79 /// used directly from your code.
81 protected internal DataRow (DataRowBuilder builder)
83 _table = builder.Table;
84 // Get the row id from the builder.
85 _rowId = builder._rowId;
87 _proposed = _table.RecordCache.NewRecord();
88 // Initialise the data columns of the row with the dafault values, if any
89 // TODO : should proposed version be available immediately after record creation ?
90 foreach(DataColumn column in _table.Columns) {
91 column[_proposed] = column.DefaultValue;
94 // TODO : consider allocation when needed
95 columnErrors = new string[_table.Columns.Count];
96 rowError = String.Empty;
98 //on first creating a DataRow it is always detached.
99 rowState = DataRowState.Detached;
101 ArrayList aiColumns = _table.Columns.AutoIncrmentColumns;
102 foreach (string col in aiColumns) {
103 DataColumn dc = _table.Columns[col];
104 this [dc] = dc.AutoIncrementValue();
107 // create mapped XmlDataElement
108 DataSet ds = _table.DataSet;
109 if (ds != null && ds._xmlDataDocument != null)
110 mappedElement = new XmlDataDocument.XmlDataElement (this, _table.Prefix, _table.TableName, _table.Namespace, ds._xmlDataDocument);
113 #endregion // Constructors
118 /// Gets a value indicating whether there are errors in a row.
120 public bool HasErrors {
122 if (RowError != string.Empty)
125 for (int i= 0; i < columnErrors.Length; i++){
126 if (columnErrors[i] != null && columnErrors[i] != string.Empty)
135 /// Gets or sets the data stored in the column specified by name.
137 public object this[string columnName] {
138 get { return this[columnName, DataRowVersion.Default]; }
140 int columnIndex = _table.Columns.IndexOf (columnName);
141 if (columnIndex == -1)
142 throw new IndexOutOfRangeException ();
143 this[columnIndex] = value;
148 /// Gets or sets the data stored in specified DataColumn
150 public object this[DataColumn column] {
153 return this[column, DataRowVersion.Default];}
155 int columnIndex = _table.Columns.IndexOf (column);
156 if (columnIndex == -1)
157 throw new ArgumentException ("The column does not belong to this table.");
158 this[columnIndex] = value;
163 /// Gets or sets the data stored in column specified by index.
165 public object this[int columnIndex] {
166 get { return this[columnIndex, DataRowVersion.Default]; }
168 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
169 throw new IndexOutOfRangeException ();
170 if (rowState == DataRowState.Deleted)
171 throw new DeletedRowInaccessibleException ();
172 DataColumn column = _table.Columns[columnIndex];
173 _table.ChangingDataColumn (this, column, value);
176 bool orginalEditing = editing;
177 if (!orginalEditing) BeginEdit ();
178 object v = SetColumnValue (value, column, columnIndex);
179 column[_proposed] = v;
180 _table.ChangedDataColumn (this, column, v);
181 if (!orginalEditing) EndEdit ();
186 /// Gets the specified version of data stored in the named column.
188 public object this[string columnName, DataRowVersion version] {
190 int columnIndex = _table.Columns.IndexOf (columnName);
191 if (columnIndex == -1)
192 throw new IndexOutOfRangeException ();
193 return this[columnIndex, version];
198 /// Gets the specified version of data stored in the specified DataColumn.
200 public object this[DataColumn column, DataRowVersion version] {
202 if (column.Table != Table)
203 throw new ArgumentException ("The column does not belong to this table.");
204 int columnIndex = column.Ordinal;
205 return this[columnIndex, version];
210 /// Gets the data stored in the column, specified by index and version of the data to
213 public object this[int columnIndex, DataRowVersion version] {
215 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
216 throw new IndexOutOfRangeException ();
217 // Accessing deleted rows
218 if (!_inExpressionEvaluation && rowState == DataRowState.Deleted && version != DataRowVersion.Original)
219 throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
221 DataColumn column = _table.Columns[columnIndex];
222 if (column.Expression != String.Empty) {
223 object o = column.CompiledExpression.Eval (this);
224 return Convert.ChangeType (o, column.DataType);
227 int recordIndex = IndexFromVersion(version);
229 if (recordIndex >= 0) {
230 return column[recordIndex];
233 if (rowState == DataRowState.Detached && version == DataRowVersion.Default && _proposed < 0)
234 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.");
236 throw new VersionNotFoundException (Locale.GetText ("There is no " + version.ToString () + " data to access."));
241 /// Gets or sets all of the values for this row through an array.
243 public object[] ItemArray {
246 if (rowState == DataRowState.Detached)
247 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.");
248 // Accessing deleted rows
249 if (rowState == DataRowState.Deleted)
250 throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
252 object[] items = new object[_table.Columns.Count];
253 foreach(DataColumn column in _table.Columns) {
254 items[column.Ordinal] = column[_current];
259 if (value.Length > _table.Columns.Count)
260 throw new ArgumentException ();
262 if (rowState == DataRowState.Deleted)
263 throw new DeletedRowInaccessibleException ();
265 bool orginalEditing = editing;
266 if (!orginalEditing) {
269 object newVal = null;
270 DataColumnChangeEventArgs e = new DataColumnChangeEventArgs();
271 foreach(DataColumn column in _table.Columns) {
272 int i = column.Ordinal;
273 newVal = (i < value.Length) ? value[i] : null;
275 e.Initialize(this, column, newVal);
276 _table.RaiseOnColumnChanged(e);
277 column[_proposed] = SetColumnValue (e.ProposedValue, column, i);
279 if (!orginalEditing) {
286 /// Gets the current state of the row in regards to its relationship to the
287 /// DataRowCollection.
289 public DataRowState RowState {
296 /// Gets the DataTable for which this row has a schema.
298 public DataTable Table {
305 /// Gets and sets index of row. This is used from
308 internal int XmlRowID {
318 /// Gets and sets index of row.
333 //FIXME?: Couldn't find a way to set the RowState when adding the DataRow
334 //to a Datatable so I added this method. Delete if there is a better way.
335 internal void AttachRow() {
337 Table.RecordCache.DisposeRecord(_current);
339 _current = _proposed;
341 rowState = DataRowState.Added;
344 //FIXME?: Couldn't find a way to set the RowState when removing the DataRow
345 //from a Datatable so I added this method. Delete if there is a better way.
346 internal void DetachRow() {
347 if (_proposed >= 0) {
348 _table.RecordCache.DisposeRecord(_proposed);
352 _hasParentCollection = false;
353 rowState = DataRowState.Detached;
356 private object SetColumnValue (object v, DataColumn col, int index)
358 object newval = null;
360 if (_hasParentCollection && col.ReadOnly && v != this[index])
361 throw new ReadOnlyException ();
365 if (col.DataType.ToString().Equals("System.Guid"))
366 throw new ArgumentException("Cannot set column to be null, Please use DBNull instead");
368 if(col.DefaultValue != DBNull.Value)
370 newval = col.DefaultValue;
372 else if(col.AutoIncrement == true)
374 // AutoIncrement column is already filled,
375 // so it just need to return existing value.
376 newval = this [index];
380 if (!col.AllowDBNull)
382 //Constraint violations during data load is raise in DataTable EndLoad
383 this._nullConstraintViolation = true;
384 if (this.Table._duringDataLoad) {
385 this.Table._nullConstraintViolationDuringDataLoad = true;
387 _nullConstraintMessage = "Column '" + col.ColumnName + "' does not allow nulls.";
390 newval = DBNull.Value;
393 else if (v == DBNull.Value)
396 if (!col.AllowDBNull)
398 //Constraint violations during data load is raise in DataTable EndLoad
399 this._nullConstraintViolation = true;
400 if (this.Table._duringDataLoad) {
401 this.Table._nullConstraintViolationDuringDataLoad = true;
403 _nullConstraintMessage = "Column '" + col.ColumnName + "' does not allow nulls.";
406 newval = DBNull.Value;
410 // Type vType = v.GetType(); // data type of value
411 // Type cType = col.DataType; // column data type
412 // if (cType != vType)
414 // TypeCode typeCode = Type.GetTypeCode(cType);
415 // switch(typeCode) {
416 // case TypeCode.Boolean :
417 // v = Convert.ToBoolean (v);
419 // case TypeCode.Byte :
420 // v = Convert.ToByte (v);
422 // case TypeCode.Char :
423 // v = Convert.ToChar (v);
425 // case TypeCode.DateTime :
426 // v = Convert.ToDateTime (v);
428 // case TypeCode.Decimal :
429 // v = Convert.ToDecimal (v);
431 // case TypeCode.Double :
432 // v = Convert.ToDouble (v);
434 // case TypeCode.Int16 :
435 // v = Convert.ToInt16 (v);
437 // case TypeCode.Int32 :
438 // v = Convert.ToInt32 (v);
440 // case TypeCode.Int64 :
441 // v = Convert.ToInt64 (v);
443 // case TypeCode.SByte :
444 // v = Convert.ToSByte (v);
446 // case TypeCode.Single :
447 // v = Convert.ToSingle (v);
449 // case TypeCode.String :
450 // v = Convert.ToString (v);
452 // case TypeCode.UInt16 :
453 // v = Convert.ToUInt16 (v);
455 // case TypeCode.UInt32 :
456 // v = Convert.ToUInt32 (v);
458 // case TypeCode.UInt64 :
459 // v = Convert.ToUInt64 (v);
463 // switch(cType.ToString()) {
464 // case "System.TimeSpan" :
465 // v = (System.TimeSpan) v;
467 // case "System.Type" :
468 // v = (System.Type) v;
470 // case "System.Object" :
471 // //v = (System.Object) v;
474 // if (!cType.IsArray)
475 // throw new InvalidCastException("Type not supported.");
480 // vType = v.GetType();
483 // The MaxLength property is ignored for non-text columns
484 // if ((Type.GetTypeCode(vType) == TypeCode.String) && (col.MaxLength != -1) &&
485 // (this.Table.Columns[index].MaxLength < ((string)v).Length)) {
486 // throw new ArgumentException("Cannot set column '" + col.ColumnName + "' to '" + v + "'. The value violates the MaxLength limit of this column.");
489 // if(col.AutoIncrement == true) {
490 // long inc = Convert.ToInt64(v);
491 // col.UpdateAutoIncrementValue (inc);
494 // col.DataHasBeenSet = true;
498 internal void SetValuesFromDataRecord(IDataRecord record, int[] mapping)
500 if ( mapping.Length > Table.Columns.Count)
501 throw new ArgumentException ();
503 // bool orginalEditing = editing;
504 // if (!orginalEditing) {
508 if (!HasVersion(DataRowVersion.Proposed)) {
509 _proposed = Table.RecordCache.NewRecord();
512 for(int i=0; i < mapping.Length; i++) {
513 DataColumn column = Table.Columns[i];
514 column.DataContainer.SetItemFromDataRecord(_proposed, record,mapping[i]);
515 if ( column.AutoIncrement ) {
516 this[column] = column[_proposed];
521 Table.RecordCache.DisposeRecord(_proposed);
526 // if (!orginalEditing) {
532 /// Gets or sets the custom error description for a row.
534 public string RowError {
543 internal int IndexFromVersion(DataRowVersion version)
545 if (HasVersion(version))
549 case DataRowVersion.Default:
550 if (editing || rowState == DataRowState.Detached) {
551 recordIndex = _proposed;
554 recordIndex = _current;
557 case DataRowVersion.Proposed:
558 recordIndex = _proposed;
560 case DataRowVersion.Current:
561 recordIndex = _current;
563 case DataRowVersion.Original:
564 recordIndex = _original;
567 throw new ArgumentException ();
574 internal XmlDataDocument.XmlDataElement DataElement {
575 get { return mappedElement; }
576 set { mappedElement = value; }
579 internal void SetOriginalValue (string columnName, object val)
581 int columnIndex = _table.Columns.IndexOf (columnName);
582 DataColumn column = _table.Columns[columnIndex];
583 _table.ChangingDataColumn (this, column, val);
586 _original = Table.RecordCache.NewRecord();
588 val = SetColumnValue (val, column, columnIndex);
589 column[_original] = val;
590 rowState = DataRowState.Modified;
594 /// Commits all the changes made to this row since the last time AcceptChanges was
597 public void AcceptChanges ()
599 EndEdit(); // in case it hasn't been called
601 case DataRowState.Unchanged:
603 case DataRowState.Added:
604 case DataRowState.Modified:
605 rowState = DataRowState.Unchanged;
607 case DataRowState.Deleted:
608 _table.Rows.RemoveInternal (this);
611 case DataRowState.Detached:
612 throw new RowNotInTableException("Cannot perform this operation on a row not in the table.");
614 // Accept from detached
615 if (_original >= 0) {
616 Table.RecordCache.DisposeRecord(_original);
618 _original = _current;
622 /// Begins an edit operation on a DataRow object.
624 public void BeginEdit ()
626 if (_inChangingEvent)
627 throw new InRowChangingEventException("Cannot call BeginEdit inside an OnRowChanging event.");
628 if (rowState == DataRowState.Deleted)
629 throw new DeletedRowInaccessibleException ();
630 if (!HasVersion (DataRowVersion.Proposed)) {
631 _proposed = Table.RecordCache.NewRecord();
632 foreach(DataColumn column in Table.Columns) {
633 column.DataContainer.CopyValue(_current,_proposed);
636 // setting editing to true stops validations on the row
641 /// Cancels the current edit on the row.
643 public void CancelEdit ()
645 if (_inChangingEvent)
646 throw new InRowChangingEventException("Cannot call CancelEdit inside an OnRowChanging event.");
648 if (HasVersion (DataRowVersion.Proposed)) {
649 Table.RecordCache.DisposeRecord(_proposed);
651 if (rowState == DataRowState.Modified) {
652 rowState = DataRowState.Unchanged;
658 /// Clears the errors for the row, including the RowError and errors set with
661 public void ClearErrors ()
663 rowError = String.Empty;
664 columnErrors = new String[_table.Columns.Count];
668 /// Deletes the DataRow.
670 public void Delete ()
672 _table.DeletingDataRow(this, DataRowAction.Delete);
674 case DataRowState.Added:
675 // check what to do with child rows
676 CheckChildRows(DataRowAction.Delete);
677 _table.DeleteRowFromIndexes (this);
678 Table.Rows.RemoveInternal (this);
680 // if row was in Added state we move it to Detached.
683 case DataRowState.Deleted:
686 // check what to do with child rows
687 CheckChildRows(DataRowAction.Delete);
688 _table.DeleteRowFromIndexes (this);
689 rowState = DataRowState.Deleted;
692 _table.DeletedDataRow(this, DataRowAction.Delete);
695 // check the child rows of this row before deleting the row.
696 private void CheckChildRows(DataRowAction action)
699 // in this method we find the row that this row is in a relation with them.
700 // in shortly we find all child rows of this row.
701 // then we function according to the DeleteRule of the foriegnkey.
704 // 1. find if this row is attached to dataset.
705 // 2. find if EnforceConstraints is true.
706 // 3. find if there are any constraint on the table that the row is in.
707 if (_table.DataSet != null && _table.DataSet.EnforceConstraints && _table.Constraints.Count > 0)
709 foreach (DataTable table in _table.DataSet.Tables)
711 // loop on all ForeignKeyConstrain of the table.
712 foreach (ForeignKeyConstraint fk in table.Constraints.ForeignKeyConstraints)
714 if (fk.RelatedTable == _table)
717 if (action == DataRowAction.Delete)
718 rule = fk.DeleteRule;
720 rule = fk.UpdateRule;
721 CheckChildRows(fk, action, rule);
728 private void CheckChildRows(ForeignKeyConstraint fkc, DataRowAction action, Rule rule)
730 DataRow[] childRows = GetChildRows(fkc, DataRowVersion.Default);
733 case Rule.Cascade: // delete or change all relted rows.
734 if (childRows != null)
736 for (int j = 0; j < childRows.Length; j++)
738 // if action is delete we delete all child rows
739 if (action == DataRowAction.Delete)
741 if (childRows[j].RowState != DataRowState.Deleted)
742 childRows[j].Delete();
744 // if action is change we change the values in the child row
745 else if (action == DataRowAction.Change)
747 // change only the values in the key columns
748 // set the childcolumn value to the new parent row value
749 for (int k = 0; k < fkc.Columns.Length; k++)
750 childRows[j][fkc.Columns[k]] = this[fkc.RelatedColumns[k], DataRowVersion.Proposed];
755 case Rule.None: // throw an exception if there are any child rows.
756 if (childRows != null)
758 for (int j = 0; j < childRows.Length; j++)
760 if (childRows[j].RowState != DataRowState.Deleted)
762 string changeStr = "Cannot change this row because constraints are enforced on relation " + fkc.ConstraintName +", and changing this row will strand child rows.";
763 string delStr = "Cannot delete this row because constraints are enforced on relation " + fkc.ConstraintName +", and deleting this row will strand child rows.";
764 string message = action == DataRowAction.Delete ? delStr : changeStr;
765 throw new InvalidConstraintException(message);
770 case Rule.SetDefault: // set the values in the child rows to the defult value of the columns.
771 if (childRows != null)
773 for (int j = 0; j < childRows.Length; j++)
775 DataRow child = childRows[j];
776 if (childRows[j].RowState != DataRowState.Deleted)
778 //set only the key columns to default
779 for (int k = 0; k < fkc.Columns.Length; k++)
780 child[fkc.Columns[k]] = fkc.Columns[k].DefaultValue;
785 case Rule.SetNull: // set the values in the child row to null.
786 if (childRows != null)
788 for (int j = 0; j < childRows.Length; j++)
790 DataRow child = childRows[j];
791 if (childRows[j].RowState != DataRowState.Deleted)
793 // set only the key columns to DBNull
794 for (int k = 0; k < fkc.Columns.Length; k++)
795 child.SetNull(fkc.Columns[k]);
805 /// Ends the edit occurring on the row.
807 public void EndEdit ()
809 if (_inChangingEvent)
810 throw new InRowChangingEventException("Cannot call EndEdit inside an OnRowChanging event.");
811 if (rowState == DataRowState.Detached)
817 CheckReadOnlyStatus();
818 if (HasVersion (DataRowVersion.Proposed))
820 _inChangingEvent = true;
823 _table.ChangingDataRow(this, DataRowAction.Change);
827 _inChangingEvent = false;
829 if (rowState == DataRowState.Unchanged)
830 rowState = DataRowState.Modified;
832 //Calling next method validates UniqueConstraints
836 if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
837 _table.Rows.ValidateDataRowInternal(this);
842 Table.RecordCache.DisposeRecord(_proposed);
847 // Now we are going to check all child rows of current row.
848 // In the case the cascade is true the child rows will look up for
849 // parent row. since lookup in index is always on current,
850 // we have to move proposed version of current row to current
851 // in the case of check child row failure we are rolling
852 // current row state back.
853 int backup = _current;
854 _current = _proposed;
855 bool editing_backup = editing;
858 // check all child rows.
859 CheckChildRows(DataRowAction.Change);
861 if (_original != backup) {
862 Table.RecordCache.DisposeRecord(backup);
865 catch (Exception ex) {
866 // if check child rows failed - rollback to previous state
867 // i.e. restore proposed and current versions
868 _proposed = _current;
870 editing = editing_backup;
871 // since we failed - propagate an exception
874 _table.ChangedDataRow(this, DataRowAction.Change);
879 /// Gets the child rows of this DataRow using the specified DataRelation.
881 public DataRow[] GetChildRows (DataRelation relation)
883 return GetChildRows (relation, DataRowVersion.Current);
887 /// Gets the child rows of a DataRow using the specified RelationName of a
890 public DataRow[] GetChildRows (string relationName)
892 return GetChildRows (Table.DataSet.Relations[relationName]);
896 /// Gets the child rows of a DataRow using the specified DataRelation, and
899 public DataRow[] GetChildRows (DataRelation relation, DataRowVersion version)
901 if (relation == null)
902 return new DataRow[0];
904 if (this.Table == null || RowState == DataRowState.Detached)
905 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.");
907 if (relation.DataSet != this.Table.DataSet)
908 throw new ArgumentException();
910 if (relation.ChildKeyConstraint != null)
911 return GetChildRows (relation.ChildKeyConstraint, version);
913 ArrayList rows = new ArrayList();
914 DataColumn[] parentColumns = relation.ParentColumns;
915 DataColumn[] childColumns = relation.ChildColumns;
916 int numColumn = parentColumns.Length;
917 if (HasVersion(version))
919 object[] vals = new object[parentColumns.Length];
920 for (int i = 0; i < vals.Length; i++)
921 vals[i] = this[parentColumns[i], version];
923 foreach (DataRow row in relation.ChildTable.Rows)
925 bool allColumnsMatch = false;
926 if (row.HasVersion(DataRowVersion.Default))
928 allColumnsMatch = true;
929 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt)
931 if (!vals[columnCnt].Equals(
932 row[childColumns[columnCnt], DataRowVersion.Default]))
934 allColumnsMatch = false;
939 if (allColumnsMatch) rows.Add(row);
942 DataRow[] result = relation.ChildTable.NewRowArray(rows.Count);
943 rows.CopyTo(result, 0);
948 /// Gets the child rows of a DataRow using the specified RelationName of a
949 /// DataRelation, and DataRowVersion.
951 public DataRow[] GetChildRows (string relationName, DataRowVersion version)
953 return GetChildRows (Table.DataSet.Relations[relationName], version);
956 private DataRow[] GetChildRows (ForeignKeyConstraint fkc, DataRowVersion version)
958 ArrayList rows = new ArrayList();
959 DataColumn[] parentColumns = fkc.RelatedColumns;
960 DataColumn[] childColumns = fkc.Columns;
961 int numColumn = parentColumns.Length;
962 if (HasVersion(version)) {
963 Index index = fkc.Index;
965 // get the child rows from the index
966 Node[] childNodes = index.FindAllSimple (parentColumns, IndexFromVersion(version));
967 for (int i = 0; i < childNodes.Length; i++) {
968 rows.Add (childNodes[i].Row);
971 else { // if there is no index we search manualy.
972 int curIndex = IndexFromVersion(DataRowVersion.Current);
973 int tmpRecord = fkc.Table.RecordCache.NewRecord();
976 for (int i = 0; i < numColumn; i++) {
977 // according to MSDN: the DataType value for both columns must be identical.
978 childColumns[i].DataContainer.CopyValue(parentColumns[i].DataContainer, curIndex, tmpRecord);
981 foreach (DataRow row in fkc.Table.Rows) {
982 bool allColumnsMatch = false;
983 if (row.HasVersion(DataRowVersion.Default)) {
984 allColumnsMatch = true;
985 int childIndex = row.IndexFromVersion(DataRowVersion.Default);
986 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) {
987 if (childColumns[columnCnt].DataContainer.CompareValues(childIndex, tmpRecord) != 0) {
988 allColumnsMatch = false;
993 if (allColumnsMatch) {
999 fkc.Table.RecordCache.DisposeRecord(tmpRecord);
1004 DataRow[] result = fkc.Table.NewRowArray(rows.Count);
1005 rows.CopyTo(result, 0);
1010 /// Gets the error description of the specified DataColumn.
1012 public string GetColumnError (DataColumn column)
1014 return GetColumnError (_table.Columns.IndexOf(column));
1018 /// Gets the error description for the column specified by index.
1020 public string GetColumnError (int columnIndex)
1022 if (columnIndex < 0 || columnIndex >= columnErrors.Length)
1023 throw new IndexOutOfRangeException ();
1025 string retVal = columnErrors[columnIndex];
1027 retVal = string.Empty;
1032 /// Gets the error description for the column, specified by name.
1034 public string GetColumnError (string columnName)
1036 return GetColumnError (_table.Columns.IndexOf(columnName));
1040 /// Gets an array of columns that have errors.
1042 public DataColumn[] GetColumnsInError ()
1044 ArrayList dataColumns = new ArrayList ();
1046 for (int i = 0; i < columnErrors.Length; i += 1)
1048 if (columnErrors[i] != null && columnErrors[i] != String.Empty)
1049 dataColumns.Add (_table.Columns[i]);
1052 return (DataColumn[])(dataColumns.ToArray (typeof(DataColumn)));
1056 /// Gets the parent row of a DataRow using the specified DataRelation.
1058 public DataRow GetParentRow (DataRelation relation)
1060 return GetParentRow (relation, DataRowVersion.Current);
1064 /// Gets the parent row of a DataRow using the specified RelationName of a
1067 public DataRow GetParentRow (string relationName)
1069 return GetParentRow (relationName, DataRowVersion.Current);
1073 /// Gets the parent row of a DataRow using the specified DataRelation, and
1076 public DataRow GetParentRow (DataRelation relation, DataRowVersion version)
1078 DataRow[] rows = GetParentRows(relation, version);
1079 if (rows.Length == 0) return null;
1084 /// Gets the parent row of a DataRow using the specified RelationName of a
1085 /// DataRelation, and DataRowVersion.
1087 public DataRow GetParentRow (string relationName, DataRowVersion version)
1089 return GetParentRow (Table.DataSet.Relations[relationName], version);
1093 /// Gets the parent rows of a DataRow using the specified DataRelation.
1095 public DataRow[] GetParentRows (DataRelation relation)
1097 return GetParentRows (relation, DataRowVersion.Current);
1101 /// Gets the parent rows of a DataRow using the specified RelationName of a
1104 public DataRow[] GetParentRows (string relationName)
1106 return GetParentRows (relationName, DataRowVersion.Current);
1110 /// Gets the parent rows of a DataRow using the specified DataRelation, and
1113 public DataRow[] GetParentRows (DataRelation relation, DataRowVersion version)
1115 // TODO: Caching for better preformance
1116 if (relation == null)
1117 return new DataRow[0];
1119 if (this.Table == null || RowState == DataRowState.Detached)
1120 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.");
1122 if (relation.DataSet != this.Table.DataSet)
1123 throw new ArgumentException();
1125 ArrayList rows = new ArrayList();
1126 DataColumn[] parentColumns = relation.ParentColumns;
1127 DataColumn[] childColumns = relation.ChildColumns;
1128 int numColumn = parentColumns.Length;
1129 if (HasVersion(version)) {
1130 Index indx = relation.ParentTable.GetIndexByColumns (parentColumns);
1131 if (indx != null) { // get the child rows from the index
1132 Node[] childNodes = indx.FindAllSimple(childColumns, IndexFromVersion(version));
1133 for (int i = 0; i < childNodes.Length; i++) {
1134 rows.Add (childNodes[i].Row);
1137 else { // no index so we have to search manualy.
1138 int curIndex = IndexFromVersion(DataRowVersion.Current);
1139 int tmpRecord = relation.ParentTable.RecordCache.NewRecord();
1142 for (int i = 0; i < numColumn; i++) {
1143 // according to MSDN: the DataType value for both columns must be identical.
1144 parentColumns[i].DataContainer.CopyValue(childColumns[i].DataContainer, curIndex, tmpRecord);
1147 foreach (DataRow row in relation.ParentTable.Rows) {
1148 bool allColumnsMatch = false;
1149 if (row.HasVersion(DataRowVersion.Default)) {
1150 allColumnsMatch = true;
1151 int parentIndex = row.IndexFromVersion(DataRowVersion.Default);
1152 for (int columnCnt = 0; columnCnt < numColumn; columnCnt++) {
1153 if (parentColumns[columnCnt].DataContainer.CompareValues(parentIndex, tmpRecord) != 0) {
1154 allColumnsMatch = false;
1159 if (allColumnsMatch) {
1165 relation.ParentTable.RecordCache.DisposeRecord(tmpRecord);
1170 DataRow[] result = relation.ParentTable.NewRowArray(rows.Count);
1171 rows.CopyTo(result, 0);
1176 /// Gets the parent rows of a DataRow using the specified RelationName of a
1177 /// DataRelation, and DataRowVersion.
1179 public DataRow[] GetParentRows (string relationName, DataRowVersion version)
1181 return GetParentRows (Table.DataSet.Relations[relationName], version);
1185 /// Gets a value indicating whether a specified version exists.
1187 public bool HasVersion (DataRowVersion version)
1190 case DataRowVersion.Default:
1191 if (rowState == DataRowState.Deleted && !_inExpressionEvaluation)
1193 if (rowState == DataRowState.Detached)
1194 return _proposed >= 0;
1196 case DataRowVersion.Proposed:
1197 if (rowState == DataRowState.Deleted && !_inExpressionEvaluation)
1199 return _proposed >= 0;
1200 case DataRowVersion.Current:
1201 if ((rowState == DataRowState.Deleted && !_inExpressionEvaluation) || rowState == DataRowState.Detached)
1203 return _current >= 0;
1204 case DataRowVersion.Original:
1205 if (rowState == DataRowState.Detached)
1207 return _original >= 0;
1213 /// Gets a value indicating whether the specified DataColumn contains a null value.
1215 public bool IsNull (DataColumn column)
1217 return IsNull(column, DataRowVersion.Default);
1221 /// Gets a value indicating whether the column at the specified index contains a null
1224 public bool IsNull (int columnIndex)
1226 return IsNull(Table.Columns[columnIndex]);
1230 /// Gets a value indicating whether the named column contains a null value.
1232 public bool IsNull (string columnName)
1234 return IsNull(Table.Columns[columnName]);
1238 /// Gets a value indicating whether the specified DataColumn and DataRowVersion
1239 /// contains a null value.
1241 public bool IsNull (DataColumn column, DataRowVersion version)
1243 return column.DataContainer.IsNull(IndexFromVersion(version));
1247 /// Returns a value indicating whether all of the row columns specified contain a null value.
1249 internal bool IsNullColumns(DataColumn[] columns)
1251 bool allNull = true;
1252 for (int i = 0; i < columns.Length; i++)
1254 if (!IsNull(columns[i]))
1264 /// Rejects all changes made to the row since AcceptChanges was last called.
1266 public void RejectChanges ()
1268 if (RowState == DataRowState.Detached)
1269 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.");
1270 // If original is null, then nothing has happened since AcceptChanges
1271 // was last called. We have no "original" to go back to.
1272 if (HasVersion(DataRowVersion.Original)) {
1273 if (_current >= 0 ) {
1274 Table.RecordCache.DisposeRecord(_current);
1276 _current = _original;
1278 _table.ChangedDataRow (this, DataRowAction.Rollback);
1282 case DataRowState.Added:
1283 _table.DeleteRowFromIndexes (this);
1284 _table.Rows.RemoveInternal (this);
1286 case DataRowState.Modified:
1287 if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
1288 _table.Rows.ValidateDataRowInternal(this);
1289 rowState = DataRowState.Unchanged;
1291 case DataRowState.Deleted:
1292 rowState = DataRowState.Unchanged;
1293 if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
1294 _table.Rows.ValidateDataRowInternal(this);
1300 // If rows are just loaded via Xml the original values are null.
1301 // So in this case we have to remove all columns.
1302 // FIXME: I'm not realy sure, does this break something else, but
1305 if ((rowState & DataRowState.Added) > 0)
1307 _table.DeleteRowFromIndexes (this);
1308 _table.Rows.RemoveInternal (this);
1309 // if row was in Added state we move it to Detached.
1316 /// Sets the error description for a column specified as a DataColumn.
1318 public void SetColumnError (DataColumn column, string error)
1320 SetColumnError (_table.Columns.IndexOf (column), error);
1324 /// Sets the error description for a column specified by index.
1326 public void SetColumnError (int columnIndex, string error)
1328 if (columnIndex < 0 || columnIndex >= columnErrors.Length)
1329 throw new IndexOutOfRangeException ();
1330 columnErrors[columnIndex] = error;
1334 /// Sets the error description for a column specified by name.
1336 public void SetColumnError (string columnName, string error)
1338 SetColumnError (_table.Columns.IndexOf (columnName), error);
1342 /// Sets the value of the specified DataColumn to a null value.
1344 protected void SetNull (DataColumn column)
1346 this[column] = DBNull.Value;
1350 /// Sets the parent row of a DataRow with specified new parent DataRow.
1352 public void SetParentRow (DataRow parentRow)
1354 SetParentRow(parentRow, null);
1358 /// Sets the parent row of a DataRow with specified new parent DataRow and
1361 public void SetParentRow (DataRow parentRow, DataRelation relation)
1363 if (_table == null || parentRow.Table == null || RowState == DataRowState.Detached)
1364 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.");
1366 if (parentRow != null && _table.DataSet != parentRow.Table.DataSet)
1367 throw new ArgumentException();
1370 if (relation == null)
1372 foreach (DataRelation parentRel in _table.ParentRelations)
1374 DataColumn[] childCols = parentRel.ChildKeyConstraint.Columns;
1375 DataColumn[] parentCols = parentRel.ChildKeyConstraint.RelatedColumns;
1377 for (int i = 0; i < parentCols.Length; i++)
1379 if (parentRow == null)
1380 this[childCols[i].Ordinal] = DBNull.Value;
1382 this[childCols[i].Ordinal] = parentRow[parentCols[i]];
1389 DataColumn[] childCols = relation.ChildKeyConstraint.Columns;
1390 DataColumn[] parentCols = relation.ChildKeyConstraint.RelatedColumns;
1392 for (int i = 0; i < parentCols.Length; i++)
1394 if (parentRow == null)
1395 this[childCols[i].Ordinal] = DBNull.Value;
1397 this[childCols[i].Ordinal] = parentRow[parentCols[i]];
1403 //Copy all values of this DataaRow to the row parameter.
1404 internal void CopyValuesToRow(DataRow row)
1408 throw new ArgumentNullException("row");
1410 throw new ArgumentException("'row' is the same as this object");
1412 foreach(DataColumn column in Table.Columns) {
1413 DataColumn targetColumn = row.Table.Columns[column.ColumnName];
1414 //if a column with the same name exists in both rows copy the values
1415 if(targetColumn != null) {
1416 int index = targetColumn.Ordinal;
1417 if (HasVersion(DataRowVersion.Original)) {
1418 if (row._original < 0) {
1419 row._original = row.Table.RecordCache.NewRecord();
1421 targetColumn[row._original] = row.SetColumnValue(column[_original], targetColumn, index);
1423 if (HasVersion(DataRowVersion.Current)) {
1424 if (row._current < 0) {
1425 row._current = row.Table.RecordCache.NewRecord();
1427 targetColumn[row._current] = row.SetColumnValue(column[_current], targetColumn, index);
1429 if (HasVersion(DataRowVersion.Proposed)) {
1430 if (row._proposed < 0) {
1431 row._proposed = row.Table.RecordCache.NewRecord();
1433 targetColumn[row._proposed] = row.SetColumnValue(column[_proposed], targetColumn, index);
1436 //Saving the current value as the column value
1437 row[index] = targetColumn[row._current];
1442 row.rowState = RowState;
1443 row.RowError = RowError;
1444 row.columnErrors = columnErrors;
1448 private void CollectionChanged(object sender, System.ComponentModel.CollectionChangeEventArgs args)
1450 // if a column is added we have to initialize the values
1451 // of the original, current and proposed versions.
1452 if (args.Action == System.ComponentModel.CollectionChangeAction.Add)
1454 // get the last column added
1455 int index = Table.Columns.Count - 1;
1456 DataColumn column = Table.Columns[index];
1457 if (HasVersion(DataRowVersion.Current)) {
1458 column[_current] = SetColumnValue(null, column, index);
1460 if (HasVersion(DataRowVersion.Proposed)) {
1461 column[_proposed] = SetColumnValue(null, column, index);
1463 if (HasVersion(DataRowVersion.Original)) {
1464 column[_original] = SetColumnValue(null, column, index);
1469 internal void onColumnRemoved(int columnIndex)
1471 // when column removed we have to compress row values in the way
1472 // they will correspond to new column ordinals
1475 // if (current != null)
1477 // tmp = new object[current.Length - 1];
1478 // // copy values before removed column
1479 // if (columnIndex > 0)
1480 // Array.Copy (current, 0, tmp, 0, columnIndex);
1481 // // copy values after removed column
1482 // if(columnIndex < current.Length - 1)
1483 // Array.Copy(current, columnIndex + 1, tmp, columnIndex, current.Length - 1 - columnIndex);
1487 // if (proposed != null)
1489 // tmp = new object[proposed.Length - 1];
1490 // // copy values before removed column
1491 // if (columnIndex > 0)
1492 // Array.Copy (proposed, 0, tmp, 0, columnIndex);
1493 // // copy values after removed column
1494 // if(columnIndex < proposed.Length - 1)
1495 // Array.Copy(proposed, columnIndex + 1, tmp, columnIndex, proposed.Length - 1 - columnIndex);
1499 // if (original != null)
1501 // tmp = new object[original.Length - 1];
1502 // // copy values before removed column
1503 // if (columnIndex > 0)
1504 // Array.Copy (original, 0, tmp, 0, columnIndex);
1505 // // copy values after removed column
1506 // if(columnIndex < original.Length - 1)
1507 // Array.Copy(original, columnIndex + 1, tmp, columnIndex, original.Length - 1 - columnIndex);
1513 internal bool IsRowChanged(DataRowState rowState) {
1514 if((RowState & rowState) != 0)
1517 //we need to find if child rows of this row changed.
1518 //if yes - we should return true
1520 // if the rowState is deleted we should get the original version of the row
1521 // else - we should get the current version of the row.
1522 DataRowVersion version = (rowState == DataRowState.Deleted) ? DataRowVersion.Original : DataRowVersion.Current;
1523 int count = Table.ChildRelations.Count;
1524 for (int i = 0; i < count; i++){
1525 DataRelation rel = Table.ChildRelations[i];
1526 DataRow[] childRows = GetChildRows(rel, version);
1527 for (int j = 0; j < childRows.Length; j++){
1528 if (childRows[j].IsRowChanged(rowState))
1536 internal bool HasParentCollection
1540 return _hasParentCollection;
1544 _hasParentCollection = value;
1548 internal void CheckNullConstraints()
1550 if (_nullConstraintViolation) {
1551 if (HasVersion(DataRowVersion.Proposed)) {
1552 foreach(DataColumn column in Table.Columns) {
1553 if (IsNull(column) && !column.AllowDBNull) {
1554 throw new NoNullAllowedException(_nullConstraintMessage);
1558 _nullConstraintViolation = false;
1562 internal void CheckReadOnlyStatus()
1564 if (HasVersion(DataRowVersion.Proposed)) {
1565 int defaultIdx = IndexFromVersion(DataRowVersion.Default);
1566 foreach(DataColumn column in Table.Columns) {
1567 if ((column.DataContainer.CompareValues(defaultIdx,_proposed) != 0) && column.ReadOnly) {
1568 throw new ReadOnlyException();
1574 #endregion // Methods