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;
20 namespace System.Data {
22 /// Represents a row of data in a DataTable.
29 private DataTable _table;
31 private object[] original;
32 private object[] proposed;
33 private object[] current;
35 private string[] columnErrors;
36 private string rowError;
37 private DataRowState rowState;
38 internal int xmlRowID = 0;
39 private bool editing = false;
46 /// This member supports the .NET Framework infrastructure and is not intended to be
47 /// used directly from your code.
49 protected internal DataRow (DataRowBuilder builder)
51 _table = builder.Table;
54 current = new object[_table.Columns.Count];
55 // initialize to DBNull.Value
56 for (int c = 0; c < _table.Columns.Count; c++) {
57 current[c] = DBNull.Value;
59 proposed = new object[_table.Columns.Count];
60 Array.Copy (current, proposed, _table.Columns.Count);
62 columnErrors = new string[_table.Columns.Count];
63 rowError = String.Empty;
65 //on first creating a DataRow it is always detached.
66 rowState = DataRowState.Detached;
68 foreach (DataColumn Col in _table.Columns) {
70 if (Col.AutoIncrement) {
71 this [Col] = Col.AutoIncrementValue();
75 _table.Columns.CollectionChanged += new System.ComponentModel.CollectionChangeEventHandler(CollectionChanged);
84 /// Gets a value indicating whether there are errors in a row.
86 public bool HasErrors {
89 throw new NotImplementedException ();
94 /// Gets or sets the data stored in the column specified by name.
96 public object this[string columnName] {
97 get { return this[columnName, DataRowVersion.Default]; }
99 int columnIndex = _table.Columns.IndexOf (columnName);
100 if (columnIndex == -1)
101 throw new IndexOutOfRangeException ();
102 this[columnIndex] = value;
107 /// Gets or sets the data stored in specified DataColumn
109 public object this[DataColumn column] {
110 get { return this[column, DataRowVersion.Default]; }
112 int columnIndex = _table.Columns.IndexOf (column);
113 if (columnIndex == -1)
114 throw new ArgumentException ("The column does not belong to this table.");
115 this[columnIndex] = value;
120 /// Gets or sets the data stored in column specified by index.
122 public object this[int columnIndex] {
123 get { return this[columnIndex, DataRowVersion.Default]; }
125 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
126 throw new IndexOutOfRangeException ();
127 if (rowState == DataRowState.Deleted)
128 throw new DeletedRowInaccessibleException ();
129 DataColumn column = _table.Columns[columnIndex];
130 _table.ChangingDataColumn (this, column, value);
132 bool orginalEditing = editing;
133 if (!orginalEditing) BeginEdit ();
134 object v = SetColumnValue (value, columnIndex);
135 proposed[columnIndex] = v;
136 _table.ChangedDataColumn (this, column, v);
137 if (!orginalEditing) EndEdit ();
142 /// Gets the specified version of data stored in the named column.
144 public object this[string columnName, DataRowVersion version] {
146 int columnIndex = _table.Columns.IndexOf (columnName);
147 if (columnIndex == -1)
148 throw new IndexOutOfRangeException ();
149 return this[columnIndex, version];
154 /// Gets the specified version of data stored in the specified DataColumn.
156 public object this[DataColumn column, DataRowVersion version] {
158 int columnIndex = _table.Columns.IndexOf (column);
159 if (columnIndex == -1)
160 throw new ArgumentException ("The column does not belong to this table.");
161 return this[columnIndex, version];
166 /// Gets the data stored in the column, specified by index and version of the data to
169 public object this[int columnIndex, DataRowVersion version] {
171 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
172 throw new IndexOutOfRangeException ();
173 // Non-existent version
174 if (rowState == DataRowState.Detached && version == DataRowVersion.Current || !HasVersion (version))
175 throw new VersionNotFoundException (Locale.GetText ("There is no " + version.ToString () + " data to access."));
176 // Accessing deleted rows
177 if (rowState == DataRowState.Deleted && version != DataRowVersion.Original)
178 throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
180 case DataRowVersion.Default:
181 if (editing || rowState == DataRowState.Detached)
182 return proposed[columnIndex];
183 return current[columnIndex];
184 case DataRowVersion.Proposed:
185 return proposed[columnIndex];
186 case DataRowVersion.Current:
187 return current[columnIndex];
188 case DataRowVersion.Original:
189 return original[columnIndex];
191 throw new ArgumentException ();
197 /// Gets or sets all of the values for this row through an array.
200 public object[] ItemArray {
205 if (value.Length > _table.Columns.Count)
206 throw new ArgumentException ();
208 if (rowState == DataRowState.Deleted)
209 throw new DeletedRowInaccessibleException ();
211 object[] newItems = new object[_table.Columns.Count];
213 for (int i = 0; i < _table.Columns.Count; i++) {
215 if (i < value.Length)
220 newItems[i] = SetColumnValue (v, i);
223 bool orginalEditing = editing;
224 if (!orginalEditing) BeginEdit ();
226 if (!orginalEditing) EndEdit ();
230 private object SetColumnValue (object v, int index)
232 object newval = null;
233 DataColumn col = _table.Columns[index];
235 if (col.ReadOnly && v != this[index])
236 throw new ReadOnlyException ();
239 if(col.DefaultValue != DBNull.Value) {
240 newval = col.DefaultValue;
242 else if(col.AutoIncrement == true) {
243 newval = this [index];
246 if (!col.AllowDBNull)
247 throw new NoNullAllowedException ();
248 newval = DBNull.Value;
251 else if (v == DBNull.Value) {
252 if (!col.AllowDBNull)
253 throw new NoNullAllowedException ();
255 newval = DBNull.Value;
259 Type vType = v.GetType(); // data type of value
260 Type cType = col.DataType; // column data type
261 if (cType != vType) {
262 TypeCode typeCode = Type.GetTypeCode(cType);
264 case TypeCode.Boolean :
265 v = Convert.ToBoolean (v);
268 v = Convert.ToByte (v);
271 v = Convert.ToChar (v);
273 case TypeCode.DateTime :
274 v = Convert.ToDateTime (v);
276 case TypeCode.Decimal :
277 v = Convert.ToDecimal (v);
279 case TypeCode.Double :
280 v = Convert.ToDouble (v);
282 case TypeCode.Int16 :
283 v = Convert.ToInt16 (v);
285 case TypeCode.Int32 :
286 v = Convert.ToInt32 (v);
288 case TypeCode.Int64 :
289 v = Convert.ToInt64 (v);
291 case TypeCode.SByte :
292 v = Convert.ToSByte (v);
294 case TypeCode.Single :
295 v = Convert.ToSingle (v);
297 case TypeCode.String :
298 v = Convert.ToString (v);
300 case TypeCode.UInt16 :
301 v = Convert.ToUInt16 (v);
303 case TypeCode.UInt32 :
304 v = Convert.ToUInt32 (v);
306 case TypeCode.UInt64 :
307 v = Convert.ToUInt64 (v);
310 switch(cType.ToString()) {
311 case "System.TimeSpan" :
312 v = (System.TimeSpan) v;
317 case "System.Object" :
318 //v = (System.Object) v;
321 // FIXME: is exception correct?
322 throw new InvalidCastException("Type not supported.");
329 if(col.AutoIncrement == true) {
330 long inc = Convert.ToInt64(v);
331 col.UpdateAutoIncrementValue (inc);
334 col.DataHasBeenSet = true;
339 /// Gets or sets the custom error description for a row.
341 public string RowError {
342 get { return rowError; }
343 set { rowError = value; }
347 /// Gets the current state of the row in regards to its relationship to the
348 /// DataRowCollection.
350 public DataRowState RowState {
351 get { return rowState; }
354 //FIXME?: Couldn't find a way to set the RowState when adding the DataRow
355 //to a Datatable so I added this method. Delete if there is a better way.
356 internal void AttachRow() {
359 rowState = DataRowState.Added;
362 //FIXME?: Couldn't find a way to set the RowState when removing the DataRow
363 //from a Datatable so I added this method. Delete if there is a better way.
364 internal void DetachRow() {
366 rowState = DataRowState.Detached;
370 /// Gets the DataTable for which this row has a schema.
372 public DataTable Table {
373 get { return _table; }
377 /// Gets and sets index of row. This is used from
380 internal int XmlRowID {
381 get { return xmlRowID; }
382 set { xmlRowID = value; }
390 /// Commits all the changes made to this row since the last time AcceptChanges was
393 public void AcceptChanges ()
395 EndEdit(); // in case it hasn't been called
397 case DataRowState.Added:
398 case DataRowState.Modified:
399 rowState = DataRowState.Unchanged;
401 case DataRowState.Deleted:
402 _table.Rows.Remove (this);
404 case DataRowState.Detached:
405 throw new RowNotInTableException("Cannot perform this operation on a row not in the table.");
407 // Accept from detached
408 if (original == null)
409 original = new object[_table.Columns.Count];
410 Array.Copy (current, original, _table.Columns.Count);
414 /// Begins an edit operation on a DataRow object.
417 public void BeginEdit ()
419 if (rowState == DataRowState.Deleted)
420 throw new DeletedRowInaccessibleException ();
421 if (!HasVersion (DataRowVersion.Proposed)) {
422 proposed = new object[_table.Columns.Count];
423 Array.Copy (current, proposed, current.Length);
425 //TODO: Suspend validation
430 /// Cancels the current edit on the row.
433 public void CancelEdit ()
437 if (HasVersion (DataRowVersion.Proposed)) {
439 if (rowState == DataRowState.Modified)
440 rowState = DataRowState.Unchanged;
445 /// Clears the errors for the row, including the RowError and errors set with
448 public void ClearErrors ()
450 rowError = String.Empty;
451 columnErrors = new String[_table.Columns.Count];
455 /// Deletes the DataRow.
458 public void Delete ()
461 case DataRowState.Added:
462 Table.Rows.Remove (this);
464 case DataRowState.Deleted:
465 throw new DeletedRowInaccessibleException ();
467 //TODO: Events, Constraints
468 rowState = DataRowState.Deleted;
474 /// Ends the edit occurring on the row.
477 public void EndEdit ()
480 if (rowState == DataRowState.Detached)
482 if (HasVersion (DataRowVersion.Proposed))
484 if (rowState == DataRowState.Unchanged)
485 rowState = DataRowState.Modified;
487 //Calling next method validates UniqueConstraints
489 _table.Rows.ValidateDataRowInternal(this);
496 /// Gets the child rows of this DataRow using the specified DataRelation.
498 public DataRow[] GetChildRows (DataRelation relation)
500 return GetChildRows (relation, DataRowVersion.Current);
504 /// Gets the child rows of a DataRow using the specified RelationName of a
507 public DataRow[] GetChildRows (string relationName)
509 return GetChildRows (Table.DataSet.Relations[relationName]);
513 /// Gets the child rows of a DataRow using the specified DataRelation, and
516 public DataRow[] GetChildRows (DataRelation relation, DataRowVersion version)
518 // TODO: Caching for better preformance
519 ArrayList rows = new ArrayList();
520 DataColumn[] parentColumns = relation.ParentColumns;
521 DataColumn[] childColumns = relation.ChildColumns;
522 int numColumn = parentColumns.Length;
523 foreach (DataRow row in relation.ChildTable.Rows) {
524 bool allColumnsMatch = true;
525 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) {
526 if (!this[parentColumns[columnCnt], version].Equals(
527 row[childColumns[columnCnt], version])) {
528 allColumnsMatch = false;
532 if (allColumnsMatch) rows.Add(row);
534 return rows.ToArray(typeof(DataRow)) as DataRow[];
538 /// Gets the child rows of a DataRow using the specified RelationName of a
539 /// DataRelation, and DataRowVersion.
541 public DataRow[] GetChildRows (string relationName, DataRowVersion version)
543 return GetChildRows (Table.DataSet.Relations[relationName], version);
547 /// Gets the error description of the specified DataColumn.
549 public string GetColumnError (DataColumn column)
551 return GetColumnError (_table.Columns.IndexOf(column));
555 /// Gets the error description for the column specified by index.
557 public string GetColumnError (int columnIndex)
559 if (columnIndex < 0 || columnIndex >= columnErrors.Length)
560 throw new IndexOutOfRangeException ();
562 return columnErrors[columnIndex];
566 /// Gets the error description for the column, specified by name.
568 public string GetColumnError (string columnName)
570 return GetColumnError (_table.Columns.IndexOf(columnName));
574 /// Gets an array of columns that have errors.
576 public DataColumn[] GetColumnsInError ()
578 ArrayList dataColumns = new ArrayList ();
580 for (int i = 0; i < columnErrors.Length; i += 1)
582 if (columnErrors[i] != String.Empty)
583 dataColumns.Add (_table.Columns[i]);
586 return (DataColumn[])(dataColumns.ToArray ());
590 /// Gets the parent row of a DataRow using the specified DataRelation.
592 public DataRow GetParentRow (DataRelation relation)
594 return GetParentRow (relation, DataRowVersion.Current);
598 /// Gets the parent row of a DataRow using the specified RelationName of a
601 public DataRow GetParentRow (string relationName)
603 return GetParentRow (relationName, DataRowVersion.Current);
607 /// Gets the parent row of a DataRow using the specified DataRelation, and
610 public DataRow GetParentRow (DataRelation relation, DataRowVersion version)
612 DataRow[] rows = GetParentRows(relation, version);
613 if (rows.Length == 0) return null;
618 /// Gets the parent row of a DataRow using the specified RelationName of a
619 /// DataRelation, and DataRowVersion.
621 public DataRow GetParentRow (string relationName, DataRowVersion version)
623 return GetParentRow (Table.DataSet.Relations[relationName], version);
627 /// Gets the parent rows of a DataRow using the specified DataRelation.
629 public DataRow[] GetParentRows (DataRelation relation)
631 return GetParentRows (relation, DataRowVersion.Current);
635 /// Gets the parent rows of a DataRow using the specified RelationName of a
638 public DataRow[] GetParentRows (string relationName)
640 return GetParentRows (relationName, DataRowVersion.Current);
644 /// Gets the parent rows of a DataRow using the specified DataRelation, and
647 public DataRow[] GetParentRows (DataRelation relation, DataRowVersion version)
649 // TODO: Caching for better preformance
650 ArrayList rows = new ArrayList();
651 DataColumn[] parentColumns = relation.ParentColumns;
652 DataColumn[] childColumns = relation.ChildColumns;
653 int numColumn = parentColumns.Length;
654 foreach (DataRow row in relation.ParentTable.Rows) {
655 bool allColumnsMatch = true;
656 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) {
657 if (!this[parentColumns[columnCnt], version].Equals(
658 row[childColumns[columnCnt], version])) {
659 allColumnsMatch = false;
663 if (allColumnsMatch) rows.Add(row);
665 return rows.ToArray(typeof(DataRow)) as DataRow[];
669 /// Gets the parent rows of a DataRow using the specified RelationName of a
670 /// DataRelation, and DataRowVersion.
672 public DataRow[] GetParentRows (string relationName, DataRowVersion version)
674 return GetParentRows (Table.DataSet.Relations[relationName], version);
678 /// Gets a value indicating whether a specified version exists.
680 public bool HasVersion (DataRowVersion version)
684 case DataRowVersion.Default:
686 case DataRowVersion.Proposed:
687 return (proposed != null);
688 case DataRowVersion.Current:
689 return (current != null);
690 case DataRowVersion.Original:
691 return (original != null);
697 /// Gets a value indicating whether the specified DataColumn contains a null value.
699 public bool IsNull (DataColumn column)
701 object o = this[column];
702 return (o == null || o == DBNull.Value);
706 /// Gets a value indicating whether the column at the specified index contains a null
709 public bool IsNull (int columnIndex)
711 object o = this[columnIndex];
712 return (o == null || o == DBNull.Value);
716 /// Gets a value indicating whether the named column contains a null value.
718 public bool IsNull (string columnName)
720 object o = this[columnName];
721 return (o == null || o == DBNull.Value);
725 /// Gets a value indicating whether the specified DataColumn and DataRowVersion
726 /// contains a null value.
728 public bool IsNull (DataColumn column, DataRowVersion version)
730 object o = this[column, version];
731 return (o == null || o == DBNull.Value);
735 /// Rejects all changes made to the row since AcceptChanges was last called.
737 public void RejectChanges ()
739 // If original is null, then nothing has happened since AcceptChanges
740 // was last called. We have no "original" to go back to.
741 if (original != null)
743 Array.Copy (original, current, _table.Columns.Count);
745 _table.ChangedDataRow (this, DataRowAction.Rollback);
749 case DataRowState.Added:
750 _table.Rows.Remove (this);
752 case DataRowState.Modified:
753 rowState = DataRowState.Unchanged;
755 case DataRowState.Deleted:
756 rowState = DataRowState.Unchanged;
762 // If rows are just loaded via Xml the original values are null.
763 // So in this case we have to remove all columns.
764 // FIXME: I'm not realy sure, does this break something else, but
767 if ((rowState & DataRowState.Added) > 0)
768 _table.Rows.Remove (this);
773 /// Sets the error description for a column specified as a DataColumn.
775 public void SetColumnError (DataColumn column, string error)
777 SetColumnError (_table.Columns.IndexOf (column), error);
781 /// Sets the error description for a column specified by index.
783 public void SetColumnError (int columnIndex, string error)
785 if (columnIndex < 0 || columnIndex >= columnErrors.Length)
786 throw new IndexOutOfRangeException ();
787 columnErrors[columnIndex] = error;
791 /// Sets the error description for a column specified by name.
793 public void SetColumnError (string columnName, string error)
795 SetColumnError (_table.Columns.IndexOf (columnName), error);
799 /// Sets the value of the specified DataColumn to a null value.
801 protected void SetNull (DataColumn column)
803 this[column] = DBNull.Value;
807 /// Sets the parent row of a DataRow with specified new parent DataRow.
810 public void SetParentRow (DataRow parentRow)
812 throw new NotImplementedException ();
816 /// Sets the parent row of a DataRow with specified new parent DataRow and
820 public void SetParentRow (DataRow parentRow, DataRelation relation)
822 throw new NotImplementedException ();
825 //Copy all values of this DataaRow to the row parameter.
826 internal void CopyValuesToRow(DataRow row)
829 throw new ArgumentNullException("row");
831 throw new ArgumentException("'row' is the same as this object");
833 DataColumnCollection columns = Table.Columns;
835 for(int i = 0; i < columns.Count; i++){
836 string columnName = columns[i].ColumnName;
837 int index = row.Table.Columns.IndexOf(columnName);
838 //if a column withe the same name exists in bote rows copy the values
840 if (HasVersion(DataRowVersion.Original))
842 if (row.original == null)
843 row.original = new object[row.Table.Columns.Count];
844 row.original[index] = row.SetColumnValue(original[i], index);
846 if (HasVersion(DataRowVersion.Current))
848 if (row.current == null)
849 row.current = new object[row.Table.Columns.Count];
850 row.current[index] = row.SetColumnValue(current[i], index);
852 if (HasVersion(DataRowVersion.Proposed))
854 if (row.proposed == null)
855 row.proposed = new object[row.Table.Columns.Count];
856 row.proposed[index] = row.SetColumnValue(proposed[i], index);
861 row.rowState = RowState;
862 row.RowError = RowError;
863 row.columnErrors = columnErrors;
867 public void CollectionChanged(object sender, System.ComponentModel.CollectionChangeEventArgs args)
869 // if a column is added we hava to add an additional value the
870 // the priginal, current and propoed arrays.
871 // this scenario can happened in merge operation.
873 if (args.Action == System.ComponentModel.CollectionChangeAction.Add)
875 object[] tmp = new object[current.Length + 1];
876 Array.Copy (current, tmp, current.Length);
877 tmp[tmp.Length - 1] = DBNull.Value;
880 if (proposed != null)
882 tmp = new object[proposed.Length + 1];
883 Array.Copy (proposed, tmp, proposed.Length);
888 tmp = new object[original.Length + 1];
889 Array.Copy (original, tmp, original.Length);
896 internal bool IsRowChanged(DataRowState rowState) {
897 if((RowState & rowState) != 0)
900 //we need to find if child rows of this row changed.
901 //if yes - we should return true
903 // if the rowState is deleted we should get the original version of the row
904 // else - we should get the current version of the row.
905 DataRowVersion version = (rowState == DataRowState.Deleted) ? DataRowVersion.Original : DataRowVersion.Current;
906 int count = Table.ChildRelations.Count;
907 for (int i = 0; i < count; i++){
908 DataRelation rel = Table.ChildRelations[i];
909 DataRow[] childRows = GetChildRows(rel, version);
910 for (int j = 0; j < childRows.Length; j++){
911 if (childRows[j].IsRowChanged(rowState))
919 #endregion // Methods