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 internal int _original
= -1;
56 internal int _current
= -1;
57 internal int _proposed
= -1;
59 private ArrayList _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
.DataContainer
.CopyValue(_table
.DefaultValuesRowIndex
,_proposed
);
94 rowError
= String
.Empty
;
96 //on first creating a DataRow it is always detached.
97 rowState
= DataRowState
.Detached
;
99 ArrayList aiColumns
= _table
.Columns
.AutoIncrmentColumns
;
100 foreach (DataColumn dc
in aiColumns
) {
101 this [dc
] = dc
.AutoIncrementValue();
104 // create mapped XmlDataElement
105 DataSet ds
= _table
.DataSet
;
106 if (ds
!= null && ds
._xmlDataDocument
!= null)
107 mappedElement
= new XmlDataDocument
.XmlDataElement (this, _table
.Prefix
, _table
.TableName
, _table
.Namespace
, ds
._xmlDataDocument
);
110 internal DataRow(DataTable table
,int rowId
)
116 #endregion // Constructors
120 private ArrayList ColumnErrors
123 if (_columnErrors
== null) {
124 _columnErrors
= new ArrayList();
126 return _columnErrors
;
130 _columnErrors
= value;
135 /// Gets a value indicating whether there are errors in a row.
137 public bool HasErrors
{
139 if (RowError
!= string.Empty
)
142 foreach(String columnError
in ColumnErrors
) {
143 if (columnError
!= null && columnError
!= string.Empty
) {
152 /// Gets or sets the data stored in the column specified by name.
154 public object this[string columnName
] {
155 get { return this[columnName, DataRowVersion.Default]; }
157 int columnIndex
= _table
.Columns
.IndexOf (columnName
);
158 if (columnIndex
== -1)
159 throw new IndexOutOfRangeException ();
160 this[columnIndex
] = value;
165 /// Gets or sets the data stored in specified DataColumn
167 public object this[DataColumn column
] {
170 return this[column
, DataRowVersion
.Default
];}
172 int columnIndex
= _table
.Columns
.IndexOf (column
);
173 if (columnIndex
== -1)
174 throw new ArgumentException ("The column does not belong to this table.");
175 this[columnIndex
] = value;
180 /// Gets or sets the data stored in column specified by index.
182 public object this[int columnIndex
] {
183 get { return this[columnIndex, DataRowVersion.Default]; }
185 if (columnIndex
< 0 || columnIndex
> _table
.Columns
.Count
)
186 throw new IndexOutOfRangeException ();
187 if (rowState
== DataRowState
.Deleted
)
188 throw new DeletedRowInaccessibleException ();
190 DataColumn column
= _table
.Columns
[columnIndex
];
191 _table
.ChangingDataColumn (this, column
, value);
193 if (value == null && column
.DataType
!= typeof(string)) {
194 throw new ArgumentException("Cannot set column " + column
.ColumnName
+ " to be null, Please use DBNull instead");
197 CheckValue (value, column
);
199 bool orginalEditing
= editing
;
200 if (!orginalEditing
) {
204 column
[_proposed
] = value;
205 _table
.ChangedDataColumn (this, column
, value);
206 if (!orginalEditing
) {
213 /// Gets the specified version of data stored in the named column.
215 public object this[string columnName
, DataRowVersion version
] {
217 int columnIndex
= _table
.Columns
.IndexOf (columnName
);
218 if (columnIndex
== -1)
219 throw new IndexOutOfRangeException ();
220 return this[columnIndex
, version
];
225 /// Gets the specified version of data stored in the specified DataColumn.
227 public object this[DataColumn column
, DataRowVersion version
] {
229 if (column
.Table
!= Table
)
230 throw new ArgumentException ("The column does not belong to this table.");
231 int columnIndex
= column
.Ordinal
;
232 return this[columnIndex
, version
];
237 /// Gets the data stored in the column, specified by index and version of the data to
240 public object this[int columnIndex
, DataRowVersion version
] {
242 if (columnIndex
< 0 || columnIndex
> _table
.Columns
.Count
)
243 throw new IndexOutOfRangeException ();
244 // Accessing deleted rows
245 if (!_inExpressionEvaluation
&& rowState
== DataRowState
.Deleted
&& version
!= DataRowVersion
.Original
)
246 throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
248 DataColumn column
= _table
.Columns
[columnIndex
];
249 if (column
.Expression
!= String
.Empty
) {
250 object o
= column
.CompiledExpression
.Eval (this);
251 return Convert
.ChangeType (o
, column
.DataType
);
254 int recordIndex
= IndexFromVersion(version
);
256 if (recordIndex
>= 0) {
257 return column
[recordIndex
];
260 if (rowState
== DataRowState
.Detached
&& version
== DataRowVersion
.Default
&& _proposed
< 0)
261 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.");
263 throw new VersionNotFoundException (Locale
.GetText ("There is no " + version
.ToString () + " data to access."));
268 /// Gets or sets all of the values for this row through an array.
270 public object[] ItemArray
{
273 if (rowState
== DataRowState
.Detached
)
274 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.");
275 // Accessing deleted rows
276 if (rowState
== DataRowState
.Deleted
)
277 throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
279 object[] items
= new object[_table
.Columns
.Count
];
280 foreach(DataColumn column
in _table
.Columns
) {
281 items
[column
.Ordinal
] = column
[_current
];
286 if (value.Length
> _table
.Columns
.Count
)
287 throw new ArgumentException ();
289 if (rowState
== DataRowState
.Deleted
)
290 throw new DeletedRowInaccessibleException ();
292 bool orginalEditing
= editing
;
293 if (!orginalEditing
) {
296 object newVal
= null;
297 DataColumnChangeEventArgs e
= new DataColumnChangeEventArgs();
298 foreach(DataColumn column
in _table
.Columns
) {
299 int i
= column
.Ordinal
;
300 newVal
= (i
< value.Length
) ? value[i
] : null;
302 e
.Initialize(this, column
, newVal
);
303 _table
.RaiseOnColumnChanged(e
);
304 CheckValue (e
.ProposedValue
, column
);
305 column
[_proposed
] = e
.ProposedValue
;
307 if (!orginalEditing
) {
314 /// Gets the current state of the row in regards to its relationship to the
315 /// DataRowCollection.
317 public DataRowState RowState
{
324 /// Gets the DataTable for which this row has a schema.
326 public DataTable Table
{
333 /// Gets and sets index of row. This is used from
336 internal int XmlRowID
{
346 /// Gets and sets index of row.
361 //FIXME?: Couldn't find a way to set the RowState when adding the DataRow
362 //to a Datatable so I added this method. Delete if there is a better way.
363 internal void AttachRow() {
365 Table
.RecordCache
.DisposeRecord(_current
);
367 _current
= _proposed
;
369 rowState
= DataRowState
.Added
;
372 //FIXME?: Couldn't find a way to set the RowState when removing the DataRow
373 //from a Datatable so I added this method. Delete if there is a better way.
374 internal void DetachRow() {
375 if (_proposed
>= 0) {
376 _table
.RecordCache
.DisposeRecord(_proposed
);
380 _hasParentCollection
= false;
381 rowState
= DataRowState
.Detached
;
384 private void CheckValue (object v
, DataColumn col
)
386 if (_hasParentCollection
&& col
.ReadOnly
) {
387 throw new ReadOnlyException ();
390 if (v
== null || v
== DBNull
.Value
) {
391 if (col
.AllowDBNull
|| col
.AutoIncrement
|| col
.DefaultValue
!= DBNull
.Value
) {
395 //Constraint violations during data load is raise in DataTable EndLoad
396 this._nullConstraintViolation
= true;
397 if (this.Table
._duringDataLoad
) {
398 this.Table
._nullConstraintViolationDuringDataLoad
= true;
400 _nullConstraintMessage
= "Column '" + col
.ColumnName
+ "' does not allow nulls.";
405 internal void SetValuesFromDataRecord(IDataRecord record
, int[] mapping
)
407 if ( mapping
.Length
> Table
.Columns
.Count
)
408 throw new ArgumentException ();
410 // bool orginalEditing = editing;
411 // if (!orginalEditing) {
415 if (!HasVersion(DataRowVersion
.Proposed
)) {
416 _proposed
= Table
.RecordCache
.NewRecord();
420 for(int i
=0; i
< mapping
.Length
; i
++) {
421 DataColumn column
= Table
.Columns
[i
];
422 column
.DataContainer
.SetItemFromDataRecord(_proposed
, record
,mapping
[i
]);
423 if ( column
.AutoIncrement
) {
424 column
.UpdateAutoIncrementValue(column
.DataContainer
.GetInt64(_proposed
));
429 Table
.RecordCache
.DisposeRecord(_proposed
);
434 // if (!orginalEditing) {
440 /// Gets or sets the custom error description for a row.
442 public string RowError
{
451 internal int IndexFromVersion(DataRowVersion version
)
453 if (HasVersion(version
))
457 case DataRowVersion
.Default
:
458 if (editing
|| rowState
== DataRowState
.Detached
) {
459 recordIndex
= _proposed
;
462 recordIndex
= _current
;
465 case DataRowVersion
.Proposed
:
466 recordIndex
= _proposed
;
468 case DataRowVersion
.Current
:
469 recordIndex
= _current
;
471 case DataRowVersion
.Original
:
472 recordIndex
= _original
;
475 throw new ArgumentException ();
482 internal XmlDataDocument
.XmlDataElement DataElement
{
483 get { return mappedElement; }
484 set { mappedElement = value; }
487 internal void SetOriginalValue (string columnName
, object val
)
489 DataColumn column
= _table
.Columns
[columnName
];
490 _table
.ChangingDataColumn (this, column
, val
);
492 if (_original
< 0 || _original
== _current
) {
493 // This really creates a new record version if one does not exist
494 _original
= Table
.RecordCache
.NewRecord();
496 CheckValue (val
, column
);
497 column
[_original
] = val
;
498 rowState
= DataRowState
.Modified
;
502 /// Commits all the changes made to this row since the last time AcceptChanges was
505 public void AcceptChanges ()
507 EndEdit(); // in case it hasn't been called
509 case DataRowState
.Unchanged
:
511 case DataRowState
.Added
:
512 case DataRowState
.Modified
:
513 rowState
= DataRowState
.Unchanged
;
515 case DataRowState
.Deleted
:
516 _table
.Rows
.RemoveInternal (this);
519 case DataRowState
.Detached
:
520 throw new RowNotInTableException("Cannot perform this operation on a row not in the table.");
522 // Accept from detached
523 if (_original
>= 0) {
524 Table
.RecordCache
.DisposeRecord(_original
);
526 _original
= _current
;
530 /// Begins an edit operation on a DataRow object.
532 public void BeginEdit ()
534 if (_inChangingEvent
)
535 throw new InRowChangingEventException("Cannot call BeginEdit inside an OnRowChanging event.");
536 if (rowState
== DataRowState
.Deleted
)
537 throw new DeletedRowInaccessibleException ();
538 if (!HasVersion (DataRowVersion
.Proposed
)) {
539 _proposed
= Table
.RecordCache
.NewRecord();
540 foreach(DataColumn column
in Table
.Columns
) {
541 column
.DataContainer
.CopyValue(_current
,_proposed
);
544 // setting editing to true stops validations on the row
549 /// Cancels the current edit on the row.
551 public void CancelEdit ()
553 if (_inChangingEvent
)
554 throw new InRowChangingEventException("Cannot call CancelEdit inside an OnRowChanging event.");
556 if (HasVersion (DataRowVersion
.Proposed
)) {
557 Table
.RecordCache
.DisposeRecord(_proposed
);
559 if (rowState
== DataRowState
.Modified
) {
560 rowState
= DataRowState
.Unchanged
;
566 /// Clears the errors for the row, including the RowError and errors set with
569 public void ClearErrors ()
571 rowError
= String
.Empty
;
572 ColumnErrors
.Clear();
576 /// Deletes the DataRow.
578 public void Delete ()
580 _table
.DeletingDataRow(this, DataRowAction
.Delete
);
582 case DataRowState
.Added
:
583 // check what to do with child rows
584 CheckChildRows(DataRowAction
.Delete
);
585 _table
.DeleteRowFromIndexes (this);
586 Table
.Rows
.RemoveInternal (this);
588 // if row was in Added state we move it to Detached.
591 case DataRowState
.Deleted
:
594 // check what to do with child rows
595 CheckChildRows(DataRowAction
.Delete
);
596 _table
.DeleteRowFromIndexes (this);
597 rowState
= DataRowState
.Deleted
;
600 _table
.DeletedDataRow(this, DataRowAction
.Delete
);
603 // check the child rows of this row before deleting the row.
604 private void CheckChildRows(DataRowAction action
)
607 // in this method we find the row that this row is in a relation with them.
608 // in shortly we find all child rows of this row.
609 // then we function according to the DeleteRule of the foriegnkey.
612 // 1. find if this row is attached to dataset.
613 // 2. find if EnforceConstraints is true.
614 // 3. find if there are any constraint on the table that the row is in.
615 if (_table
.DataSet
!= null && _table
.DataSet
.EnforceConstraints
&& _table
.Constraints
.Count
> 0)
617 foreach (DataTable table
in _table
.DataSet
.Tables
)
619 // loop on all ForeignKeyConstrain of the table.
620 foreach (ForeignKeyConstraint fk
in table
.Constraints
.ForeignKeyConstraints
)
622 if (fk
.RelatedTable
== _table
)
625 if (action
== DataRowAction
.Delete
)
626 rule
= fk
.DeleteRule
;
628 rule
= fk
.UpdateRule
;
629 CheckChildRows(fk
, action
, rule
);
636 private void CheckChildRows(ForeignKeyConstraint fkc
, DataRowAction action
, Rule rule
)
638 DataRow
[] childRows
= GetChildRows(fkc
, DataRowVersion
.Default
);
641 case Rule
.Cascade
: // delete or change all relted rows.
642 if (childRows
!= null)
644 for (int j
= 0; j
< childRows
.Length
; j
++)
646 // if action is delete we delete all child rows
647 if (action
== DataRowAction
.Delete
)
649 if (childRows
[j
].RowState
!= DataRowState
.Deleted
)
650 childRows
[j
].Delete();
652 // if action is change we change the values in the child row
653 else if (action
== DataRowAction
.Change
)
655 // change only the values in the key columns
656 // set the childcolumn value to the new parent row value
657 for (int k
= 0; k
< fkc
.Columns
.Length
; k
++)
658 childRows
[j
][fkc
.Columns
[k
]] = this[fkc
.RelatedColumns
[k
], DataRowVersion
.Proposed
];
663 case Rule
.None
: // throw an exception if there are any child rows.
664 if (childRows
!= null)
666 for (int j
= 0; j
< childRows
.Length
; j
++)
668 if (childRows
[j
].RowState
!= DataRowState
.Deleted
)
670 string changeStr
= "Cannot change this row because constraints are enforced on relation " + fkc
.ConstraintName
+", and changing this row will strand child rows.";
671 string delStr
= "Cannot delete this row because constraints are enforced on relation " + fkc
.ConstraintName
+", and deleting this row will strand child rows.";
672 string message
= action
== DataRowAction
.Delete
? delStr
: changeStr
;
673 throw new InvalidConstraintException(message
);
678 case Rule
.SetDefault
: // set the values in the child rows to the defult value of the columns.
679 if (childRows
!= null && childRows
.Length
> 0) {
680 int defaultValuesRowIndex
= childRows
[0].Table
.DefaultValuesRowIndex
;
681 foreach(DataRow childRow
in childRows
) {
682 if (childRow
.RowState
!= DataRowState
.Deleted
) {
683 int defaultIdx
= childRow
.IndexFromVersion(DataRowVersion
.Default
);
684 foreach(DataColumn column
in fkc
.Columns
) {
685 column
.DataContainer
.CopyValue(defaultValuesRowIndex
,defaultIdx
);
691 case Rule
.SetNull
: // set the values in the child row to null.
692 if (childRows
!= null)
694 for (int j
= 0; j
< childRows
.Length
; j
++)
696 DataRow child
= childRows
[j
];
697 if (childRows
[j
].RowState
!= DataRowState
.Deleted
)
699 // set only the key columns to DBNull
700 for (int k
= 0; k
< fkc
.Columns
.Length
; k
++)
701 child
.SetNull(fkc
.Columns
[k
]);
711 /// Ends the edit occurring on the row.
713 public void EndEdit ()
715 if (_inChangingEvent
)
716 throw new InRowChangingEventException("Cannot call EndEdit inside an OnRowChanging event.");
717 if (rowState
== DataRowState
.Detached
)
723 CheckReadOnlyStatus();
724 if (HasVersion (DataRowVersion
.Proposed
))
726 _inChangingEvent
= true;
729 _table
.ChangingDataRow(this, DataRowAction
.Change
);
733 _inChangingEvent
= false;
735 if (rowState
== DataRowState
.Unchanged
)
736 rowState
= DataRowState
.Modified
;
738 //Calling next method validates UniqueConstraints
742 if ((_table
.DataSet
== null || _table
.DataSet
.EnforceConstraints
) && !_table
._duringDataLoad
)
743 _table
.Rows
.ValidateDataRowInternal(this);
748 Table
.RecordCache
.DisposeRecord(_proposed
);
753 // Now we are going to check all child rows of current row.
754 // In the case the cascade is true the child rows will look up for
755 // parent row. since lookup in index is always on current,
756 // we have to move proposed version of current row to current
757 // in the case of check child row failure we are rolling
758 // current row state back.
759 int backup
= _current
;
760 _current
= _proposed
;
761 bool editing_backup
= editing
;
764 // check all child rows.
765 CheckChildRows(DataRowAction
.Change
);
767 if (_original
!= backup
) {
768 Table
.RecordCache
.DisposeRecord(backup
);
771 catch (Exception ex
) {
772 // if check child rows failed - rollback to previous state
773 // i.e. restore proposed and current versions
774 _proposed
= _current
;
776 editing
= editing_backup
;
777 // since we failed - propagate an exception
780 _table
.ChangedDataRow(this, DataRowAction
.Change
);
785 /// Gets the child rows of this DataRow using the specified DataRelation.
787 public DataRow
[] GetChildRows (DataRelation relation
)
789 return GetChildRows (relation
, DataRowVersion
.Current
);
793 /// Gets the child rows of a DataRow using the specified RelationName of a
796 public DataRow
[] GetChildRows (string relationName
)
798 return GetChildRows (Table
.DataSet
.Relations
[relationName
]);
802 /// Gets the child rows of a DataRow using the specified DataRelation, and
805 public DataRow
[] GetChildRows (DataRelation relation
, DataRowVersion version
)
807 if (relation
== null)
808 return new DataRow
[0];
810 if (this.Table
== null || RowState
== DataRowState
.Detached
)
811 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.");
813 if (relation
.DataSet
!= this.Table
.DataSet
)
814 throw new ArgumentException();
816 if (relation
.ChildKeyConstraint
!= null)
817 return GetChildRows (relation
.ChildKeyConstraint
, version
);
819 ArrayList rows
= new ArrayList();
820 DataColumn
[] parentColumns
= relation
.ParentColumns
;
821 DataColumn
[] childColumns
= relation
.ChildColumns
;
822 int numColumn
= parentColumns
.Length
;
823 if (HasVersion(version
))
825 object[] vals
= new object[parentColumns
.Length
];
826 for (int i
= 0; i
< vals
.Length
; i
++)
827 vals
[i
] = this[parentColumns
[i
], version
];
829 foreach (DataRow row
in relation
.ChildTable
.Rows
)
831 bool allColumnsMatch
= false;
832 if (row
.HasVersion(DataRowVersion
.Default
))
834 allColumnsMatch
= true;
835 for (int columnCnt
= 0; columnCnt
< numColumn
; ++columnCnt
)
837 if (!vals
[columnCnt
].Equals(
838 row
[childColumns
[columnCnt
], DataRowVersion
.Default
]))
840 allColumnsMatch
= false;
845 if (allColumnsMatch
) rows
.Add(row
);
848 DataRow
[] result
= relation
.ChildTable
.NewRowArray(rows
.Count
);
849 rows
.CopyTo(result
, 0);
854 /// Gets the child rows of a DataRow using the specified RelationName of a
855 /// DataRelation, and DataRowVersion.
857 public DataRow
[] GetChildRows (string relationName
, DataRowVersion version
)
859 return GetChildRows (Table
.DataSet
.Relations
[relationName
], version
);
862 private DataRow
[] GetChildRows (ForeignKeyConstraint fkc
, DataRowVersion version
)
864 ArrayList rows
= new ArrayList();
865 DataColumn
[] parentColumns
= fkc
.RelatedColumns
;
866 DataColumn
[] childColumns
= fkc
.Columns
;
867 int numColumn
= parentColumns
.Length
;
868 if (HasVersion(version
)) {
869 Index index
= fkc
.Index
;
871 // get the child rows from the index
872 Node
[] childNodes
= index
.FindAllSimple (parentColumns
, IndexFromVersion(version
));
873 for (int i
= 0; i
< childNodes
.Length
; i
++) {
874 rows
.Add (childNodes
[i
].Row
);
877 else { // if there is no index we search manualy.
878 int curIndex
= IndexFromVersion(DataRowVersion
.Current
);
879 int tmpRecord
= fkc
.Table
.RecordCache
.NewRecord();
882 for (int i
= 0; i
< numColumn
; i
++) {
883 // according to MSDN: the DataType value for both columns must be identical.
884 childColumns
[i
].DataContainer
.CopyValue(parentColumns
[i
].DataContainer
, curIndex
, tmpRecord
);
887 foreach (DataRow row
in fkc
.Table
.Rows
) {
888 bool allColumnsMatch
= false;
889 if (row
.HasVersion(DataRowVersion
.Default
)) {
890 allColumnsMatch
= true;
891 int childIndex
= row
.IndexFromVersion(DataRowVersion
.Default
);
892 for (int columnCnt
= 0; columnCnt
< numColumn
; ++columnCnt
) {
893 if (childColumns
[columnCnt
].DataContainer
.CompareValues(childIndex
, tmpRecord
) != 0) {
894 allColumnsMatch
= false;
899 if (allColumnsMatch
) {
905 fkc
.Table
.RecordCache
.DisposeRecord(tmpRecord
);
910 DataRow
[] result
= fkc
.Table
.NewRowArray(rows
.Count
);
911 rows
.CopyTo(result
, 0);
916 /// Gets the error description of the specified DataColumn.
918 public string GetColumnError (DataColumn column
)
920 return GetColumnError (_table
.Columns
.IndexOf(column
));
924 /// Gets the error description for the column specified by index.
926 public string GetColumnError (int columnIndex
)
928 if (columnIndex
< 0 || columnIndex
>= Table
.Columns
.Count
)
929 throw new IndexOutOfRangeException ();
931 string retVal
= null;
932 if (columnIndex
< ColumnErrors
.Count
) {
933 retVal
= (String
) ColumnErrors
[columnIndex
];
935 return (retVal
!= null) ? retVal
: String
.Empty
;
939 /// Gets the error description for the column, specified by name.
941 public string GetColumnError (string columnName
)
943 return GetColumnError (_table
.Columns
.IndexOf(columnName
));
947 /// Gets an array of columns that have errors.
949 public DataColumn
[] GetColumnsInError ()
951 ArrayList dataColumns
= new ArrayList ();
953 int columnOrdinal
= 0;
954 foreach(String columnError
in ColumnErrors
) {
955 if (columnError
!= null && columnError
!= String
.Empty
) {
956 dataColumns
.Add (_table
.Columns
[columnOrdinal
]);
961 return (DataColumn
[])(dataColumns
.ToArray (typeof(DataColumn
)));
965 /// Gets the parent row of a DataRow using the specified DataRelation.
967 public DataRow
GetParentRow (DataRelation relation
)
969 return GetParentRow (relation
, DataRowVersion
.Current
);
973 /// Gets the parent row of a DataRow using the specified RelationName of a
976 public DataRow
GetParentRow (string relationName
)
978 return GetParentRow (relationName
, DataRowVersion
.Current
);
982 /// Gets the parent row of a DataRow using the specified DataRelation, and
985 public DataRow
GetParentRow (DataRelation relation
, DataRowVersion version
)
987 DataRow
[] rows
= GetParentRows(relation
, version
);
988 if (rows
.Length
== 0) return null;
993 /// Gets the parent row of a DataRow using the specified RelationName of a
994 /// DataRelation, and DataRowVersion.
996 public DataRow
GetParentRow (string relationName
, DataRowVersion version
)
998 return GetParentRow (Table
.DataSet
.Relations
[relationName
], version
);
1002 /// Gets the parent rows of a DataRow using the specified DataRelation.
1004 public DataRow
[] GetParentRows (DataRelation relation
)
1006 return GetParentRows (relation
, DataRowVersion
.Current
);
1010 /// Gets the parent rows of a DataRow using the specified RelationName of a
1013 public DataRow
[] GetParentRows (string relationName
)
1015 return GetParentRows (relationName
, DataRowVersion
.Current
);
1019 /// Gets the parent rows of a DataRow using the specified DataRelation, and
1022 public DataRow
[] GetParentRows (DataRelation relation
, DataRowVersion version
)
1024 // TODO: Caching for better preformance
1025 if (relation
== null)
1026 return new DataRow
[0];
1028 if (this.Table
== null || RowState
== DataRowState
.Detached
)
1029 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.");
1031 if (relation
.DataSet
!= this.Table
.DataSet
)
1032 throw new ArgumentException();
1034 ArrayList rows
= new ArrayList();
1035 DataColumn
[] parentColumns
= relation
.ParentColumns
;
1036 DataColumn
[] childColumns
= relation
.ChildColumns
;
1037 int numColumn
= parentColumns
.Length
;
1038 if (HasVersion(version
)) {
1039 Index indx
= relation
.ParentTable
.GetIndexByColumns (parentColumns
);
1041 (Table
== null || Table
.DataSet
== null ||
1042 Table
.DataSet
.EnforceConstraints
)) { // get the child rows from the index
1043 Node
[] childNodes
= indx
.FindAllSimple(childColumns
, IndexFromVersion(version
));
1044 for (int i
= 0; i
< childNodes
.Length
; i
++) {
1045 rows
.Add (childNodes
[i
].Row
);
1048 else { // no index so we have to search manualy.
1049 int curIndex
= IndexFromVersion(DataRowVersion
.Current
);
1050 int tmpRecord
= relation
.ParentTable
.RecordCache
.NewRecord();
1052 for (int i
= 0; i
< numColumn
; i
++) {
1053 // according to MSDN: the DataType value for both columns must be identical.
1054 parentColumns
[i
].DataContainer
.CopyValue(childColumns
[i
].DataContainer
, curIndex
, tmpRecord
);
1057 foreach (DataRow row
in relation
.ParentTable
.Rows
) {
1058 bool allColumnsMatch
= false;
1059 if (row
.HasVersion(DataRowVersion
.Default
)) {
1060 allColumnsMatch
= true;
1061 int parentIndex
= row
.IndexFromVersion(DataRowVersion
.Default
);
1062 for (int columnCnt
= 0; columnCnt
< numColumn
; columnCnt
++) {
1063 if (parentColumns
[columnCnt
].DataContainer
.CompareValues(parentIndex
, tmpRecord
) != 0) {
1064 allColumnsMatch
= false;
1069 if (allColumnsMatch
) {
1075 relation
.ParentTable
.RecordCache
.DisposeRecord(tmpRecord
);
1080 DataRow
[] result
= relation
.ParentTable
.NewRowArray(rows
.Count
);
1081 rows
.CopyTo(result
, 0);
1086 /// Gets the parent rows of a DataRow using the specified RelationName of a
1087 /// DataRelation, and DataRowVersion.
1089 public DataRow
[] GetParentRows (string relationName
, DataRowVersion version
)
1091 return GetParentRows (Table
.DataSet
.Relations
[relationName
], version
);
1095 /// Gets a value indicating whether a specified version exists.
1097 public bool HasVersion (DataRowVersion version
)
1100 case DataRowVersion
.Default
:
1101 if (rowState
== DataRowState
.Deleted
&& !_inExpressionEvaluation
)
1103 if (rowState
== DataRowState
.Detached
)
1104 return _proposed
>= 0;
1106 case DataRowVersion
.Proposed
:
1107 if (rowState
== DataRowState
.Deleted
&& !_inExpressionEvaluation
)
1109 return _proposed
>= 0;
1110 case DataRowVersion
.Current
:
1111 if ((rowState
== DataRowState
.Deleted
&& !_inExpressionEvaluation
) || rowState
== DataRowState
.Detached
)
1113 return _current
>= 0;
1114 case DataRowVersion
.Original
:
1115 if (rowState
== DataRowState
.Detached
)
1117 return _original
>= 0;
1123 /// Gets a value indicating whether the specified DataColumn contains a null value.
1125 public bool IsNull (DataColumn column
)
1127 return IsNull(column
, DataRowVersion
.Default
);
1131 /// Gets a value indicating whether the column at the specified index contains a null
1134 public bool IsNull (int columnIndex
)
1136 return IsNull(Table
.Columns
[columnIndex
]);
1140 /// Gets a value indicating whether the named column contains a null value.
1142 public bool IsNull (string columnName
)
1144 return IsNull(Table
.Columns
[columnName
]);
1148 /// Gets a value indicating whether the specified DataColumn and DataRowVersion
1149 /// contains a null value.
1151 public bool IsNull (DataColumn column
, DataRowVersion version
)
1153 return column
.DataContainer
.IsNull(IndexFromVersion(version
));
1157 /// Returns a value indicating whether all of the row columns specified contain a null value.
1159 internal bool IsNullColumns(DataColumn
[] columns
)
1161 bool allNull
= true;
1162 for (int i
= 0; i
< columns
.Length
; i
++)
1164 if (!IsNull(columns
[i
]))
1174 /// Rejects all changes made to the row since AcceptChanges was last called.
1176 public void RejectChanges ()
1178 if (RowState
== DataRowState
.Detached
)
1179 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.");
1180 // If original is null, then nothing has happened since AcceptChanges
1181 // was last called. We have no "original" to go back to.
1182 if (HasVersion(DataRowVersion
.Original
)) {
1183 if (_current
>= 0 ) {
1184 Table
.RecordCache
.DisposeRecord(_current
);
1186 _current
= _original
;
1188 _table
.ChangedDataRow (this, DataRowAction
.Rollback
);
1192 case DataRowState
.Added
:
1193 _table
.DeleteRowFromIndexes (this);
1194 _table
.Rows
.RemoveInternal (this);
1196 case DataRowState
.Modified
:
1197 if ((_table
.DataSet
== null || _table
.DataSet
.EnforceConstraints
) && !_table
._duringDataLoad
)
1198 _table
.Rows
.ValidateDataRowInternal(this);
1199 rowState
= DataRowState
.Unchanged
;
1201 case DataRowState
.Deleted
:
1202 rowState
= DataRowState
.Unchanged
;
1203 if ((_table
.DataSet
== null || _table
.DataSet
.EnforceConstraints
) && !_table
._duringDataLoad
)
1204 _table
.Rows
.ValidateDataRowInternal(this);
1210 // If rows are just loaded via Xml the original values are null.
1211 // So in this case we have to remove all columns.
1212 // FIXME: I'm not realy sure, does this break something else, but
1215 if ((rowState
& DataRowState
.Added
) > 0)
1217 _table
.DeleteRowFromIndexes (this);
1218 _table
.Rows
.RemoveInternal (this);
1219 // if row was in Added state we move it to Detached.
1226 /// Sets the error description for a column specified as a DataColumn.
1228 public void SetColumnError (DataColumn column
, string error
)
1230 SetColumnError (_table
.Columns
.IndexOf (column
), error
);
1234 /// Sets the error description for a column specified by index.
1236 public void SetColumnError (int columnIndex
, string error
)
1238 if (columnIndex
< 0 || columnIndex
>= Table
.Columns
.Count
)
1239 throw new IndexOutOfRangeException ();
1241 while(ColumnErrors
.Count
< columnIndex
) {
1242 ColumnErrors
.Add(null);
1244 ColumnErrors
.Add(error
);
1248 /// Sets the error description for a column specified by name.
1250 public void SetColumnError (string columnName
, string error
)
1252 SetColumnError (_table
.Columns
.IndexOf (columnName
), error
);
1256 /// Sets the value of the specified DataColumn to a null value.
1258 protected void SetNull (DataColumn column
)
1260 this[column
] = DBNull
.Value
;
1264 /// Sets the parent row of a DataRow with specified new parent DataRow.
1266 public void SetParentRow (DataRow parentRow
)
1268 SetParentRow(parentRow
, null);
1272 /// Sets the parent row of a DataRow with specified new parent DataRow and
1275 public void SetParentRow (DataRow parentRow
, DataRelation relation
)
1277 if (_table
== null || parentRow
.Table
== null || RowState
== DataRowState
.Detached
)
1278 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.");
1280 if (parentRow
!= null && _table
.DataSet
!= parentRow
.Table
.DataSet
)
1281 throw new ArgumentException();
1284 if (relation
== null)
1286 foreach (DataRelation parentRel
in _table
.ParentRelations
)
1288 DataColumn
[] childCols
= parentRel
.ChildKeyConstraint
.Columns
;
1289 DataColumn
[] parentCols
= parentRel
.ChildKeyConstraint
.RelatedColumns
;
1291 for (int i
= 0; i
< parentCols
.Length
; i
++)
1293 if (parentRow
== null)
1294 this[childCols
[i
].Ordinal
] = DBNull
.Value
;
1296 this[childCols
[i
].Ordinal
] = parentRow
[parentCols
[i
]];
1303 DataColumn
[] childCols
= relation
.ChildKeyConstraint
.Columns
;
1304 DataColumn
[] parentCols
= relation
.ChildKeyConstraint
.RelatedColumns
;
1306 for (int i
= 0; i
< parentCols
.Length
; i
++)
1308 if (parentRow
== null)
1309 this[childCols
[i
].Ordinal
] = DBNull
.Value
;
1311 this[childCols
[i
].Ordinal
] = parentRow
[parentCols
[i
]];
1317 //Copy all values of this DataaRow to the row parameter.
1318 internal void CopyValuesToRow(DataRow row
)
1321 throw new ArgumentNullException("row");
1323 throw new ArgumentException("'row' is the same as this object");
1325 foreach(DataColumn column
in Table
.Columns
) {
1326 DataColumn targetColumn
= row
.Table
.Columns
[column
.ColumnName
];
1327 //if a column with the same name exists in both rows copy the values
1328 if(targetColumn
!= null) {
1329 int index
= targetColumn
.Ordinal
;
1330 if (HasVersion(DataRowVersion
.Original
)) {
1331 if (row
._original
< 0) {
1332 row
._original
= row
.Table
.RecordCache
.NewRecord();
1334 object val
= column
[_original
];
1335 row
.CheckValue(val
, targetColumn
);
1336 targetColumn
[row
._original
] = val
;
1338 if (HasVersion(DataRowVersion
.Current
)) {
1339 if (row
._current
< 0) {
1340 row
._current
= row
.Table
.RecordCache
.NewRecord();
1342 object val
= column
[_current
];
1343 row
.CheckValue(val
, targetColumn
);
1344 targetColumn
[row
._current
] = val
;
1346 if (HasVersion(DataRowVersion
.Proposed
)) {
1347 if (row
._proposed
< 0) {
1348 row
._proposed
= row
.Table
.RecordCache
.NewRecord();
1350 object val
= column
[row
._proposed
];
1351 row
.CheckValue(val
, targetColumn
);
1352 targetColumn
[row
._proposed
] = val
;
1355 //Saving the current value as the column value
1356 row
[index
] = targetColumn
[row
._current
];
1363 // Copy row state - rowState and errors
1364 internal void CopyState(DataRow row
)
1366 row
.rowState
= RowState
;
1367 row
.RowError
= RowError
;
1368 row
.ColumnErrors
= (ArrayList
)ColumnErrors
.Clone();
1371 internal bool IsRowChanged(DataRowState rowState
) {
1372 if((RowState
& rowState
) != 0)
1375 //we need to find if child rows of this row changed.
1376 //if yes - we should return true
1378 // if the rowState is deleted we should get the original version of the row
1379 // else - we should get the current version of the row.
1380 DataRowVersion version
= (rowState
== DataRowState
.Deleted
) ? DataRowVersion
.Original
: DataRowVersion
.Current
;
1381 int count
= Table
.ChildRelations
.Count
;
1382 for (int i
= 0; i
< count
; i
++){
1383 DataRelation rel
= Table
.ChildRelations
[i
];
1384 DataRow
[] childRows
= GetChildRows(rel
, version
);
1385 for (int j
= 0; j
< childRows
.Length
; j
++){
1386 if (childRows
[j
].IsRowChanged(rowState
))
1394 internal bool HasParentCollection
1398 return _hasParentCollection
;
1402 _hasParentCollection
= value;
1406 internal void CheckNullConstraints()
1408 if (_nullConstraintViolation
) {
1409 if (HasVersion(DataRowVersion
.Proposed
)) {
1410 foreach(DataColumn column
in Table
.Columns
) {
1411 if (IsNull(column
) && !column
.AllowDBNull
) {
1412 throw new NoNullAllowedException(_nullConstraintMessage
);
1416 _nullConstraintViolation
= false;
1420 internal void CheckReadOnlyStatus()
1422 if (HasVersion(DataRowVersion
.Proposed
)) {
1423 int defaultIdx
= IndexFromVersion(DataRowVersion
.Default
);
1424 foreach(DataColumn column
in Table
.Columns
) {
1425 if ((column
.DataContainer
.CompareValues(defaultIdx
,_proposed
) != 0) && column
.ReadOnly
) {
1426 throw new ReadOnlyException();
1432 #endregion // Methods