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>
10 // Sureshkumar T <tsureshkumar@novell.com>
11 // Veerapuram Varadhan <vvaradhan@novell.com>
13 // (C) Ximian, Inc 2002
14 // (C) Daniel Morgan 2002, 2003
15 // Copyright (C) 2002 Tim Coleman
19 // Copyright (C) 2004-2009 Novell, Inc (http://www.novell.com)
21 // Permission is hereby granted, free of charge, to any person obtaining
22 // a copy of this software and associated documentation files (the
23 // "Software"), to deal in the Software without restriction, including
24 // without limitation the rights to use, copy, modify, merge, publish,
25 // distribute, sublicense, and/or sell copies of the Software, and to
26 // permit persons to whom the Software is furnished to do so, subject to
27 // the following conditions:
29 // The above copyright notice and this permission notice shall be
30 // included in all copies or substantial portions of the Software.
32 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
33 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
34 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
35 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
36 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
37 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
38 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
42 using System.Data.Common;
43 using System.Collections;
44 using System.Globalization;
47 using System.ComponentModel;
50 namespace System.Data {
52 /// Represents a row of data in a DataTable.
57 public class DataRow {
60 private DataTable _table;
62 internal int _original = -1;
63 internal int _current = -1;
64 internal int _proposed = -1;
66 private ArrayList _columnErrors;
67 private string rowError;
68 internal int xmlRowID = 0;
69 internal bool _nullConstraintViolation;
70 private string _nullConstraintMessage;
71 private bool _inChangingEvent;
73 internal bool _rowChanged = false;
75 private XmlDataDocument.XmlDataElement mappedElement;
76 internal bool _inExpressionEvaluation = false;
83 /// This member supports the .NET Framework infrastructure and is not intended to be
84 /// used directly from your code.
86 protected internal DataRow (DataRowBuilder builder)
88 _table = builder.Table;
89 // Get the row id from the builder.
90 _rowId = builder._rowId;
92 rowError = String.Empty;
95 internal DataRow (DataTable table, int rowId)
101 #endregion // Constructors
105 private ArrayList ColumnErrors {
107 if (_columnErrors == null)
108 _columnErrors = new ArrayList ();
109 return _columnErrors;
112 set { _columnErrors = value; }
116 /// Gets a value indicating whether there are errors in a row.
118 public bool HasErrors {
120 if (RowError != string.Empty)
123 foreach (String columnError in ColumnErrors) {
124 if (columnError != null && columnError != string.Empty)
132 /// Gets or sets the data stored in the column specified by name.
134 public object this [string columnName] {
135 get { return this [columnName, DataRowVersion.Default]; }
137 DataColumn column = _table.Columns [columnName];
139 throw new ArgumentException ("The column '" + columnName +
140 "' does not belong to the table : " + _table.TableName);
141 this [column.Ordinal] = value;
146 /// Gets or sets the data stored in specified DataColumn
148 public object this [DataColumn column] {
149 get { return this [column, DataRowVersion.Default]; }
152 throw new ArgumentNullException ("column");
153 int columnIndex = _table.Columns.IndexOf (column);
154 if (columnIndex == -1)
155 throw new ArgumentException (string.Format (CultureInfo.InvariantCulture,
156 "The column '{0}' does not belong to the table : {1}.",
157 column.ColumnName, _table.TableName));
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 ();
173 DataColumn column = _table.Columns [columnIndex];
174 _table.ChangingDataColumn (this, column, value);
177 if (value == null && column.DataType.IsValueType)
179 if (value == null && column.DataType != typeof (string))
181 throw new ArgumentException ("Canot set column '"
182 + column.ColumnName + "' to be null."
183 + " Please use DBNull instead.");
187 CheckValue (value, column);
188 bool already_editing = Proposed >= 0;
189 if (!already_editing)
192 column [Proposed] = value;
193 _table.ChangedDataColumn (this, column, value);
195 if (!already_editing)
201 /// Gets the specified version of data stored in the named column.
203 public object this [string columnName, DataRowVersion version] {
205 DataColumn column = _table.Columns [columnName];
207 throw new ArgumentException ("The column '" + columnName +
208 "' does not belong to the table : " + _table.TableName);
209 return this [column.Ordinal, version];
214 /// Gets the specified version of data stored in the specified DataColumn.
216 public object this [DataColumn column, DataRowVersion version] {
219 throw new ArgumentNullException ("column");
220 if (column.Table != Table)
221 throw new ArgumentException (string.Format (CultureInfo.InvariantCulture,
222 "The column '{0}' does not belong to the table : {1}.",
223 column.ColumnName, _table.TableName));
224 return this [column.Ordinal, version];
229 /// Set a value for the column into the offset specified by the version.<br>
230 /// If the value is auto increment or null, necessary auto increment value
231 /// or the default value will be used.
233 internal void SetValue (int column, object value, int version)
235 DataColumn dc = Table.Columns [column];
237 if (value == null && !dc.AutoIncrement) // set default value / auto increment
238 value = dc.DefaultValue;
240 Table.ChangingDataColumn (this, dc, value);
241 CheckValue (value, dc);
242 if (!dc.AutoIncrement)
243 dc [version] = value;
244 else if (_proposed >= 0 && _proposed != version) // proposed holds the AI
245 dc [version] = dc [_proposed];
249 /// Gets the data stored in the column, specified by index and version of the data to
252 public object this [int columnIndex, DataRowVersion version] {
254 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
255 throw new IndexOutOfRangeException ();
257 DataColumn column = _table.Columns [columnIndex];
258 int recordIndex = IndexFromVersion (version);
260 if (column.Expression != String.Empty && _table.Rows.IndexOf (this) != -1) {
261 // FIXME: how does this handle 'version'?
262 // TODO: Can we avoid the Eval each time by using the cached value?
263 object o = column.CompiledExpression.Eval (this);
264 if (o != null && o != DBNull.Value)
265 o = Convert.ChangeType (o, column.DataType);
266 column [recordIndex] = o;
267 return column [recordIndex];
270 return column [recordIndex];
275 /// Gets or sets all of the values for this row through an array.
277 public object [] ItemArray {
279 // Accessing deleted rows
280 if (RowState == DataRowState.Deleted)
281 throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
284 if (RowState == DataRowState.Detached) {
285 // Check if datarow is removed from the table.
287 throw new RowNotInTableException (
288 "This row has been removed from a table and does not have any data."
289 + " BeginEdit() will allow creation of new data in this row.");
293 object[] items = new object [_table.Columns.Count];
295 foreach(DataColumn column in _table.Columns)
296 items [column.Ordinal] = column [index];
300 if (value.Length > _table.Columns.Count)
301 throw new ArgumentException ();
303 if (RowState == DataRowState.Deleted)
304 throw new DeletedRowInaccessibleException ();
308 DataColumnChangeEventArgs e = new DataColumnChangeEventArgs ();
309 foreach (DataColumn column in _table.Columns) {
310 int i = column.Ordinal;
311 object newVal = i < value.Length ? value [i] : null;
316 e.Initialize (this, column, newVal);
317 CheckValue (e.ProposedValue, column);
318 _table.RaiseOnColumnChanging (e);
319 column [Proposed] = e.ProposedValue;
320 _table.RaiseOnColumnChanged (e);
328 /// Gets the current state of the row in regards to its relationship to the
329 /// DataRowCollection.
331 public DataRowState RowState {
334 if (Original == -1 && Current == -1)
335 return DataRowState.Detached;
336 if (Original == Current)
337 return DataRowState.Unchanged;
339 return DataRowState.Added;
341 return DataRowState.Deleted;
342 return DataRowState.Modified;
346 if (DataRowState.Detached == value) {
350 if (DataRowState.Unchanged == value)
352 if (DataRowState.Added == value)
354 if (DataRowState.Deleted == value)
361 public void SetAdded ()
363 if (RowState != DataRowState.Unchanged)
364 throw new InvalidOperationException ("SetAdded and SetModified can only be called on DataRows with Unchanged DataRowState.");
368 public void SetModified ()
370 if (RowState != DataRowState.Unchanged)
371 throw new InvalidOperationException ("SetAdded and SetModified can only be called on DataRows with Unchanged DataRowState.");
372 Current = _table.RecordCache.NewRecord ();
373 _table.RecordCache.CopyRecord (_table, Original, Current);
378 /// Gets the DataTable for which this row has a schema.
380 public DataTable Table {
381 get { return _table; }
383 internal set { _table = value; }
388 /// Gets and sets index of row. This is used from
391 internal int XmlRowID {
392 get { return xmlRowID; }
393 set { xmlRowID = value; }
397 /// Gets and sets index of row.
400 get { return _rowId; }
401 set { _rowId = value; }
404 internal int Original {
405 get { return _original; }
408 //Table.RecordCache[_original] = null;
409 Table.RecordCache [value] = this;
415 internal int Current {
416 get { return _current; }
419 //Table.RecordCache[_current] = null;
420 Table.RecordCache [value] = this;
426 internal int Proposed {
427 get { return _proposed; }
430 //Table.RecordCache[_proposed] = null;
431 Table.RecordCache [value] = this;
440 // Called by DataRowCollection.Add/InsertAt
441 internal void AttachAt (int row_id, DataRowAction action)
444 if (Proposed != -1) {
446 Table.RecordCache.DisposeRecord (Current);
451 if ((action & (DataRowAction.ChangeCurrentAndOriginal | DataRowAction.ChangeOriginal)) != 0)
458 Table.DeleteRowFromIndexes (this);
459 _table.Rows.RemoveInternal (this);
461 if (Proposed >= 0 && Proposed != Current && Proposed != Original)
462 _table.RecordCache.DisposeRecord (Proposed);
465 if (Current >= 0 && Current != Original)
466 _table.RecordCache.DisposeRecord (Current);
470 _table.RecordCache.DisposeRecord (Original);
476 internal void ImportRecord (int record)
478 if (HasVersion (DataRowVersion.Proposed))
479 Table.RecordCache.DisposeRecord (Proposed);
483 foreach (DataColumn column in Table.Columns.AutoIncrmentColumns)
484 column.UpdateAutoIncrementValue (column.DataContainer.GetInt64 (Proposed));
486 foreach (DataColumn col in Table.Columns)
487 CheckValue (this [col], col, false);
490 private void CheckValue (object v, DataColumn col)
492 CheckValue (v, col, true);
495 private void CheckValue (object v, DataColumn col, bool doROCheck)
497 if (doROCheck && _rowId != -1 && col.ReadOnly)
498 throw new ReadOnlyException ();
500 if (v == null || v == DBNull.Value) {
501 if (col.AllowDBNull || col.AutoIncrement || col.DefaultValue != DBNull.Value)
504 //Constraint violations during data load is raise in DataTable EndLoad
505 this._nullConstraintViolation = true;
506 if (this.Table._duringDataLoad || (Table.DataSet != null && !Table.DataSet.EnforceConstraints))
507 this.Table._nullConstraintViolationDuringDataLoad = true;
508 _nullConstraintMessage = "Column '" + col.ColumnName + "' does not allow nulls.";
513 /// Gets or sets the custom error description for a row.
515 public string RowError {
516 get { return rowError; }
517 set { rowError = value; }
520 internal int IndexFromVersion (DataRowVersion version)
523 case DataRowVersion.Default:
531 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.");
533 throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
535 case DataRowVersion.Proposed:
536 return AssertValidVersionIndex (version, Proposed);
537 case DataRowVersion.Current:
538 return AssertValidVersionIndex (version, Current);
539 case DataRowVersion.Original:
540 return AssertValidVersionIndex (version, Original);
542 throw new DataException ("Version must be Original, Current, or Proposed.");
546 private int AssertValidVersionIndex (DataRowVersion version, int index)
551 throw new VersionNotFoundException (String.Format ("There is no {0} data to accces.", version));
554 internal DataRowVersion VersionFromIndex (int index)
557 throw new ArgumentException ("Index must not be negative.");
559 // the order of ifs matters
560 if (index == Current)
561 return DataRowVersion.Current;
562 if (index == Original)
563 return DataRowVersion.Original;
564 if (index == Proposed)
565 return DataRowVersion.Proposed;
567 throw new ArgumentException (String.Format ("The index {0} does not belong to this row.", index));
570 internal XmlDataDocument.XmlDataElement DataElement {
572 if (mappedElement != null || _table.DataSet == null || _table.DataSet._xmlDataDocument == null)
573 return mappedElement;
575 // create mapped XmlDataElement
576 mappedElement = new XmlDataDocument.XmlDataElement (
577 this, _table.Prefix, XmlHelper.Encode (_table.TableName),
578 _table.Namespace, _table.DataSet._xmlDataDocument);
579 return mappedElement;
581 set { mappedElement = value; }
584 internal void SetOriginalValue (string columnName, object val)
586 DataColumn column = _table.Columns [columnName];
587 _table.ChangingDataColumn (this, column, val);
589 if (Original < 0 || Original == Current) {
590 Original = Table.RecordCache.NewRecord ();
592 foreach (DataColumn col in _table.Columns)
593 col.DataContainer.CopyValue (Table.DefaultValuesRowIndex, Original);
596 CheckValue (val, column);
597 column [Original] = val;
601 /// Commits all the changes made to this row since the last time AcceptChanges was
604 public void AcceptChanges ()
606 EndEdit (); // in case it hasn't been called
608 _table.ChangingDataRow (this, DataRowAction.Commit);
609 CheckChildRows (DataRowAction.Commit);
611 case DataRowState.Added:
612 case DataRowState.Modified:
614 Table.RecordCache.DisposeRecord (Original);
617 case DataRowState.Deleted:
620 case DataRowState.Detached:
621 throw new RowNotInTableException("Cannot perform this operation on a row not in the table.");
624 _table.ChangedDataRow (this, DataRowAction.Commit);
628 /// Begins an edit operation on a DataRow object.
631 [EditorBrowsable (EditorBrowsableState.Advanced)]
633 public void BeginEdit ()
635 if (_inChangingEvent)
636 throw new InRowChangingEventException ("Cannot call BeginEdit inside an OnRowChanging event.");
637 if (RowState == DataRowState.Deleted)
638 throw new DeletedRowInaccessibleException ();
640 if (!HasVersion (DataRowVersion.Proposed)) {
641 Proposed = Table.RecordCache.NewRecord ();
642 int from = HasVersion (DataRowVersion.Current) ? Current : Table.DefaultValuesRowIndex;
643 for (int i = 0; i < Table.Columns.Count; i++){
644 DataColumn column = Table.Columns [i];
645 column.DataContainer.CopyValue (from, Proposed);
651 /// Cancels the current edit on the row.
654 [EditorBrowsable (EditorBrowsableState.Advanced)]
656 public void CancelEdit ()
658 if (_inChangingEvent)
659 throw new InRowChangingEventException ("Cannot call CancelEdit inside an OnRowChanging event.");
661 if (HasVersion (DataRowVersion.Proposed)) {
662 int oldRecord = Proposed;
663 DataRowState oldState = RowState;
664 Table.RecordCache.DisposeRecord(Proposed);
667 foreach (Index index in Table.Indexes)
668 index.Update (this, oldRecord, DataRowVersion.Proposed, oldState);
673 /// Clears the errors for the row, including the RowError and errors set with
676 public void ClearErrors ()
678 rowError = String.Empty;
679 ColumnErrors.Clear();
683 /// Deletes the DataRow.
685 public void Delete ()
687 _table.DeletingDataRow (this, DataRowAction.Delete);
689 case DataRowState.Added:
690 CheckChildRows (DataRowAction.Delete);
693 case DataRowState.Deleted:
694 case DataRowState.Detached:
697 // check what to do with child rows
698 CheckChildRows (DataRowAction.Delete);
702 int current = Current;
703 DataRowState oldState = RowState;
704 if (Current != Original)
705 _table.RecordCache.DisposeRecord (Current);
707 foreach (Index index in Table.Indexes)
708 index.Update (this, current, DataRowVersion.Current, oldState);
710 _table.DeletedDataRow (this, DataRowAction.Delete);
713 // check the child rows of this row before deleting the row.
714 private void CheckChildRows (DataRowAction action)
716 DataSet ds = _table.DataSet;
718 if (ds == null || !ds.EnforceConstraints)
721 // if the table we're attached-to doesn't have an constraints, no foreign keys are pointing to us ...
722 if (_table.Constraints.Count == 0)
725 foreach (DataTable table in ds.Tables) {
726 // loop on all ForeignKeyConstrain of the table.
727 foreach (Constraint constraint in table.Constraints) {
728 ForeignKeyConstraint fk = constraint as ForeignKeyConstraint;
729 if (fk == null || fk.RelatedTable != _table)
733 case DataRowAction.Delete:
734 CheckChildRows (fk, action, fk.DeleteRule);
736 case DataRowAction.Commit:
737 case DataRowAction.Rollback:
738 if (fk.AcceptRejectRule != AcceptRejectRule.None)
739 CheckChildRows (fk, action, Rule.Cascade);
742 CheckChildRows (fk, action, fk.UpdateRule);
749 private void CheckChildRows (ForeignKeyConstraint fkc, DataRowAction action, Rule rule)
751 DataRow [] childRows = GetChildRows (fkc, DataRowVersion.Current);
752 if (childRows == null)
756 case Rule.Cascade: // delete or change all relted rows.
758 case DataRowAction.Delete:
759 for (int j = 0; j < childRows.Length; j++) {
760 if (childRows [j].RowState != DataRowState.Deleted)
761 childRows [j].Delete ();
764 case DataRowAction.Change:
765 for (int j = 0; j < childRows.Length; j++) {
766 // if action is change we change the values in the child row
767 // change only the values in the key columns
768 // set the childcolumn value to the new parent row value
769 for (int k = 0; k < fkc.Columns.Length; k++)
770 if (!fkc.RelatedColumns [k].DataContainer [Current].Equals (fkc.RelatedColumns [k].DataContainer [Proposed]))
771 childRows [j][fkc.Columns [k]] = this [fkc.RelatedColumns [k], DataRowVersion.Proposed];
774 case DataRowAction.Rollback:
775 for (int j = 0; j < childRows.Length; j++) {
776 if (childRows [j].RowState != DataRowState.Unchanged)
777 childRows [j].RejectChanges ();
782 case Rule.None: // throw an exception if there are any child rows.
783 for (int j = 0; j < childRows.Length; j++) {
784 if (childRows[j].RowState != DataRowState.Deleted) {
785 string changeStr = "Cannot change this row because constraints are enforced on relation " + fkc.ConstraintName +", and changing this row will strand child rows.";
786 string delStr = "Cannot delete this row because constraints are enforced on relation " + fkc.ConstraintName +", and deleting this row will strand child rows.";
787 string message = action == DataRowAction.Delete ? delStr : changeStr;
788 throw new InvalidConstraintException (message);
792 case Rule.SetDefault: // set the values in the child rows to the default value of the columns.
793 if (childRows.Length > 0) {
794 int defaultValuesRowIndex = childRows [0].Table.DefaultValuesRowIndex;
795 foreach (DataRow childRow in childRows) {
796 if (childRow.RowState != DataRowState.Deleted) {
797 int defaultIdx = childRow.IndexFromVersion (DataRowVersion.Default);
798 foreach (DataColumn column in fkc.Columns)
799 column.DataContainer.CopyValue (defaultValuesRowIndex, defaultIdx);
804 case Rule.SetNull: // set the values in the child row to null.
805 for (int j = 0; j < childRows.Length; j++) {
806 DataRow child = childRows [j];
807 if (childRows[j].RowState != DataRowState.Deleted) {
808 // set only the key columns to DBNull
809 for (int k = 0; k < fkc.Columns.Length; k++)
810 child.SetNull (fkc.Columns[k]);
818 /// Ends the edit occurring on the row.
821 [EditorBrowsable (EditorBrowsableState.Advanced)]
823 public void EndEdit ()
825 if (_inChangingEvent)
826 throw new InRowChangingEventException ("Cannot call EndEdit inside an OnRowChanging event.");
828 if (RowState == DataRowState.Detached || !HasVersion (DataRowVersion.Proposed))
831 CheckReadOnlyStatus ();
833 _inChangingEvent = true;
835 _table.ChangingDataRow (this, DataRowAction.Change);
837 _inChangingEvent = false;
840 DataRowState oldState = RowState;
842 int oldRecord = Current;
847 //FIXME : ideally indexes shouldnt be maintained during dataload.But this needs to
848 //be implemented at multiple places.For now, just maintain the index.
849 //if (!Table._duringDataLoad) {
850 foreach (Index index in Table.Indexes)
851 index.Update (this, oldRecord, DataRowVersion.Current, oldState);
855 AssertConstraints ();
857 // restore previous state to let the cascade update to find the rows
861 CheckChildRows (DataRowAction.Change);
867 int proposed = Proposed >= 0 ? Proposed : Current;
869 //if (!Table._duringDataLoad) {
870 foreach (Index index in Table.Indexes)
871 index.Update (this, proposed, DataRowVersion.Current, RowState);
876 if (Original != oldRecord)
877 Table.RecordCache.DisposeRecord (oldRecord);
879 // Note : row state must not be changed before all the job on indexes finished,
880 // since the indexes works with recods rather than with rows and the decision
881 // which of row records to choose depends on row state.
882 if (_rowChanged == true) {
883 _table.ChangedDataRow (this, DataRowAction.Change);
889 /// Gets the child rows of this DataRow using the specified DataRelation.
891 public DataRow [] GetChildRows (DataRelation relation)
893 return GetChildRows (relation, DataRowVersion.Default);
897 /// Gets the child rows of a DataRow using the specified RelationName of a
900 public DataRow [] GetChildRows (string relationName)
902 return GetChildRows (Table.DataSet.Relations [relationName]);
906 /// Gets the child rows of a DataRow using the specified DataRelation, and
909 public DataRow[] GetChildRows (DataRelation relation, DataRowVersion version)
911 if (relation == null)
912 return Table.NewRowArray (0);
914 if (this.Table == null)
915 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.");
917 if (relation.DataSet != this.Table.DataSet)
918 throw new ArgumentException ();
920 if (_table != relation.ParentTable)
921 throw new InvalidConstraintException ("GetChildRow requires a row whose Table is " + relation.ParentTable + ", but the specified row's table is " + _table);
923 if (relation.ChildKeyConstraint != null)
924 return GetChildRows (relation.ChildKeyConstraint, version);
926 ArrayList rows = new ArrayList ();
927 DataColumn[] parentColumns = relation.ParentColumns;
928 DataColumn[] childColumns = relation.ChildColumns;
929 int numColumn = parentColumns.Length;
930 DataRow[] result = null;
932 int versionIndex = IndexFromVersion (version);
933 int tmpRecord = relation.ChildTable.RecordCache.NewRecord ();
936 for (int i = 0; i < numColumn; i++)
937 // according to MSDN: the DataType value for both columns must be identical.
938 childColumns [i].DataContainer.CopyValue (parentColumns [i].DataContainer, versionIndex, tmpRecord);
940 Index index = relation.ChildTable.FindIndex (childColumns);
943 int [] records = index.FindAll (tmpRecord);
944 result = relation.ChildTable.NewRowArray (records.Length);
945 for(int i = 0; i < records.Length; i++)
946 result [i] = relation.ChildTable.RecordCache [records [i]];
948 foreach (DataRow row in relation.ChildTable.Rows) {
949 bool allColumnsMatch = false;
950 if (row.HasVersion (DataRowVersion.Default)) {
951 allColumnsMatch = true;
952 int childIndex = row.IndexFromVersion (DataRowVersion.Default);
953 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) {
954 if (childColumns[columnCnt].DataContainer.CompareValues (childIndex, tmpRecord) != 0) {
955 allColumnsMatch = false;
963 result = relation.ChildTable.NewRowArray (rows.Count);
964 rows.CopyTo (result, 0);
968 relation.ChildTable.RecordCache.DisposeRecord (tmpRecord);
975 /// Gets the child rows of a DataRow using the specified RelationName of a
976 /// DataRelation, and DataRowVersion.
978 public DataRow [] GetChildRows (string relationName, DataRowVersion version)
980 return GetChildRows (Table.DataSet.Relations [relationName], version);
983 private DataRow [] GetChildRows (ForeignKeyConstraint fkc, DataRowVersion version)
985 ArrayList rows = new ArrayList ();
986 DataColumn [] parentColumns = fkc.RelatedColumns;
987 DataColumn [] childColumns = fkc.Columns;
988 int numColumn = parentColumns.Length;
990 Index index = fkc.Index;
992 int curIndex = IndexFromVersion (version);
993 int tmpRecord = fkc.Table.RecordCache.NewRecord ();
994 for (int i = 0; i < numColumn; i++)
995 // according to MSDN: the DataType value for both columns must be identical.
996 childColumns [i].DataContainer.CopyValue (parentColumns [i].DataContainer, curIndex, tmpRecord);
1000 // get the child rows from the index
1001 int[] childRecords = index.FindAll (tmpRecord);
1002 for (int i = 0; i < childRecords.Length; i++)
1003 rows.Add (childColumns [i].Table.RecordCache [childRecords [i]]);
1004 } else { // if there is no index we search manualy.
1005 foreach (DataRow row in fkc.Table.Rows) {
1006 bool allColumnsMatch = false;
1007 if (row.HasVersion (DataRowVersion.Default)) {
1008 allColumnsMatch = true;
1009 int childIndex = row.IndexFromVersion (DataRowVersion.Default);
1010 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) {
1011 if (childColumns [columnCnt].DataContainer.CompareValues (childIndex, tmpRecord) != 0) {
1012 allColumnsMatch = false;
1017 if (allColumnsMatch)
1022 fkc.Table.RecordCache.DisposeRecord(tmpRecord);
1025 DataRow[] result = fkc.Table.NewRowArray (rows.Count);
1026 rows.CopyTo (result, 0);
1031 /// Gets the error description of the specified DataColumn.
1033 public string GetColumnError (DataColumn column)
1036 throw new ArgumentNullException ("column");
1038 int index = _table.Columns.IndexOf (column);
1040 throw new ArgumentException (String.Format ("Column '{0}' does not belong to table {1}.", column.ColumnName, Table.TableName));
1042 return GetColumnError (index);
1046 /// Gets the error description for the column specified by index.
1048 public string GetColumnError (int columnIndex)
1050 if (columnIndex < 0 || columnIndex >= Table.Columns.Count)
1051 throw new IndexOutOfRangeException ();
1053 string retVal = null;
1054 if (columnIndex < ColumnErrors.Count)
1055 retVal = (String) ColumnErrors [columnIndex];
1056 return (retVal != null) ? retVal : String.Empty;
1060 /// Gets the error description for the column, specified by name.
1062 public string GetColumnError (string columnName)
1064 return GetColumnError (_table.Columns.IndexOf (columnName));
1068 /// Gets an array of columns that have errors.
1070 public DataColumn [] GetColumnsInError ()
1072 ArrayList dataColumns = new ArrayList ();
1074 int columnOrdinal = 0;
1075 foreach (String columnError in ColumnErrors) {
1076 if (columnError != null && columnError != String.Empty)
1077 dataColumns.Add (_table.Columns [columnOrdinal]);
1081 return (DataColumn [])(dataColumns.ToArray (typeof (DataColumn)));
1085 /// Gets the parent row of a DataRow using the specified DataRelation.
1087 public DataRow GetParentRow (DataRelation relation)
1089 return GetParentRow (relation, DataRowVersion.Default);
1093 /// Gets the parent row of a DataRow using the specified RelationName of a
1096 public DataRow GetParentRow (string relationName)
1098 return GetParentRow (relationName, DataRowVersion.Default);
1102 /// Gets the parent row of a DataRow using the specified DataRelation, and
1105 public DataRow GetParentRow (DataRelation relation, DataRowVersion version)
1107 DataRow[] rows = GetParentRows (relation, version);
1108 if (rows.Length == 0)
1114 /// Gets the parent row of a DataRow using the specified RelationName of a
1115 /// DataRelation, and DataRowVersion.
1117 public DataRow GetParentRow (string relationName, DataRowVersion version)
1119 return GetParentRow (Table.DataSet.Relations [relationName], version);
1123 /// Gets the parent rows of a DataRow using the specified DataRelation.
1125 public DataRow [] GetParentRows (DataRelation relation)
1127 return GetParentRows (relation, DataRowVersion.Default);
1131 /// Gets the parent rows of a DataRow using the specified RelationName of a
1134 public DataRow [] GetParentRows (string relationName)
1136 return GetParentRows (relationName, DataRowVersion.Default);
1140 /// Gets the parent rows of a DataRow using the specified DataRelation, and
1143 public DataRow [] GetParentRows (DataRelation relation, DataRowVersion version)
1145 // TODO: Caching for better preformance
1146 if (relation == null)
1147 return Table.NewRowArray (0);
1149 if (this.Table == null)
1150 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.");
1152 if (relation.DataSet != this.Table.DataSet)
1153 throw new ArgumentException ();
1155 if (_table != relation.ChildTable)
1156 throw new InvalidConstraintException ("GetParentRows requires a row whose Table is " + relation.ChildTable + ", but the specified row's table is " + _table);
1158 ArrayList rows = new ArrayList ();
1159 DataColumn[] parentColumns = relation.ParentColumns;
1160 DataColumn[] childColumns = relation.ChildColumns;
1161 int numColumn = parentColumns.Length;
1163 int curIndex = IndexFromVersion (version);
1164 int tmpRecord = relation.ParentTable.RecordCache.NewRecord ();
1165 for (int i = 0; i < numColumn; i++)
1166 // according to MSDN: the DataType value for both columns must be identical.
1167 parentColumns [i].DataContainer.CopyValue(childColumns [i].DataContainer, curIndex, tmpRecord);
1170 Index index = relation.ParentTable.FindIndex(parentColumns);
1171 if (index != null) { // get the parent rows from the index
1172 int [] parentRecords = index.FindAll (tmpRecord);
1173 for (int i = 0; i < parentRecords.Length; i++)
1174 rows.Add (parentColumns [i].Table.RecordCache [parentRecords [i]]);
1175 } else { // no index so we have to search manualy.
1176 foreach (DataRow row in relation.ParentTable.Rows) {
1177 bool allColumnsMatch = false;
1178 if (row.HasVersion (DataRowVersion.Default)) {
1179 allColumnsMatch = true;
1180 int parentIndex = row.IndexFromVersion (DataRowVersion.Default);
1181 for (int columnCnt = 0; columnCnt < numColumn; columnCnt++) {
1182 if (parentColumns [columnCnt].DataContainer.CompareValues (parentIndex, tmpRecord) != 0) {
1183 allColumnsMatch = false;
1188 if (allColumnsMatch)
1193 relation.ParentTable.RecordCache.DisposeRecord (tmpRecord);
1196 DataRow [] result = relation.ParentTable.NewRowArray (rows.Count);
1197 rows.CopyTo (result, 0);
1202 /// Gets the parent rows of a DataRow using the specified RelationName of a
1203 /// DataRelation, and DataRowVersion.
1205 public DataRow [] GetParentRows (string relationName, DataRowVersion version)
1207 return GetParentRows (Table.DataSet.Relations [relationName], version);
1211 /// Gets a value indicating whether a specified version exists.
1213 public bool HasVersion (DataRowVersion version)
1216 case DataRowVersion.Default:
1217 return (Proposed >= 0 || Current >= 0);
1218 case DataRowVersion.Proposed:
1219 return Proposed >= 0;
1220 case DataRowVersion.Current:
1221 return Current >= 0;
1222 case DataRowVersion.Original:
1223 return Original >= 0;
1225 return IndexFromVersion (version) >= 0;
1230 /// Gets a value indicating whether the specified DataColumn contains a null value.
1232 public bool IsNull (DataColumn column)
1234 return IsNull (column, DataRowVersion.Default);
1238 /// Gets a value indicating whether the column at the specified index contains a null
1241 public bool IsNull (int columnIndex)
1243 return IsNull (Table.Columns [columnIndex]);
1247 /// Gets a value indicating whether the named column contains a null value.
1249 public bool IsNull (string columnName)
1251 return IsNull (Table.Columns [columnName]);
1255 /// Gets a value indicating whether the specified DataColumn and DataRowVersion
1256 /// contains a null value.
1258 public bool IsNull (DataColumn column, DataRowVersion version)
1260 return column.DataContainer.IsNull (IndexFromVersion (version));
1264 /// Returns a value indicating whether all of the row columns specified contain a null value.
1266 internal bool IsNullColumns (DataColumn [] columns)
1269 for (i = 0; i < columns.Length; i++) {
1270 if (!IsNull (columns [i]))
1273 return i == columns.Length;
1277 /// Rejects all changes made to the row since AcceptChanges was last called.
1279 public void RejectChanges ()
1281 if (RowState == DataRowState.Detached)
1282 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.");
1283 // If original is null, then nothing has happened since AcceptChanges
1284 // was last called. We have no "original" to go back to.
1286 // Varadhan: Following if may be un-necessary
1287 /*if (_inChangingEvent) {
1288 _table.ChangedDataRow (this, DataRowAction.Rollback);
1292 Table.ChangingDataRow (this, DataRowAction.Rollback);
1293 //TODO : Need to Verify the constraints..
1295 case DataRowState.Added:
1298 case DataRowState.Modified:
1299 int current = Current;
1300 Table.RecordCache.DisposeRecord (Current);
1301 CheckChildRows (DataRowAction.Rollback);
1303 foreach (Index index in Table.Indexes)
1304 index.Update (this, current, DataRowVersion.Current, DataRowState.Modified);
1306 case DataRowState.Deleted:
1307 CheckChildRows (DataRowAction.Rollback);
1309 // Add row to index and validate if the constraints are satisfied
1313 Table.ChangedDataRow (this, DataRowAction.Rollback);
1317 /// Sets the error description for a column specified as a DataColumn.
1319 public void SetColumnError (DataColumn column, string error)
1321 SetColumnError (_table.Columns.IndexOf (column), error);
1325 /// Sets the error description for a column specified by index.
1327 public void SetColumnError (int columnIndex, string error)
1329 if (columnIndex < 0 || columnIndex >= Table.Columns.Count)
1330 throw new IndexOutOfRangeException ();
1332 while (columnIndex >= ColumnErrors.Count)
1333 ColumnErrors.Add (null);
1335 ColumnErrors [columnIndex] = error;
1339 /// Sets the error description for a column specified by name.
1341 public void SetColumnError (string columnName, string error)
1343 SetColumnError (_table.Columns.IndexOf (columnName), error);
1347 /// Sets the value of the specified DataColumn to a null value.
1349 protected void SetNull (DataColumn column)
1351 this [column] = DBNull.Value;
1355 /// Sets the parent row of a DataRow with specified new parent DataRow.
1357 public void SetParentRow (DataRow parentRow)
1359 SetParentRow (parentRow, null);
1363 /// Sets the parent row of a DataRow with specified new parent DataRow and
1366 public void SetParentRow (DataRow parentRow, DataRelation relation)
1368 if (_table == null || (parentRow != null && parentRow.Table == null))
1369 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.");
1371 if (parentRow != null && _table.DataSet != parentRow.Table.DataSet)
1372 throw new ArgumentException ();
1374 if (RowState == DataRowState.Detached && !HasVersion (DataRowVersion.Default))
1375 // the row should have default data to access, i.e. we can do this for the newly created row, but not for the row once deleted from the table
1376 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.");
1380 IEnumerable relations;
1381 if (relation != null) {
1382 if (parentRow != null && relation.ParentColumns [0].Table != parentRow.Table)
1383 throw new InvalidConstraintException (string.Format ("Parent belongs to table {0} but relation is for table {1}", parentRow.Table, relation.ParentColumns [0].Table));
1384 relations = new DataRelation [] { relation };
1386 relations = _table.ParentRelations;
1389 foreach (DataRelation rel in relations) {
1390 DataColumn [] childCols = rel.ChildColumns;
1391 DataColumn [] parentCols = rel.ParentColumns;
1393 for (int i = 0; i < parentCols.Length; i++) {
1394 if (parentRow == null) {
1395 childCols [i].DataContainer [Proposed] = DBNull.Value;
1396 } else if (parentCols [i].Table == parentRow.Table) {
1397 int defaultIdx = parentRow.IndexFromVersion (DataRowVersion.Default);
1398 childCols [i].DataContainer.CopyValue(parentCols [i].DataContainer, defaultIdx, Proposed);
1406 //Copy all values of this DataRow to the row parameter.
1407 internal void CopyValuesToRow (DataRow row)
1409 CopyValuesToRow(row, true);
1412 internal void CopyValuesToRow (DataRow row, bool doROCheck)
1415 throw new ArgumentNullException("row");
1417 throw new ArgumentException("'row' is the same as this object");
1419 // create target records if missing.
1420 if (HasVersion (DataRowVersion.Original)) {
1421 if (row.Original < 0)
1422 row.Original = row.Table.RecordCache.NewRecord ();
1423 else if (row.Original == row.Current) {
1424 row.Original = row.Table.RecordCache.NewRecord ();
1425 row.Table.RecordCache.CopyRecord (row.Table, row.Current, row.Original);
1428 if (row.Original > 0) {
1429 if (row.Original != row.Current)
1430 row.Table.RecordCache.DisposeRecord (row.Original);
1435 if (HasVersion (DataRowVersion.Current)) {
1436 if (Current == Original) {
1437 if (row.Current >= 0)
1438 row.Table.RecordCache.DisposeRecord (row.Current);
1439 row.Current = row.Original;
1441 if (row.Current < 0)
1442 row.Current = row.Table.RecordCache.NewRecord ();
1445 if (row.Current > 0) {
1446 row.Table.RecordCache.DisposeRecord (row.Current);
1451 if (HasVersion (DataRowVersion.Proposed)) {
1452 if (row.Proposed < 0)
1453 row.Proposed = row.Table.RecordCache.NewRecord ();
1455 if (row.Proposed > 0) {
1456 row.Table.RecordCache.DisposeRecord (row.Proposed);
1461 // copy source record values to target records
1462 foreach (DataColumn column in Table.Columns) {
1463 DataColumn targetColumn = row.Table.Columns [column.ColumnName];
1464 //if a column with the same name exists in both rows copy the values
1465 if (targetColumn != null) {
1466 if (HasVersion (DataRowVersion.Original)) {
1467 object val = column[Original];
1468 row.CheckValue (val, targetColumn, doROCheck);
1469 targetColumn [row.Original] = val;
1472 if (HasVersion (DataRowVersion.Current) && Current != Original) {
1473 object val = column[Current];
1474 row.CheckValue (val, targetColumn, doROCheck);
1475 targetColumn [row.Current] = val;
1478 if (HasVersion (DataRowVersion.Proposed)) {
1479 object val = column[row.Proposed];
1480 row.CheckValue (val, targetColumn, doROCheck);
1481 targetColumn [row.Proposed] = val;
1489 //Merge all values of this DataRow to the row parameter according to merge rules.
1490 internal void MergeValuesToRow (DataRow row, bool preserveChanges)
1493 throw new ArgumentNullException ("row");
1495 throw new ArgumentException ("'row' is the same as this object");
1497 // Original values are anyway copied
1498 if (HasVersion (DataRowVersion.Original)) {
1499 if (row.Original < 0)
1500 row.Original = row.Table.RecordCache.NewRecord ();
1501 else if (row.Original == row.Current && !(Original == Current && !preserveChanges)) {
1502 row.Original = row.Table.RecordCache.NewRecord ();
1503 row.Table.RecordCache.CopyRecord (row.Table, row.Current, row.Original);
1506 if (row.Original == row.Current) { // if target has same current, better create new original
1507 row.Original = row.Table.RecordCache.NewRecord ();
1508 row.Table.RecordCache.CopyRecord (row.Table, row.Current, row.Original);
1512 // if i have current, push all
1513 if (HasVersion (DataRowVersion.Current)) {
1514 if (! preserveChanges && row.Current < 0)
1515 row.Current = row.Table.RecordCache.NewRecord ();
1517 if (row.Current > 0 && ! preserveChanges) {
1518 row.Table.RecordCache.DisposeRecord (row.Current);
1523 // copy source record values to target records
1524 foreach (DataColumn column in Table.Columns) {
1525 DataColumn targetColumn = row.Table.Columns[column.ColumnName];
1526 //if a column with the same name exists in both rows copy the values
1527 if (targetColumn != null) {
1528 if (HasVersion (DataRowVersion.Original)) {
1529 object val = column [Original];
1530 row.CheckValue (val, targetColumn);
1531 targetColumn [row.Original] = val;
1534 if (HasVersion (DataRowVersion.Current) && !preserveChanges) {
1535 object val = column [Current];
1536 row.CheckValue (val, targetColumn);
1537 targetColumn [row.Current] = val;
1546 internal void CopyErrors (DataRow row)
1548 row.RowError = RowError;
1549 DataColumn[] errorColumns = GetColumnsInError();
1550 foreach (DataColumn col in errorColumns) {
1551 DataColumn targetColumn = row.Table.Columns [col.ColumnName];
1552 row.SetColumnError (targetColumn, GetColumnError (col));
1556 internal bool IsRowChanged (DataRowState rowState)
1558 if((RowState & rowState) != 0)
1561 //we need to find if child rows of this row changed.
1562 //if yes - we should return true
1564 // if the rowState is deleted we should get the original version of the row
1565 // else - we should get the current version of the row.
1566 DataRowVersion version = (rowState == DataRowState.Deleted) ? DataRowVersion.Original : DataRowVersion.Current;
1567 int count = Table.ChildRelations.Count;
1568 for (int i = 0; i < count; i++){
1569 DataRelation rel = Table.ChildRelations [i];
1570 DataRow [] childRows = GetChildRows (rel, version);
1571 for (int j = 0; j < childRows.Length; j++){
1572 if (childRows [j].IsRowChanged (rowState))
1580 internal void Validate ()
1582 Table.AddRowToIndexes (this);
1583 AssertConstraints ();
1586 void AssertConstraints ()
1588 if (Table == null || Table._duringDataLoad)
1591 if (Table.DataSet != null && !Table.DataSet.EnforceConstraints)
1593 for (int i = 0; i < Table.Columns.Count; ++i) {
1594 DataColumn column = Table.Columns [i];
1595 if (!column.AllowDBNull && IsNull (column))
1596 throw new NoNullAllowedException (_nullConstraintMessage);
1599 foreach (Constraint constraint in Table.Constraints) {
1601 constraint.AssertConstraint (this);
1602 } catch (Exception e) {
1603 Table.DeleteRowFromIndexes (this);
1609 internal void CheckNullConstraints ()
1611 if (_nullConstraintViolation) {
1612 if (HasVersion (DataRowVersion.Proposed)) {
1613 foreach (DataColumn column in Table.Columns) {
1614 if (IsNull (column) && !column.AllowDBNull)
1615 throw new NoNullAllowedException (_nullConstraintMessage);
1618 _nullConstraintViolation = false;
1622 internal void CheckReadOnlyStatus()
1624 int defaultIdx = IndexFromVersion (DataRowVersion.Default);
1625 foreach(DataColumn column in Table.Columns) {
1626 if ((column.DataContainer.CompareValues (defaultIdx,Proposed) != 0) && column.ReadOnly)
1627 throw new ReadOnlyException ();
1631 #endregion // Methods
1635 /// This method loads a given value into the existing row affecting versions,
1636 /// state based on the LoadOption. The matrix of changes for this method are as
1637 /// mentioned in the DataTable.Load (IDataReader, LoadOption) method.
1639 internal void Load (object [] values, LoadOption loadOption)
1643 if (loadOption == LoadOption.OverwriteChanges ||
1644 (loadOption == LoadOption.PreserveChanges && RowState == DataRowState.Unchanged)) {
1645 Table.ChangingDataRow (this, DataRowAction.ChangeCurrentAndOriginal);
1646 temp = Table.CreateRecord (values);
1647 Table.DeleteRowFromIndexes(this);
1648 if (HasVersion (DataRowVersion.Original) && Current != Original)
1649 Table.RecordCache.DisposeRecord (Original);
1652 if (HasVersion (DataRowVersion.Current))
1653 Table.RecordCache.DisposeRecord (Current);
1655 Table.AddRowToIndexes(this);
1656 Table.ChangedDataRow (this, DataRowAction.ChangeCurrentAndOriginal);
1660 if (loadOption == LoadOption.PreserveChanges) {
1661 Table.ChangingDataRow (this, DataRowAction.ChangeOriginal);
1662 temp = Table.CreateRecord (values);
1663 if (HasVersion (DataRowVersion.Original) && Current != Original)
1664 Table.RecordCache.DisposeRecord (Original);
1666 Table.ChangedDataRow (this, DataRowAction.ChangeOriginal);
1671 if (RowState != DataRowState.Deleted) {
1672 int rindex = HasVersion (DataRowVersion.Proposed) ? Proposed : Current;
1673 temp = Table.CreateRecord (values);
1674 if (RowState == DataRowState.Added || Table.CompareRecords (rindex, temp) != 0) {
1675 Table.ChangingDataRow (this, DataRowAction.Change);
1676 Table.DeleteRowFromIndexes(this);
1677 if (HasVersion (DataRowVersion.Proposed)) {
1678 Table.RecordCache.DisposeRecord (Proposed);
1682 if (Original != Current)
1683 Table.RecordCache.DisposeRecord (Current);
1685 Table.AddRowToIndexes(this);
1686 Table.ChangedDataRow (this, DataRowAction.Change);
1688 Table.ChangingDataRow (this, DataRowAction.Nothing);
1689 Table.RecordCache.DisposeRecord (temp);
1690 Table.ChangedDataRow (this, DataRowAction.Nothing);