(DISTFILES): Comment out a few missing files.
[mono-project.git] / mcs / class / System.Data / System.Data / DataRow.cs
blobdca7e83a39bb88761e0a4bb29a0dd6b5de792636
1 //
2 // System.Data.DataRow.cs
3 //
4 // Author:
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:
26 //
27 // The above copyright notice and this permission notice shall be
28 // included in all copies or substantial portions of the Software.
29 //
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.
39 using System;
40 using System.Collections;
41 using System.Globalization;
42 using System.Xml;
44 namespace System.Data {
45 /// <summary>
46 /// Represents a row of data in a DataTable.
47 /// </summary>
48 [Serializable]
49 public class DataRow
51 #region Fields
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;
68 private int _rowId;
70 private XmlDataDocument.XmlDataElement mappedElement;
71 internal bool _inExpressionEvaluation = false;
73 #endregion // Fields
75 #region Constructors
77 /// <summary>
78 /// This member supports the .NET Framework infrastructure and is not intended to be
79 /// used directly from your code.
80 /// </summary>
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)
112 _table = table;
113 _rowId = rowId;
116 #endregion // Constructors
118 #region Properties
120 private ArrayList ColumnErrors
122 get {
123 if (_columnErrors == null) {
124 _columnErrors = new ArrayList();
126 return _columnErrors;
129 set {
130 _columnErrors = value;
134 /// <summary>
135 /// Gets a value indicating whether there are errors in a row.
136 /// </summary>
137 public bool HasErrors {
138 get {
139 if (RowError != string.Empty)
140 return true;
142 foreach(String columnError in ColumnErrors) {
143 if (columnError != null && columnError != string.Empty) {
144 return true;
147 return false;
151 /// <summary>
152 /// Gets or sets the data stored in the column specified by name.
153 /// </summary>
154 public object this[string columnName] {
155 get { return this[columnName, DataRowVersion.Default]; }
156 set {
157 int columnIndex = _table.Columns.IndexOf (columnName);
158 if (columnIndex == -1)
159 throw new IndexOutOfRangeException ();
160 this[columnIndex] = value;
164 /// <summary>
165 /// Gets or sets the data stored in specified DataColumn
166 /// </summary>
167 public object this[DataColumn column] {
169 get {
170 return this[column, DataRowVersion.Default];}
171 set {
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;
179 /// <summary>
180 /// Gets or sets the data stored in column specified by index.
181 /// </summary>
182 public object this[int columnIndex] {
183 get { return this[columnIndex, DataRowVersion.Default]; }
184 set {
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) {
201 BeginEdit ();
204 column[_proposed] = value;
205 _table.ChangedDataColumn (this, column, value);
206 if (!orginalEditing) {
207 EndEdit ();
212 /// <summary>
213 /// Gets the specified version of data stored in the named column.
214 /// </summary>
215 public object this[string columnName, DataRowVersion version] {
216 get {
217 int columnIndex = _table.Columns.IndexOf (columnName);
218 if (columnIndex == -1)
219 throw new IndexOutOfRangeException ();
220 return this[columnIndex, version];
224 /// <summary>
225 /// Gets the specified version of data stored in the specified DataColumn.
226 /// </summary>
227 public object this[DataColumn column, DataRowVersion version] {
228 get {
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];
236 /// <summary>
237 /// Gets the data stored in the column, specified by index and version of the data to
238 /// retrieve.
239 /// </summary>
240 public object this[int columnIndex, DataRowVersion version] {
241 get {
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."));
267 /// <summary>
268 /// Gets or sets all of the values for this row through an array.
269 /// </summary>
270 public object[] ItemArray {
271 get {
272 // row not in table
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];
283 return items;
285 set {
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) {
294 BeginEdit ();
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) {
308 EndEdit ();
313 /// <summary>
314 /// Gets the current state of the row in regards to its relationship to the
315 /// DataRowCollection.
316 /// </summary>
317 public DataRowState RowState {
318 get {
319 return rowState;
323 /// <summary>
324 /// Gets the DataTable for which this row has a schema.
325 /// </summary>
326 public DataTable Table {
327 get {
328 return _table;
332 /// <summary>
333 /// Gets and sets index of row. This is used from
334 /// XmlDataDocument.
335 // </summary>
336 internal int XmlRowID {
337 get {
338 return xmlRowID;
340 set {
341 xmlRowID = value;
345 /// <summary>
346 /// Gets and sets index of row.
347 // </summary>
348 internal int RowID {
349 get {
350 return _rowId;
352 set {
353 _rowId = value;
357 #endregion
359 #region Methods
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() {
364 if (_current >= 0) {
365 Table.RecordCache.DisposeRecord(_current);
367 _current = _proposed;
368 _proposed = -1;
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);
377 _proposed = -1;
379 _rowId = -1;
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) {
392 return;
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) {
412 // BeginEdit ();
413 // }
415 if (!HasVersion(DataRowVersion.Proposed)) {
416 _proposed = Table.RecordCache.NewRecord();
419 try {
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));
428 catch (Exception e){
429 Table.RecordCache.DisposeRecord(_proposed);
430 _proposed = -1;
431 throw e;
434 // if (!orginalEditing) {
435 // EndEdit ();
436 // }
439 /// <summary>
440 /// Gets or sets the custom error description for a row.
441 /// </summary>
442 public string RowError {
443 get {
444 return rowError;
446 set {
447 rowError = value;
451 internal int IndexFromVersion(DataRowVersion version)
453 if (HasVersion(version))
455 int recordIndex;
456 switch (version) {
457 case DataRowVersion.Default:
458 if (editing || rowState == DataRowState.Detached) {
459 recordIndex = _proposed;
461 else {
462 recordIndex = _current;
464 break;
465 case DataRowVersion.Proposed:
466 recordIndex = _proposed;
467 break;
468 case DataRowVersion.Current:
469 recordIndex = _current;
470 break;
471 case DataRowVersion.Original:
472 recordIndex = _original;
473 break;
474 default:
475 throw new ArgumentException ();
477 return recordIndex;
479 return -1;
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;
501 /// <summary>
502 /// Commits all the changes made to this row since the last time AcceptChanges was
503 /// called.
504 /// </summary>
505 public void AcceptChanges ()
507 EndEdit(); // in case it hasn't been called
508 switch (rowState) {
509 case DataRowState.Unchanged:
510 return;
511 case DataRowState.Added:
512 case DataRowState.Modified:
513 rowState = DataRowState.Unchanged;
514 break;
515 case DataRowState.Deleted:
516 _table.Rows.RemoveInternal (this);
517 DetachRow();
518 break;
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;
529 /// <summary>
530 /// Begins an edit operation on a DataRow object.
531 /// </summary>
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
545 editing = true;
548 /// <summary>
549 /// Cancels the current edit on the row.
550 /// </summary>
551 public void CancelEdit ()
553 if (_inChangingEvent)
554 throw new InRowChangingEventException("Cannot call CancelEdit inside an OnRowChanging event.");
555 editing = false;
556 if (HasVersion (DataRowVersion.Proposed)) {
557 Table.RecordCache.DisposeRecord(_proposed);
558 _proposed = -1;
559 if (rowState == DataRowState.Modified) {
560 rowState = DataRowState.Unchanged;
565 /// <summary>
566 /// Clears the errors for the row, including the RowError and errors set with
567 /// SetColumnError.
568 /// </summary>
569 public void ClearErrors ()
571 rowError = String.Empty;
572 ColumnErrors.Clear();
575 /// <summary>
576 /// Deletes the DataRow.
577 /// </summary>
578 public void Delete ()
580 _table.DeletingDataRow(this, DataRowAction.Delete);
581 switch (rowState) {
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.
589 DetachRow();
590 break;
591 case DataRowState.Deleted:
592 break;
593 default:
594 // check what to do with child rows
595 CheckChildRows(DataRowAction.Delete);
596 _table.DeleteRowFromIndexes (this);
597 rowState = DataRowState.Deleted;
598 break;
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)
624 Rule rule;
625 if (action == DataRowAction.Delete)
626 rule = fk.DeleteRule;
627 else
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);
639 switch (rule)
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];
662 break;
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);
677 break;
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);
690 break;
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]);
705 break;
710 /// <summary>
711 /// Ends the edit occurring on the row.
712 /// </summary>
713 public void EndEdit ()
715 if (_inChangingEvent)
716 throw new InRowChangingEventException("Cannot call EndEdit inside an OnRowChanging event.");
717 if (rowState == DataRowState.Detached)
719 editing = false;
720 return;
723 CheckReadOnlyStatus();
724 if (HasVersion (DataRowVersion.Proposed))
726 _inChangingEvent = true;
729 _table.ChangingDataRow(this, DataRowAction.Change);
731 finally
733 _inChangingEvent = false;
735 if (rowState == DataRowState.Unchanged)
736 rowState = DataRowState.Modified;
738 //Calling next method validates UniqueConstraints
739 //and ForeignKeys.
742 if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
743 _table.Rows.ValidateDataRowInternal(this);
745 catch (Exception e)
747 editing = false;
748 Table.RecordCache.DisposeRecord(_proposed);
749 _proposed = -1;
750 throw e;
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;
762 editing = false;
763 try {
764 // check all child rows.
765 CheckChildRows(DataRowAction.Change);
766 _proposed = -1;
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;
775 _current = backup;
776 editing = editing_backup;
777 // since we failed - propagate an exception
778 throw ex;
780 _table.ChangedDataRow(this, DataRowAction.Change);
784 /// <summary>
785 /// Gets the child rows of this DataRow using the specified DataRelation.
786 /// </summary>
787 public DataRow[] GetChildRows (DataRelation relation)
789 return GetChildRows (relation, DataRowVersion.Current);
792 /// <summary>
793 /// Gets the child rows of a DataRow using the specified RelationName of a
794 /// DataRelation.
795 /// </summary>
796 public DataRow[] GetChildRows (string relationName)
798 return GetChildRows (Table.DataSet.Relations[relationName]);
801 /// <summary>
802 /// Gets the child rows of a DataRow using the specified DataRelation, and
803 /// DataRowVersion.
804 /// </summary>
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;
841 break;
845 if (allColumnsMatch) rows.Add(row);
848 DataRow[] result = relation.ChildTable.NewRowArray(rows.Count);
849 rows.CopyTo(result, 0);
850 return result;
853 /// <summary>
854 /// Gets the child rows of a DataRow using the specified RelationName of a
855 /// DataRelation, and DataRowVersion.
856 /// </summary>
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;
870 if (index != null) {
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();
881 try {
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;
895 break;
899 if (allColumnsMatch) {
900 rows.Add(row);
904 finally {
905 fkc.Table.RecordCache.DisposeRecord(tmpRecord);
910 DataRow[] result = fkc.Table.NewRowArray(rows.Count);
911 rows.CopyTo(result, 0);
912 return result;
915 /// <summary>
916 /// Gets the error description of the specified DataColumn.
917 /// </summary>
918 public string GetColumnError (DataColumn column)
920 return GetColumnError (_table.Columns.IndexOf(column));
923 /// <summary>
924 /// Gets the error description for the column specified by index.
925 /// </summary>
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;
938 /// <summary>
939 /// Gets the error description for the column, specified by name.
940 /// </summary>
941 public string GetColumnError (string columnName)
943 return GetColumnError (_table.Columns.IndexOf(columnName));
946 /// <summary>
947 /// Gets an array of columns that have errors.
948 /// </summary>
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]);
958 columnOrdinal++;
961 return (DataColumn[])(dataColumns.ToArray (typeof(DataColumn)));
964 /// <summary>
965 /// Gets the parent row of a DataRow using the specified DataRelation.
966 /// </summary>
967 public DataRow GetParentRow (DataRelation relation)
969 return GetParentRow (relation, DataRowVersion.Current);
972 /// <summary>
973 /// Gets the parent row of a DataRow using the specified RelationName of a
974 /// DataRelation.
975 /// </summary>
976 public DataRow GetParentRow (string relationName)
978 return GetParentRow (relationName, DataRowVersion.Current);
981 /// <summary>
982 /// Gets the parent row of a DataRow using the specified DataRelation, and
983 /// DataRowVersion.
984 /// </summary>
985 public DataRow GetParentRow (DataRelation relation, DataRowVersion version)
987 DataRow[] rows = GetParentRows(relation, version);
988 if (rows.Length == 0) return null;
989 return rows[0];
992 /// <summary>
993 /// Gets the parent row of a DataRow using the specified RelationName of a
994 /// DataRelation, and DataRowVersion.
995 /// </summary>
996 public DataRow GetParentRow (string relationName, DataRowVersion version)
998 return GetParentRow (Table.DataSet.Relations[relationName], version);
1001 /// <summary>
1002 /// Gets the parent rows of a DataRow using the specified DataRelation.
1003 /// </summary>
1004 public DataRow[] GetParentRows (DataRelation relation)
1006 return GetParentRows (relation, DataRowVersion.Current);
1009 /// <summary>
1010 /// Gets the parent rows of a DataRow using the specified RelationName of a
1011 /// DataRelation.
1012 /// </summary>
1013 public DataRow[] GetParentRows (string relationName)
1015 return GetParentRows (relationName, DataRowVersion.Current);
1018 /// <summary>
1019 /// Gets the parent rows of a DataRow using the specified DataRelation, and
1020 /// DataRowVersion.
1021 /// </summary>
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);
1040 if (indx != null &&
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();
1051 try {
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;
1065 break;
1069 if (allColumnsMatch) {
1070 rows.Add(row);
1074 finally {
1075 relation.ParentTable.RecordCache.DisposeRecord(tmpRecord);
1080 DataRow[] result = relation.ParentTable.NewRowArray(rows.Count);
1081 rows.CopyTo(result, 0);
1082 return result;
1085 /// <summary>
1086 /// Gets the parent rows of a DataRow using the specified RelationName of a
1087 /// DataRelation, and DataRowVersion.
1088 /// </summary>
1089 public DataRow[] GetParentRows (string relationName, DataRowVersion version)
1091 return GetParentRows (Table.DataSet.Relations[relationName], version);
1094 /// <summary>
1095 /// Gets a value indicating whether a specified version exists.
1096 /// </summary>
1097 public bool HasVersion (DataRowVersion version)
1099 switch (version) {
1100 case DataRowVersion.Default:
1101 if (rowState == DataRowState.Deleted && !_inExpressionEvaluation)
1102 return false;
1103 if (rowState == DataRowState.Detached)
1104 return _proposed >= 0;
1105 return true;
1106 case DataRowVersion.Proposed:
1107 if (rowState == DataRowState.Deleted && !_inExpressionEvaluation)
1108 return false;
1109 return _proposed >= 0;
1110 case DataRowVersion.Current:
1111 if ((rowState == DataRowState.Deleted && !_inExpressionEvaluation) || rowState == DataRowState.Detached)
1112 return false;
1113 return _current >= 0;
1114 case DataRowVersion.Original:
1115 if (rowState == DataRowState.Detached)
1116 return false;
1117 return _original >= 0;
1119 return false;
1122 /// <summary>
1123 /// Gets a value indicating whether the specified DataColumn contains a null value.
1124 /// </summary>
1125 public bool IsNull (DataColumn column)
1127 return IsNull(column, DataRowVersion.Default);
1130 /// <summary>
1131 /// Gets a value indicating whether the column at the specified index contains a null
1132 /// value.
1133 /// </summary>
1134 public bool IsNull (int columnIndex)
1136 return IsNull(Table.Columns[columnIndex]);
1139 /// <summary>
1140 /// Gets a value indicating whether the named column contains a null value.
1141 /// </summary>
1142 public bool IsNull (string columnName)
1144 return IsNull(Table.Columns[columnName]);
1147 /// <summary>
1148 /// Gets a value indicating whether the specified DataColumn and DataRowVersion
1149 /// contains a null value.
1150 /// </summary>
1151 public bool IsNull (DataColumn column, DataRowVersion version)
1153 return column.DataContainer.IsNull(IndexFromVersion(version));
1156 /// <summary>
1157 /// Returns a value indicating whether all of the row columns specified contain a null value.
1158 /// </summary>
1159 internal bool IsNullColumns(DataColumn[] columns)
1161 bool allNull = true;
1162 for (int i = 0; i < columns.Length; i++)
1164 if (!IsNull(columns[i]))
1166 allNull = false;
1167 break;
1170 return allNull;
1173 /// <summary>
1174 /// Rejects all changes made to the row since AcceptChanges was last called.
1175 /// </summary>
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);
1189 CancelEdit ();
1190 switch (rowState)
1192 case DataRowState.Added:
1193 _table.DeleteRowFromIndexes (this);
1194 _table.Rows.RemoveInternal (this);
1195 break;
1196 case DataRowState.Modified:
1197 if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
1198 _table.Rows.ValidateDataRowInternal(this);
1199 rowState = DataRowState.Unchanged;
1200 break;
1201 case DataRowState.Deleted:
1202 rowState = DataRowState.Unchanged;
1203 if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
1204 _table.Rows.ValidateDataRowInternal(this);
1205 break;
1209 else {
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
1213 // if so: FIXME ;)
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.
1220 DetachRow();
1225 /// <summary>
1226 /// Sets the error description for a column specified as a DataColumn.
1227 /// </summary>
1228 public void SetColumnError (DataColumn column, string error)
1230 SetColumnError (_table.Columns.IndexOf (column), error);
1233 /// <summary>
1234 /// Sets the error description for a column specified by index.
1235 /// </summary>
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);
1247 /// <summary>
1248 /// Sets the error description for a column specified by name.
1249 /// </summary>
1250 public void SetColumnError (string columnName, string error)
1252 SetColumnError (_table.Columns.IndexOf (columnName), error);
1255 /// <summary>
1256 /// Sets the value of the specified DataColumn to a null value.
1257 /// </summary>
1258 protected void SetNull (DataColumn column)
1260 this[column] = DBNull.Value;
1263 /// <summary>
1264 /// Sets the parent row of a DataRow with specified new parent DataRow.
1265 /// </summary>
1266 public void SetParentRow (DataRow parentRow)
1268 SetParentRow(parentRow, null);
1271 /// <summary>
1272 /// Sets the parent row of a DataRow with specified new parent DataRow and
1273 /// DataRelation.
1274 /// </summary>
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();
1283 BeginEdit();
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;
1295 else
1296 this[childCols[i].Ordinal] = parentRow[parentCols[i]];
1301 else
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;
1310 else
1311 this[childCols[i].Ordinal] = parentRow[parentCols[i]];
1314 EndEdit();
1317 //Copy all values of this DataaRow to the row parameter.
1318 internal void CopyValuesToRow(DataRow row)
1320 if (row == null)
1321 throw new ArgumentNullException("row");
1322 if (row == this)
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];
1360 CopyState(row);
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)
1373 return true;
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))
1387 return true;
1391 return false;
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