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 using System.Collections;
18 using System.Globalization;
21 namespace System.Data {
23 /// Represents a row of data in a DataTable.
30 private DataTable _table;
32 private object[] original;
33 private object[] proposed;
34 private object[] current;
36 private string[] columnErrors;
37 private string rowError;
38 private DataRowState rowState;
39 internal int xmlRowID = 0;
40 internal bool _nullConstraintViolation;
41 private string _nullConstraintMessage;
42 private bool editing = false;
43 private bool _hasParentCollection;
44 private bool _inChangingEvent;
46 internal bool _inExpressionEvaluation = false;
48 private XmlDataDocument.XmlDataElement mappedElement;
55 /// This member supports the .NET Framework infrastructure and is not intended to be
56 /// used directly from your code.
58 protected internal DataRow (DataRowBuilder builder)
60 _table = builder.Table;
61 // Get the row id from the builder.
62 _rowId = builder._rowId;
66 proposed = new object[_table.Columns.Count];
67 // Initialise the data coloumns of the row with the dafault values, if any
68 for (int c = 0; c < _table.Columns.Count; c++)
70 if(_table.Columns [c].DefaultValue == null)
71 proposed[c] = DBNull.Value;
73 proposed [c] = _table.Columns[c].DefaultValue;
76 columnErrors = new string[_table.Columns.Count];
77 rowError = String.Empty;
79 //on first creating a DataRow it is always detached.
80 rowState = DataRowState.Detached;
82 ArrayList aiColumns = _table.Columns.AutoIncrmentColumns;
83 foreach (string col in aiColumns) {
84 DataColumn dc = _table.Columns[col];
85 this [dc] = dc.AutoIncrementValue();
87 _table.Columns.CollectionChanged += new System.ComponentModel.CollectionChangeEventHandler(CollectionChanged);
89 // create mapped XmlDataElement
90 DataSet ds = _table.DataSet;
91 if (ds != null && ds._xmlDataDocument != null)
92 mappedElement = new XmlDataDocument.XmlDataElement (this, _table.Prefix, _table.TableName, _table.Namespace, ds._xmlDataDocument);
101 /// Gets a value indicating whether there are errors in a row.
103 public bool HasErrors {
105 if (RowError != string.Empty)
108 for (int i= 0; i < columnErrors.Length; i++){
109 if (columnErrors[i] != null && columnErrors[i] != string.Empty)
118 /// Gets or sets the data stored in the column specified by name.
120 public object this[string columnName] {
121 get { return this[columnName, DataRowVersion.Default]; }
123 int columnIndex = _table.Columns.IndexOf (columnName);
124 if (columnIndex == -1)
125 throw new IndexOutOfRangeException ();
126 this[columnIndex] = value;
131 /// Gets or sets the data stored in specified DataColumn
133 public object this[DataColumn column] {
136 return this[column, DataRowVersion.Default];}
138 int columnIndex = _table.Columns.IndexOf (column);
139 if (columnIndex == -1)
140 throw new ArgumentException ("The column does not belong to this table.");
141 this[columnIndex] = value;
146 /// Gets or sets the data stored in column specified by index.
148 public object this[int columnIndex] {
149 get { return this[columnIndex, DataRowVersion.Default]; }
151 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
152 throw new IndexOutOfRangeException ();
153 if (rowState == DataRowState.Deleted)
154 throw new DeletedRowInaccessibleException ();
155 DataColumn column = _table.Columns[columnIndex];
156 _table.ChangingDataColumn (this, column, value);
159 bool orginalEditing = editing;
160 if (!orginalEditing) BeginEdit ();
161 object v = SetColumnValue (value, column, columnIndex);
162 proposed[columnIndex] = v;
163 _table.ChangedDataColumn (this, column, v);
164 if (!orginalEditing) EndEdit ();
169 /// Gets the specified version of data stored in the named column.
171 public object this[string columnName, DataRowVersion version] {
173 int columnIndex = _table.Columns.IndexOf (columnName);
174 if (columnIndex == -1)
175 throw new IndexOutOfRangeException ();
176 return this[columnIndex, version];
181 /// Gets the specified version of data stored in the specified DataColumn.
183 public object this[DataColumn column, DataRowVersion version] {
185 if (column.Table != Table)
186 throw new ArgumentException ("The column does not belong to this table.");
187 int columnIndex = column.Ordinal;
188 return this[columnIndex, version];
193 /// Gets the data stored in the column, specified by index and version of the data to
196 public object this[int columnIndex, DataRowVersion version] {
198 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
199 throw new IndexOutOfRangeException ();
200 // Accessing deleted rows
201 if (!_inExpressionEvaluation && rowState == DataRowState.Deleted && version != DataRowVersion.Original)
202 throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
204 DataColumn col = _table.Columns[columnIndex];
205 if (col.Expression != String.Empty) {
206 object o = col.CompiledExpression.Eval (this);
207 return Convert.ChangeType (o, col.DataType);
210 if (HasVersion(version))
214 case DataRowVersion.Default:
215 if (editing || rowState == DataRowState.Detached)
216 return proposed[columnIndex];
217 return current[columnIndex];
218 case DataRowVersion.Proposed:
219 return proposed[columnIndex];
220 case DataRowVersion.Current:
221 return current[columnIndex];
222 case DataRowVersion.Original:
223 return original[columnIndex];
225 throw new ArgumentException ();
228 if (rowState == DataRowState.Detached && version == DataRowVersion.Default && proposed == null)
229 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.");
231 throw new VersionNotFoundException (Locale.GetText ("There is no " + version.ToString () + " data to access."));
235 internal void SetOriginalValue (string columnName, object val)
237 int columnIndex = _table.Columns.IndexOf (columnName);
238 DataColumn column = _table.Columns[columnIndex];
239 _table.ChangingDataColumn (this, column, val);
241 if (original == null) original = new object [_table.Columns.Count];
242 val = SetColumnValue (val, column, columnIndex);
243 original[columnIndex] = val;
244 rowState = DataRowState.Modified;
248 /// Gets or sets all of the values for this row through an array.
250 public object[] ItemArray {
253 if (rowState == DataRowState.Detached)
254 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.");
255 // Accessing deleted rows
256 if (rowState == DataRowState.Deleted)
257 throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
262 if (value.Length > _table.Columns.Count)
263 throw new ArgumentException ();
265 if (rowState == DataRowState.Deleted)
266 throw new DeletedRowInaccessibleException ();
268 object[] newItems = new object[_table.Columns.Count];
270 for (int i = 0; i < _table.Columns.Count; i++) {
272 if (i < value.Length)
277 newItems[i] = SetColumnValue (v, _table.Columns[i], i);
280 bool orginalEditing = editing;
281 if (!orginalEditing) BeginEdit ();
283 if (!orginalEditing) EndEdit ();
287 private object SetColumnValue (object v, DataColumn col, int index)
289 object newval = null;
291 if (_hasParentCollection && col.ReadOnly && v != this[index])
292 throw new ReadOnlyException ();
296 if (col.DataType.ToString().Equals("System.Guid"))
297 throw new ArgumentException("Cannot set column to be null, Please use DBNull instead");
299 if(col.DefaultValue != DBNull.Value)
301 newval = col.DefaultValue;
303 else if(col.AutoIncrement == true && CanAccess(index,DataRowVersion.Default))
305 // AutoIncrement column is already filled,
306 // so it just need to return existing value.
307 newval = this [index];
311 if (!col.AllowDBNull)
313 //Constraint violations during data load is raise in DataTable EndLoad
314 this._nullConstraintViolation = true;
315 if (this.Table._duringDataLoad) {
316 this.Table._nullConstraintViolationDuringDataLoad = true;
318 _nullConstraintMessage = "Column '" + col.ColumnName + "' does not allow nulls.";
321 newval = DBNull.Value;
324 else if (v == DBNull.Value)
327 if (!col.AllowDBNull)
329 //Constraint violations during data load is raise in DataTable EndLoad
330 this._nullConstraintViolation = true;
331 if (this.Table._duringDataLoad) {
332 this.Table._nullConstraintViolationDuringDataLoad = true;
334 _nullConstraintMessage = "Column '" + col.ColumnName + "' does not allow nulls.";
337 newval = DBNull.Value;
341 Type vType = v.GetType(); // data type of value
342 Type cType = col.DataType; // column data type
345 TypeCode typeCode = Type.GetTypeCode(cType);
347 case TypeCode.Boolean :
348 v = Convert.ToBoolean (v);
351 v = Convert.ToByte (v);
354 v = Convert.ToChar (v);
356 case TypeCode.DateTime :
357 v = Convert.ToDateTime (v);
359 case TypeCode.Decimal :
360 v = Convert.ToDecimal (v);
362 case TypeCode.Double :
363 v = Convert.ToDouble (v);
365 case TypeCode.Int16 :
366 v = Convert.ToInt16 (v);
368 case TypeCode.Int32 :
369 v = Convert.ToInt32 (v);
371 case TypeCode.Int64 :
372 v = Convert.ToInt64 (v);
374 case TypeCode.SByte :
375 v = Convert.ToSByte (v);
377 case TypeCode.Single :
378 v = Convert.ToSingle (v);
380 case TypeCode.String :
381 v = Convert.ToString (v);
383 case TypeCode.UInt16 :
384 v = Convert.ToUInt16 (v);
386 case TypeCode.UInt32 :
387 v = Convert.ToUInt32 (v);
389 case TypeCode.UInt64 :
390 v = Convert.ToUInt64 (v);
394 switch(cType.ToString()) {
395 case "System.TimeSpan" :
396 v = (System.TimeSpan) v;
401 case "System.Object" :
402 //v = (System.Object) v;
406 throw new InvalidCastException("Type not supported.");
414 // The MaxLength property is ignored for non-text columns
415 if ((Type.GetTypeCode(vType) == TypeCode.String) && (col.MaxLength != -1) &&
416 (this.Table.Columns[index].MaxLength < ((string)v).Length)) {
417 throw new ArgumentException("Cannot set column '" + col.ColumnName + "' to '" + v + "'. The value violates the MaxLength limit of this column.");
420 if(col.AutoIncrement == true) {
421 long inc = Convert.ToInt64(v);
422 col.UpdateAutoIncrementValue (inc);
425 col.DataHasBeenSet = true;
430 /// Gets or sets the custom error description for a row.
432 public string RowError {
433 get { return rowError; }
434 set { rowError = value; }
438 /// Gets the current state of the row in regards to its relationship to the
439 /// DataRowCollection.
441 public DataRowState RowState {
442 get { return rowState; }
445 //FIXME?: Couldn't find a way to set the RowState when adding the DataRow
446 //to a Datatable so I added this method. Delete if there is a better way.
447 internal void AttachRow() {
450 rowState = DataRowState.Added;
453 //FIXME?: Couldn't find a way to set the RowState when removing the DataRow
454 //from a Datatable so I added this method. Delete if there is a better way.
455 internal void DetachRow() {
458 _hasParentCollection = false;
459 rowState = DataRowState.Detached;
463 /// Gets the DataTable for which this row has a schema.
465 public DataTable Table {
466 get { return _table; }
469 internal XmlDataDocument.XmlDataElement DataElement {
470 get { return mappedElement; }
471 set { mappedElement = value; }
475 /// Gets and sets index of row. This is used from
478 internal int XmlRowID {
479 get { return xmlRowID; }
480 set { xmlRowID = value; }
484 /// Gets and sets index of row.
487 get { return _rowId; }
488 set { _rowId = value; }
496 /// Commits all the changes made to this row since the last time AcceptChanges was
499 public void AcceptChanges ()
501 EndEdit(); // in case it hasn't been called
503 case DataRowState.Added:
504 case DataRowState.Modified:
505 rowState = DataRowState.Unchanged;
507 case DataRowState.Deleted:
508 _table.Rows.RemoveInternal (this);
511 case DataRowState.Detached:
512 throw new RowNotInTableException("Cannot perform this operation on a row not in the table.");
514 // Accept from detached
515 if (original == null)
516 original = new object[_table.Columns.Count];
517 Array.Copy (current, original, _table.Columns.Count);
521 /// Begins an edit operation on a DataRow object.
523 public void BeginEdit ()
526 if (_inChangingEvent)
527 throw new InRowChangingEventException("Cannot call BeginEdit inside an OnRowChanging event.");
528 if (rowState == DataRowState.Deleted)
529 throw new DeletedRowInaccessibleException ();
530 if (!HasVersion (DataRowVersion.Proposed)) {
531 proposed = new object[_table.Columns.Count];
532 Array.Copy (current, proposed, current.Length);
534 // setting editing to true stops validations on the row
539 /// Cancels the current edit on the row.
541 public void CancelEdit ()
543 if (_inChangingEvent)
544 throw new InRowChangingEventException("Cannot call CancelEdit inside an OnRowChanging event.");
546 if (HasVersion (DataRowVersion.Proposed)) {
548 if (rowState == DataRowState.Modified)
549 rowState = DataRowState.Unchanged;
554 /// Clears the errors for the row, including the RowError and errors set with
557 public void ClearErrors ()
559 rowError = String.Empty;
560 columnErrors = new String[_table.Columns.Count];
564 /// Deletes the DataRow.
566 public void Delete ()
568 _table.DeletingDataRow(this, DataRowAction.Delete);
570 case DataRowState.Added:
571 // check what to do with child rows
572 CheckChildRows(DataRowAction.Delete);
573 _table.DeleteRowFromIndexes (this);
574 Table.Rows.RemoveInternal (this);
576 // if row was in Added state we move it to Detached.
579 case DataRowState.Deleted:
582 // check what to do with child rows
583 CheckChildRows(DataRowAction.Delete);
584 _table.DeleteRowFromIndexes (this);
585 rowState = DataRowState.Deleted;
588 _table.DeletedDataRow(this, DataRowAction.Delete);
591 // check the child rows of this row before deleting the row.
592 private void CheckChildRows(DataRowAction action)
595 // in this method we find the row that this row is in a relation with them.
596 // in shortly we find all child rows of this row.
597 // then we function according to the DeleteRule of the foriegnkey.
600 // 1. find if this row is attached to dataset.
601 // 2. find if EnforceConstraints is true.
602 // 3. find if there are any constraint on the table that the row is in.
603 if (_table.DataSet != null && _table.DataSet.EnforceConstraints && _table.Constraints.Count > 0)
605 foreach (DataTable table in _table.DataSet.Tables)
607 // loop on all ForeignKeyConstrain of the table.
608 foreach (ForeignKeyConstraint fk in table.Constraints.ForeignKeyConstraints)
610 if (fk.RelatedTable == _table)
613 if (action == DataRowAction.Delete)
614 rule = fk.DeleteRule;
616 rule = fk.UpdateRule;
617 CheckChildRows(fk, action, rule);
624 private void CheckChildRows(ForeignKeyConstraint fkc, DataRowAction action, Rule rule)
626 DataRow[] childRows = GetChildRows(fkc, DataRowVersion.Default);
629 case Rule.Cascade: // delete or change all relted rows.
630 if (childRows != null)
632 for (int j = 0; j < childRows.Length; j++)
634 // if action is delete we delete all child rows
635 if (action == DataRowAction.Delete)
637 if (childRows[j].RowState != DataRowState.Deleted)
638 childRows[j].Delete();
640 // if action is change we change the values in the child row
641 else if (action == DataRowAction.Change)
643 // change only the values in the key columns
644 // set the childcolumn value to the new parent row value
645 for (int k = 0; k < fkc.Columns.Length; k++)
646 childRows[j][fkc.Columns[k]] = this[fkc.RelatedColumns[k], DataRowVersion.Proposed];
651 case Rule.None: // throw an exception if there are any child rows.
652 if (childRows != null)
654 for (int j = 0; j < childRows.Length; j++)
656 if (childRows[j].RowState != DataRowState.Deleted)
658 string changeStr = "Cannot change this row because constraints are enforced on relation " + fkc.ConstraintName +", and changing this row will strand child rows.";
659 string delStr = "Cannot delete this row because constraints are enforced on relation " + fkc.ConstraintName +", and deleting this row will strand child rows.";
660 string message = action == DataRowAction.Delete ? delStr : changeStr;
661 throw new InvalidConstraintException(message);
666 case Rule.SetDefault: // set the values in the child rows to the defult value of the columns.
667 if (childRows != null)
669 for (int j = 0; j < childRows.Length; j++)
671 DataRow child = childRows[j];
672 if (childRows[j].RowState != DataRowState.Deleted)
674 //set only the key columns to default
675 for (int k = 0; k < fkc.Columns.Length; k++)
676 child[fkc.Columns[k]] = fkc.Columns[k].DefaultValue;
681 case Rule.SetNull: // set the values in the child row to null.
682 if (childRows != null)
684 for (int j = 0; j < childRows.Length; j++)
686 DataRow child = childRows[j];
687 if (childRows[j].RowState != DataRowState.Deleted)
689 // set only the key columns to DBNull
690 for (int k = 0; k < fkc.Columns.Length; k++)
691 child.SetNull(fkc.Columns[k]);
701 /// Ends the edit occurring on the row.
703 public void EndEdit ()
705 if (_inChangingEvent)
706 throw new InRowChangingEventException("Cannot call EndEdit inside an OnRowChanging event.");
707 if (rowState == DataRowState.Detached)
713 CheckReadOnlyStatus();
714 if (HasVersion (DataRowVersion.Proposed))
716 _inChangingEvent = true;
719 _table.ChangingDataRow(this, DataRowAction.Change);
723 _inChangingEvent = false;
725 if (rowState == DataRowState.Unchanged)
726 rowState = DataRowState.Modified;
728 //Calling next method validates UniqueConstraints
732 if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
733 _table.Rows.ValidateDataRowInternal(this);
742 // Now we are going to check all child rows of current row.
743 // In the case the cascade is true the child rows will look up for
744 // parent row. since lookup in index is always on current,
745 // we have to move proposed version of current row to current
746 // in the case of check child row failure we are rolling
747 // current row state back.
748 object[] backup = current;
750 bool editing_backup = editing;
753 // check all child rows.
754 CheckChildRows(DataRowAction.Change);
757 catch (Exception ex) {
758 // if check child rows failed - rollback to previous state
759 // i.e. restore proposed and current versions
762 editing = editing_backup;
763 // since we failed - propagate an exception
766 _table.ChangedDataRow(this, DataRowAction.Change);
771 /// Gets the child rows of this DataRow using the specified DataRelation.
773 public DataRow[] GetChildRows (DataRelation relation)
775 return GetChildRows (relation, DataRowVersion.Current);
779 /// Gets the child rows of a DataRow using the specified RelationName of a
782 public DataRow[] GetChildRows (string relationName)
784 return GetChildRows (Table.DataSet.Relations[relationName]);
788 /// Gets the child rows of a DataRow using the specified DataRelation, and
791 public DataRow[] GetChildRows (DataRelation relation, DataRowVersion version)
793 if (relation == null)
794 return new DataRow[0];
796 if (this.Table == null || RowState == DataRowState.Detached)
797 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.");
799 if (relation.DataSet != this.Table.DataSet)
800 throw new ArgumentException();
802 if (relation.ChildKeyConstraint != null)
803 return GetChildRows (relation.ChildKeyConstraint, version);
805 ArrayList rows = new ArrayList();
806 DataColumn[] parentColumns = relation.ParentColumns;
807 DataColumn[] childColumns = relation.ChildColumns;
808 int numColumn = parentColumns.Length;
809 if (HasVersion(version))
811 object[] vals = new object[parentColumns.Length];
812 for (int i = 0; i < vals.Length; i++)
813 vals[i] = this[parentColumns[i], version];
815 foreach (DataRow row in relation.ChildTable.Rows)
817 bool allColumnsMatch = false;
818 if (row.HasVersion(DataRowVersion.Default))
820 allColumnsMatch = true;
821 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt)
823 if (!vals[columnCnt].Equals(
824 row[childColumns[columnCnt], DataRowVersion.Default]))
826 allColumnsMatch = false;
831 if (allColumnsMatch) rows.Add(row);
834 DataRow[] result = relation.ChildTable.NewRowArray(rows.Count);
835 rows.CopyTo(result, 0);
840 /// Gets the child rows of a DataRow using the specified RelationName of a
841 /// DataRelation, and DataRowVersion.
843 public DataRow[] GetChildRows (string relationName, DataRowVersion version)
845 return GetChildRows (Table.DataSet.Relations[relationName], version);
848 private DataRow[] GetChildRows (ForeignKeyConstraint fkc, DataRowVersion version)
850 ArrayList rows = new ArrayList();
851 DataColumn[] parentColumns = fkc.RelatedColumns;
852 DataColumn[] childColumns = fkc.Columns;
853 int numColumn = parentColumns.Length;
854 if (HasVersion(version))
856 object[] vals = new object[parentColumns.Length];
857 for (int i = 0; i < vals.Length; i++)
858 vals[i] = this[parentColumns[i], version];
860 Index index = fkc.Index;
862 // get the child rows from the index
863 Node[] childNodes = index.FindAllSimple (vals);
864 for (int i = 0; i < childNodes.Length; i++) {
865 rows.Add (childNodes[i].Row);
868 else { // if there is no index we search manualy.
869 foreach (DataRow row in fkc.Table.Rows)
871 bool allColumnsMatch = false;
872 if (row.HasVersion(DataRowVersion.Default))
874 allColumnsMatch = true;
875 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt)
877 if (!vals[columnCnt].Equals(
878 row[childColumns[columnCnt], DataRowVersion.Default]))
880 allColumnsMatch = false;
885 if (allColumnsMatch) rows.Add(row);
890 DataRow[] result = fkc.Table.NewRowArray(rows.Count);
891 rows.CopyTo(result, 0);
896 /// Gets the error description of the specified DataColumn.
898 public string GetColumnError (DataColumn column)
900 return GetColumnError (_table.Columns.IndexOf(column));
904 /// Gets the error description for the column specified by index.
906 public string GetColumnError (int columnIndex)
908 if (columnIndex < 0 || columnIndex >= columnErrors.Length)
909 throw new IndexOutOfRangeException ();
911 string retVal = columnErrors[columnIndex];
913 retVal = string.Empty;
918 /// Gets the error description for the column, specified by name.
920 public string GetColumnError (string columnName)
922 return GetColumnError (_table.Columns.IndexOf(columnName));
926 /// Gets an array of columns that have errors.
928 public DataColumn[] GetColumnsInError ()
930 ArrayList dataColumns = new ArrayList ();
932 for (int i = 0; i < columnErrors.Length; i += 1)
934 if (columnErrors[i] != null && columnErrors[i] != String.Empty)
935 dataColumns.Add (_table.Columns[i]);
938 return (DataColumn[])(dataColumns.ToArray (typeof(DataColumn)));
942 /// Gets the parent row of a DataRow using the specified DataRelation.
944 public DataRow GetParentRow (DataRelation relation)
946 return GetParentRow (relation, DataRowVersion.Current);
950 /// Gets the parent row of a DataRow using the specified RelationName of a
953 public DataRow GetParentRow (string relationName)
955 return GetParentRow (relationName, DataRowVersion.Current);
959 /// Gets the parent row of a DataRow using the specified DataRelation, and
962 public DataRow GetParentRow (DataRelation relation, DataRowVersion version)
964 DataRow[] rows = GetParentRows(relation, version);
965 if (rows.Length == 0) return null;
970 /// Gets the parent row of a DataRow using the specified RelationName of a
971 /// DataRelation, and DataRowVersion.
973 public DataRow GetParentRow (string relationName, DataRowVersion version)
975 return GetParentRow (Table.DataSet.Relations[relationName], version);
979 /// Gets the parent rows of a DataRow using the specified DataRelation.
981 public DataRow[] GetParentRows (DataRelation relation)
983 return GetParentRows (relation, DataRowVersion.Current);
987 /// Gets the parent rows of a DataRow using the specified RelationName of a
990 public DataRow[] GetParentRows (string relationName)
992 return GetParentRows (relationName, DataRowVersion.Current);
996 /// Gets the parent rows of a DataRow using the specified DataRelation, and
999 public DataRow[] GetParentRows (DataRelation relation, DataRowVersion version)
1001 // TODO: Caching for better preformance
1002 if (relation == null)
1003 return new DataRow[0];
1005 if (this.Table == null || RowState == DataRowState.Detached)
1006 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.");
1008 if (relation.DataSet != this.Table.DataSet)
1009 throw new ArgumentException();
1011 ArrayList rows = new ArrayList();
1012 DataColumn[] parentColumns = relation.ParentColumns;
1013 DataColumn[] childColumns = relation.ChildColumns;
1014 int numColumn = parentColumns.Length;
1015 if (HasVersion(version))
1017 object[] vals = new object[childColumns.Length];
1018 for (int i = 0; i < vals.Length; i++)
1019 vals[i] = this[childColumns[i], version];
1021 Index indx = relation.ParentTable.GetIndexByColumns (parentColumns);
1022 if (indx != null) { // get the child rows from the index
1023 Node[] childNodes = indx.FindAllSimple (vals);
1024 for (int i = 0; i < childNodes.Length; i++) {
1025 rows.Add (childNodes[i].Row);
1028 else { // no index so we have to search manualy.
1029 foreach (DataRow row in relation.ParentTable.Rows)
1031 bool allColumnsMatch = false;
1032 if (row.HasVersion(DataRowVersion.Default))
1034 allColumnsMatch = true;
1035 for (int columnCnt = 0; columnCnt < numColumn; columnCnt++)
1037 if (!this[childColumns[columnCnt], version].Equals(
1038 row[parentColumns[columnCnt], DataRowVersion.Default]))
1040 allColumnsMatch = false;
1045 if (allColumnsMatch) rows.Add(row);
1050 DataRow[] result = relation.ParentTable.NewRowArray(rows.Count);
1051 rows.CopyTo(result, 0);
1056 /// Gets the parent rows of a DataRow using the specified RelationName of a
1057 /// DataRelation, and DataRowVersion.
1059 public DataRow[] GetParentRows (string relationName, DataRowVersion version)
1061 return GetParentRows (Table.DataSet.Relations[relationName], version);
1065 /// Gets a value indicating whether a specified version exists.
1067 public bool HasVersion (DataRowVersion version)
1071 case DataRowVersion.Default:
1072 if (rowState == DataRowState.Deleted && !_inExpressionEvaluation)
1074 if (rowState == DataRowState.Detached)
1075 return proposed != null;
1077 case DataRowVersion.Proposed:
1078 if (rowState == DataRowState.Deleted && !_inExpressionEvaluation)
1080 return (proposed != null);
1081 case DataRowVersion.Current:
1082 if ((rowState == DataRowState.Deleted && !_inExpressionEvaluation) || rowState == DataRowState.Detached)
1084 return (current != null);
1085 case DataRowVersion.Original:
1086 if (rowState == DataRowState.Detached)
1088 return (original != null);
1094 /// Gets a value indicating whether the specified DataColumn contains a null value.
1096 public bool IsNull (DataColumn column)
1098 object o = this[column];
1099 return (o == DBNull.Value);
1103 /// Gets a value indicating whether the column at the specified index contains a null
1106 public bool IsNull (int columnIndex)
1108 object o = this[columnIndex];
1109 return (o == DBNull.Value);
1113 /// Gets a value indicating whether the named column contains a null value.
1115 public bool IsNull (string columnName)
1117 object o = this[columnName];
1118 return (o == DBNull.Value);
1122 /// Gets a value indicating whether the specified DataColumn and DataRowVersion
1123 /// contains a null value.
1125 public bool IsNull (DataColumn column, DataRowVersion version)
1127 object o = this[column, version];
1128 return (o == DBNull.Value);
1132 /// Returns a value indicating whether all of the row columns specified contain a null value.
1134 internal bool IsNullColumns(DataColumn[] columns)
1136 bool allNull = true;
1137 for (int i = 0; i < columns.Length; i++)
1139 if (!IsNull(columns[i]))
1149 /// Rejects all changes made to the row since AcceptChanges was last called.
1151 public void RejectChanges ()
1153 if (RowState == DataRowState.Detached)
1154 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.");
1155 // If original is null, then nothing has happened since AcceptChanges
1156 // was last called. We have no "original" to go back to.
1157 if (original != null)
1159 Array.Copy (original, current, _table.Columns.Count);
1161 _table.ChangedDataRow (this, DataRowAction.Rollback);
1165 case DataRowState.Added:
1166 _table.DeleteRowFromIndexes (this);
1167 _table.Rows.RemoveInternal (this);
1169 case DataRowState.Modified:
1170 if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
1171 _table.Rows.ValidateDataRowInternal(this);
1172 rowState = DataRowState.Unchanged;
1174 case DataRowState.Deleted:
1175 rowState = DataRowState.Unchanged;
1176 if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
1177 _table.Rows.ValidateDataRowInternal(this);
1183 // If rows are just loaded via Xml the original values are null.
1184 // So in this case we have to remove all columns.
1185 // FIXME: I'm not realy sure, does this break something else, but
1188 if ((rowState & DataRowState.Added) > 0)
1190 _table.DeleteRowFromIndexes (this);
1191 _table.Rows.RemoveInternal (this);
1192 // if row was in Added state we move it to Detached.
1199 /// Sets the error description for a column specified as a DataColumn.
1201 public void SetColumnError (DataColumn column, string error)
1203 SetColumnError (_table.Columns.IndexOf (column), error);
1207 /// Sets the error description for a column specified by index.
1209 public void SetColumnError (int columnIndex, string error)
1211 if (columnIndex < 0 || columnIndex >= columnErrors.Length)
1212 throw new IndexOutOfRangeException ();
1213 columnErrors[columnIndex] = error;
1217 /// Sets the error description for a column specified by name.
1219 public void SetColumnError (string columnName, string error)
1221 SetColumnError (_table.Columns.IndexOf (columnName), error);
1225 /// Sets the value of the specified DataColumn to a null value.
1227 protected void SetNull (DataColumn column)
1229 this[column] = DBNull.Value;
1233 /// Sets the parent row of a DataRow with specified new parent DataRow.
1235 public void SetParentRow (DataRow parentRow)
1237 SetParentRow(parentRow, null);
1241 /// Sets the parent row of a DataRow with specified new parent DataRow and
1244 public void SetParentRow (DataRow parentRow, DataRelation relation)
1246 if (_table == null || parentRow.Table == null || RowState == DataRowState.Detached)
1247 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.");
1249 if (parentRow != null && _table.DataSet != parentRow.Table.DataSet)
1250 throw new ArgumentException();
1253 if (relation == null)
1255 foreach (DataRelation parentRel in _table.ParentRelations)
1257 DataColumn[] childCols = parentRel.ChildKeyConstraint.Columns;
1258 DataColumn[] parentCols = parentRel.ChildKeyConstraint.RelatedColumns;
1260 for (int i = 0; i < parentCols.Length; i++)
1262 if (parentRow == null)
1263 this[childCols[i].Ordinal] = DBNull.Value;
1265 this[childCols[i].Ordinal] = parentRow[parentCols[i]];
1272 DataColumn[] childCols = relation.ChildKeyConstraint.Columns;
1273 DataColumn[] parentCols = relation.ChildKeyConstraint.RelatedColumns;
1275 for (int i = 0; i < parentCols.Length; i++)
1277 if (parentRow == null)
1278 this[childCols[i].Ordinal] = DBNull.Value;
1280 this[childCols[i].Ordinal] = parentRow[parentCols[i]];
1286 //Copy all values of this DataaRow to the row parameter.
1287 internal void CopyValuesToRow(DataRow row)
1291 throw new ArgumentNullException("row");
1293 throw new ArgumentException("'row' is the same as this object");
1295 DataColumnCollection columns = Table.Columns;
1297 for(int i = 0; i < columns.Count; i++){
1299 string columnName = columns[i].ColumnName;
1300 DataColumn column = row.Table.Columns[columnName];
1301 //if a column with the same name exists in both rows copy the values
1302 if(column != null) {
1303 int index = column.Ordinal;
1304 if (HasVersion(DataRowVersion.Original))
1306 if (row.original == null)
1307 row.original = new object[row.Table.Columns.Count];
1308 row.original[index] = row.SetColumnValue(original[i], column, index);
1310 if (HasVersion(DataRowVersion.Current))
1312 if (row.current == null)
1313 row.current = new object[row.Table.Columns.Count];
1314 row.current[index] = row.SetColumnValue(current[i], column, index);
1316 if (HasVersion(DataRowVersion.Proposed))
1318 if (row.proposed == null)
1319 row.proposed = new object[row.Table.Columns.Count];
1320 row.proposed[index] = row.SetColumnValue(proposed[i], column, index);
1323 //Saving the current value as the column value
1324 row[index] = row.current[index];
1329 row.rowState = RowState;
1330 row.RowError = RowError;
1331 row.columnErrors = columnErrors;
1335 private void CollectionChanged(object sender, System.ComponentModel.CollectionChangeEventArgs args)
1337 // if a column is added we hava to add an additional value the
1338 // the priginal, current and propoed arrays.
1339 // this scenario can happened in merge operation.
1341 if (args.Action == System.ComponentModel.CollectionChangeAction.Add)
1344 int index = this.Table.Columns.Count - 1;
1345 if (current != null)
1347 tmp = new object [index + 1];
1348 Array.Copy (current, tmp, current.Length);
1349 tmp[tmp.Length - 1] = SetColumnValue(null, this.Table.Columns[index], index);
1352 if (proposed != null)
1354 tmp = new object [index + 1];
1355 Array.Copy (proposed, tmp, proposed.Length);
1356 tmp[tmp.Length - 1] = SetColumnValue(null, this.Table.Columns[index], index);
1359 if(original != null)
1361 tmp = new object [index + 1];
1362 Array.Copy (original, tmp, original.Length);
1363 tmp[tmp.Length - 1] = SetColumnValue(null, this.Table.Columns[index], index);
1370 internal void onColumnRemoved(int columnIndex)
1372 // when column removed we have to compress row values in the way
1373 // they will correspond to new column ordinals
1376 if (current != null)
1378 tmp = new object[current.Length - 1];
1379 // copy values before removed column
1380 if (columnIndex > 0)
1381 Array.Copy (current, 0, tmp, 0, columnIndex);
1382 // copy values after removed column
1383 if(columnIndex < current.Length - 1)
1384 Array.Copy(current, columnIndex + 1, tmp, columnIndex, current.Length - 1 - columnIndex);
1388 if (proposed != null)
1390 tmp = new object[proposed.Length - 1];
1391 // copy values before removed column
1392 if (columnIndex > 0)
1393 Array.Copy (proposed, 0, tmp, 0, columnIndex);
1394 // copy values after removed column
1395 if(columnIndex < proposed.Length - 1)
1396 Array.Copy(proposed, columnIndex + 1, tmp, columnIndex, proposed.Length - 1 - columnIndex);
1400 if (original != null)
1402 tmp = new object[original.Length - 1];
1403 // copy values before removed column
1404 if (columnIndex > 0)
1405 Array.Copy (original, 0, tmp, 0, columnIndex);
1406 // copy values after removed column
1407 if(columnIndex < original.Length - 1)
1408 Array.Copy(original, columnIndex + 1, tmp, columnIndex, original.Length - 1 - columnIndex);
1414 internal bool IsRowChanged(DataRowState rowState) {
1415 if((RowState & rowState) != 0)
1418 //we need to find if child rows of this row changed.
1419 //if yes - we should return true
1421 // if the rowState is deleted we should get the original version of the row
1422 // else - we should get the current version of the row.
1423 DataRowVersion version = (rowState == DataRowState.Deleted) ? DataRowVersion.Original : DataRowVersion.Current;
1424 int count = Table.ChildRelations.Count;
1425 for (int i = 0; i < count; i++){
1426 DataRelation rel = Table.ChildRelations[i];
1427 DataRow[] childRows = GetChildRows(rel, version);
1428 for (int j = 0; j < childRows.Length; j++){
1429 if (childRows[j].IsRowChanged(rowState))
1437 internal bool HasParentCollection
1441 return _hasParentCollection;
1445 _hasParentCollection = value;
1449 // checks existance of value in version pecified
1450 // note : this one relies on the same algorithm as this[int,DataRowVersion]
1451 private bool CanAccess(int columnIndex, DataRowVersion version)
1453 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
1455 // Accessing deleted rows
1456 if (rowState == DataRowState.Deleted && version != DataRowVersion.Original)
1459 if (HasVersion(version))
1461 object[] versionArr;
1464 case DataRowVersion.Default:
1465 if (editing || rowState == DataRowState.Detached) {
1466 versionArr = proposed;
1469 versionArr = current;
1472 case DataRowVersion.Proposed:
1473 versionArr = proposed;
1475 case DataRowVersion.Current:
1476 versionArr = current;
1478 case DataRowVersion.Original:
1479 versionArr = original;
1485 return (versionArr != null && columnIndex < versionArr.Length);
1491 internal void CheckNullConstraints()
1493 if (_nullConstraintViolation) {
1494 if (proposed != null) {
1495 for (int i = 0; i < proposed.Length; i++) {
1496 if (this[i] == DBNull.Value && !_table.Columns[i].AllowDBNull)
1497 throw new NoNullAllowedException(_nullConstraintMessage);
1500 _nullConstraintViolation = false;
1504 internal void CheckReadOnlyStatus()
1506 if (proposed == null)
1509 for (int i = 0; i < proposed.Length; i++) {
1510 if (this[i] != proposed[i] && _table.Columns[i].ReadOnly)
1511 throw new ReadOnlyException();
1516 #endregion // Methods