* DataRow.cs : Throw exceptions if Row is Detached.
[mono.git] / mcs / class / System.Data / System.Data / DataRow.cs
1 //
2 // System.Data.DataRow.cs
3 //
4 // Author:
5 //   Rodrigo Moya <rodrigo@ximian.com>
6 //   Daniel Morgan <danmorg@sc.rr.com>
7 //   Tim Coleman <tim@timcoleman.com>
8 //   Ville Palo <vi64pa@koti.soon.fi>
9 //   Alan Tam Siu Lung <Tam@SiuLung.com>
10 //
11 // (C) Ximian, Inc 2002
12 // (C) Daniel Morgan 2002, 2003
13 // Copyright (C) 2002 Tim Coleman
14 //
15
16 using System;
17 using System.Collections;
18 using System.Globalization;
19
20 namespace System.Data {
21         /// <summary>
22         /// Represents a row of data in a DataTable.
23         /// </summary>
24         [Serializable]
25         public class DataRow
26         {
27                 #region Fields
28
29                 private DataTable _table;
30
31                 private object[] original;
32                 private object[] proposed;
33                 private object[] current;
34
35                 private string[] columnErrors;
36                 private string rowError;
37                 private DataRowState rowState;
38                 internal int xmlRowID = 0;
39                 internal bool _nullConstraintViolation;
40                 private string _nullConstraintMessage;
41                 private bool editing = false;
42                 private bool _hasParentCollection;
43                 private bool _inChangingEvent;
44
45                 #endregion
46
47                 #region Constructors
48
49                 /// <summary>
50                 /// This member supports the .NET Framework infrastructure and is not intended to be 
51                 /// used directly from your code.
52                 /// </summary>
53                 protected internal DataRow (DataRowBuilder builder)
54                 {
55                         _table = builder.Table;
56
57                         original = null; 
58                         
59                         proposed = new object[_table.Columns.Count];
60                         for (int c = 0; c < _table.Columns.Count; c++) 
61                         {
62                                 proposed[c] = DBNull.Value;
63                         }
64                         
65                         columnErrors = new string[_table.Columns.Count];
66                         rowError = String.Empty;
67
68                         //on first creating a DataRow it is always detached.
69                         rowState = DataRowState.Detached;
70                         
71                         foreach (DataColumn Col in _table.Columns) {
72                                 
73                                 if (Col.AutoIncrement) {
74                                         this [Col] = Col.AutoIncrementValue();
75                                 }
76                         }
77                         _table.Columns.CollectionChanged += new System.ComponentModel.CollectionChangeEventHandler(CollectionChanged);
78                 }
79
80                 
81                 #endregion
82
83                 #region Properties
84
85                 /// <summary>
86                 /// Gets a value indicating whether there are errors in a row.
87                 /// </summary>
88                 public bool HasErrors {
89                         [MonoTODO]
90                         get {
91                                 if (RowError != string.Empty)
92                                         return true;
93
94                                 for (int i= 0; i < columnErrors.Length; i++){
95                                         if (columnErrors[i] != null && columnErrors[i] != string.Empty)
96                                                 return true;
97                                 }
98
99                                 return false;
100                         }
101                 }
102
103                 /// <summary>
104                 /// Gets or sets the data stored in the column specified by name.
105                 /// </summary>
106                 public object this[string columnName] {
107                         get { return this[columnName, DataRowVersion.Default]; }
108                         set {
109                                 int columnIndex = _table.Columns.IndexOf (columnName);
110                                 if (columnIndex == -1)
111                                         throw new IndexOutOfRangeException ();
112                                 this[columnIndex] = value;
113                         }
114                 }
115
116                 /// <summary>
117                 /// Gets or sets the data stored in specified DataColumn
118                 /// </summary>
119                 public object this[DataColumn column] {
120
121                         get {
122                                 return this[column, DataRowVersion.Default];} 
123                         set {
124                                 int columnIndex = _table.Columns.IndexOf (column);
125                                 if (columnIndex == -1)
126                                         throw new ArgumentException ("The column does not belong to this table.");
127                                 this[columnIndex] = value;
128                         }
129                 }
130
131                 /// <summary>
132                 /// Gets or sets the data stored in column specified by index.
133                 /// </summary>
134                 public object this[int columnIndex] {
135                         get { return this[columnIndex, DataRowVersion.Default]; }
136                         set {
137                                 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
138                                         throw new IndexOutOfRangeException ();
139                                 if (rowState == DataRowState.Deleted)
140                                         throw new DeletedRowInaccessibleException ();
141                                 DataColumn column = _table.Columns[columnIndex];
142                                 _table.ChangingDataColumn (this, column, value);
143                                 
144                                 
145                                 bool orginalEditing = editing;
146                                 if (!orginalEditing) BeginEdit ();
147                                 object v = SetColumnValue (value, columnIndex);
148                                 proposed[columnIndex] = v;
149                                 _table.ChangedDataColumn (this, column, v);
150                                 if (!orginalEditing) EndEdit ();
151                         }
152                 }
153
154                 /// <summary>
155                 /// Gets the specified version of data stored in the named column.
156                 /// </summary>
157                 public object this[string columnName, DataRowVersion version] {
158                         get {
159                                 int columnIndex = _table.Columns.IndexOf (columnName);
160                                 if (columnIndex == -1)
161                                         throw new IndexOutOfRangeException ();
162                                 return this[columnIndex, version];
163                         }
164                 }
165
166                 /// <summary>
167                 /// Gets the specified version of data stored in the specified DataColumn.
168                 /// </summary>
169                 public object this[DataColumn column, DataRowVersion version] {
170                         get {
171                                 int columnIndex = _table.Columns.IndexOf (column);
172                                 if (columnIndex == -1)
173                                         throw new ArgumentException ("The column does not belong to this table.");
174                                 return this[columnIndex, version];
175                         }
176                 }
177
178                 /// <summary>
179                 /// Gets the data stored in the column, specified by index and version of the data to
180                 /// retrieve.
181                 /// </summary>
182                 public object this[int columnIndex, DataRowVersion version] {
183                         get {
184                                 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
185                                         throw new IndexOutOfRangeException ();
186                                 // Accessing deleted rows
187                                 if (rowState == DataRowState.Deleted && version != DataRowVersion.Original)
188                                         throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
189                                 // Row not in table
190                                 if (rowState == DataRowState.Detached && version == DataRowVersion.Default)
191                                         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.");
192                                 // Non-existent version
193                                 if (!HasVersion (version))
194                                         throw new VersionNotFoundException (Locale.GetText ("There is no " + version.ToString () + " data to access."));
195                                 switch (version) {
196                                 case DataRowVersion.Default:
197                                         if (editing || rowState == DataRowState.Detached)
198                                                 return proposed[columnIndex];
199                                         return current[columnIndex];
200                                 case DataRowVersion.Proposed:
201                                         return proposed[columnIndex];
202                                 case DataRowVersion.Current:
203                                         return current[columnIndex];
204                                 case DataRowVersion.Original:
205                                         return original[columnIndex];
206                                 default:
207                                         throw new ArgumentException ();
208                                 }
209                         }
210                 }
211
212                 /// <summary>
213                 /// Gets or sets all of the values for this row through an array.
214                 /// </summary>
215                 [MonoTODO]
216                 public object[] ItemArray {
217                         get { 
218                                 // row not in table
219                                 if (rowState == DataRowState.Detached)
220                                         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.");
221                                 // Accessing deleted rows
222                                 if (rowState == DataRowState.Deleted)
223                                         throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
224                                 
225                                 return current; 
226                         }
227                         set {
228                                 if (value.Length > _table.Columns.Count)
229                                         throw new ArgumentException ();
230
231                                 if (rowState == DataRowState.Deleted)
232                                         throw new DeletedRowInaccessibleException ();
233                                 
234                                 object[] newItems = new object[_table.Columns.Count];                   
235                                 object v = null;
236                                 for (int i = 0; i < _table.Columns.Count; i++) {
237
238                                         if (i < value.Length)
239                                                 v = value[i];
240                                         else
241                                                 v = null;
242
243                                         newItems[i] = SetColumnValue (v, i);
244                                 }
245
246                                 bool orginalEditing = editing;
247                                 if (!orginalEditing) BeginEdit ();
248                                 proposed = newItems;
249                                 if (!orginalEditing) EndEdit ();
250                         }
251                 }
252
253                 private object SetColumnValue (object v, int index) 
254                 {               
255                         object newval = null;
256                         DataColumn col = _table.Columns[index];
257                         
258                         if (_hasParentCollection && col.ReadOnly && v != this[index])
259                                 throw new ReadOnlyException ();
260
261                         if (v == null)
262                         {
263                                 if(col.DefaultValue != DBNull.Value) 
264                                 {
265                                         newval = col.DefaultValue;
266                                 }
267                                 else if(col.AutoIncrement == true) 
268                                 {
269                                         newval = this [index];
270                                 }
271                                 else 
272                                 {
273                                         if (!col.AllowDBNull)
274                                         {
275                                                 //Constraint violations during data load is raise in DataTable EndLoad
276                                                 this._nullConstraintViolation = true;
277                                                 _nullConstraintMessage = "Column '" + col.ColumnName + "' does not allow nulls.";
278                                         }
279
280                                         newval = DBNull.Value;
281                                 }
282                         }               
283                         else if (v == DBNull.Value) 
284                         {
285                                 
286                                 if (!col.AllowDBNull)
287                                 {
288                                         //Constraint violations during data load is raise in DataTable EndLoad
289                                         this._nullConstraintViolation = true;
290                                         _nullConstraintMessage = "Column '" + col.ColumnName + "' does not allow nulls.";
291                                 }
292                                 
293                                 newval = DBNull.Value;
294                         }
295                         else 
296                         {       
297                                 Type vType = v.GetType(); // data type of value
298                                 Type cType = col.DataType; // column data type
299                                 if (cType != vType) 
300                                 {
301                                         TypeCode typeCode = Type.GetTypeCode(cType);
302                                         switch(typeCode) {
303                                                 case TypeCode.Boolean :
304                                                         v = Convert.ToBoolean (v);
305                                                         break;
306                                                 case TypeCode.Byte  :
307                                                         v = Convert.ToByte (v);
308                                                         break;
309                                                 case TypeCode.Char  :
310                                                         v = Convert.ToChar (v);
311                                                         break;
312                                                 case TypeCode.DateTime  :
313                                                         v = Convert.ToDateTime (v);
314                                                         break;
315                                                 case TypeCode.Decimal  :
316                                                         v = Convert.ToDecimal (v);
317                                                         break;
318                                                 case TypeCode.Double  :
319                                                         v = Convert.ToDouble (v);
320                                                         break;
321                                                 case TypeCode.Int16  :
322                                                         v = Convert.ToInt16 (v);
323                                                         break;
324                                                 case TypeCode.Int32  :
325                                                         v = Convert.ToInt32 (v);
326                                                         break;
327                                                 case TypeCode.Int64  :
328                                                         v = Convert.ToInt64 (v);
329                                                         break;
330                                                 case TypeCode.SByte  :
331                                                         v = Convert.ToSByte (v);
332                                                         break;
333                                                 case TypeCode.Single  :
334                                                         v = Convert.ToSingle (v);
335                                                         break;
336                                                 case TypeCode.String  :
337                                                         v = Convert.ToString (v);
338                                                         break;
339                                                 case TypeCode.UInt16  :
340                                                         v = Convert.ToUInt16 (v);
341                                                         break;
342                                                 case TypeCode.UInt32  :
343                                                         v = Convert.ToUInt32 (v);
344                                                         break;
345                                                 case TypeCode.UInt64  :
346                                                         v = Convert.ToUInt64 (v);
347                                                         break;
348                                                 default :
349                                                                 
350                                                 switch(cType.ToString()) {
351                                                         case "System.TimeSpan" :
352                                                                 v = (System.TimeSpan) v;
353                                                                 break;
354                                                         case "System.Type" :
355                                                                 v = (System.Type) v;
356                                                                 break;
357                                                         case "System.Object" :
358                                                                 //v = (System.Object) v;
359                                                                 break;
360                                                         default:
361                                                                 if (!cType.IsArray)
362                                                                         throw new InvalidCastException("Type not supported.");
363                                                                 break;
364                                                 }
365                                                         break;
366                                         }
367                                         vType = v.GetType();
368                                 }
369                                 newval = v;
370                                 if(col.AutoIncrement == true) {
371                                         long inc = Convert.ToInt64(v);
372                                         col.UpdateAutoIncrementValue (inc);
373                                 }
374                         }
375                         col.DataHasBeenSet = true;
376                         return newval;
377                 }
378
379                 /// <summary>
380                 /// Gets or sets the custom error description for a row.
381                 /// </summary>
382                 public string RowError {
383                         get { return rowError; }
384                         set { rowError = value; }
385                 }
386
387                 /// <summary>
388                 /// Gets the current state of the row in regards to its relationship to the
389                 /// DataRowCollection.
390                 /// </summary>
391                 public DataRowState RowState {
392                         get { return rowState; }
393                 }
394
395                 //FIXME?: Couldn't find a way to set the RowState when adding the DataRow
396                 //to a Datatable so I added this method. Delete if there is a better way.
397                 internal void AttachRow() {
398                         current = proposed;
399                         proposed = null;
400                         rowState = DataRowState.Added;
401                 }
402
403                 //FIXME?: Couldn't find a way to set the RowState when removing the DataRow
404                 //from a Datatable so I added this method. Delete if there is a better way.
405                 internal void DetachRow() {
406                         proposed = null;
407                         _hasParentCollection = false;
408                         rowState = DataRowState.Detached;
409                 }
410
411                 /// <summary>
412                 /// Gets the DataTable for which this row has a schema.
413                 /// </summary>
414                 public DataTable Table {
415                         get { return _table; }
416                 }
417
418                 /// <summary>
419                 /// Gets and sets index of row. This is used from 
420                 /// XmlDataDocument.
421                 // </summary>
422                 internal int XmlRowID {
423                         get { return xmlRowID; }
424                         set { xmlRowID = value; }
425                 }
426
427                 #endregion
428
429                 #region Methods
430
431                 /// <summary>
432                 /// Commits all the changes made to this row since the last time AcceptChanges was
433                 /// called.
434                 /// </summary>
435                 public void AcceptChanges () 
436                 {
437                         EndEdit(); // in case it hasn't been called
438                         switch (rowState) {
439                         case DataRowState.Added:
440                         case DataRowState.Modified:
441                                 rowState = DataRowState.Unchanged;
442                                 break;
443                         case DataRowState.Deleted:
444                                 _table.Rows.RemoveInternal (this);
445                                 DetachRow();
446                                 break;
447                         case DataRowState.Detached:
448                                 throw new RowNotInTableException("Cannot perform this operation on a row not in the table.");
449                         }
450                         // Accept from detached
451                         if (original == null)
452                                 original = new object[_table.Columns.Count];
453                         Array.Copy (current, original, _table.Columns.Count);
454                 }
455
456                 /// <summary>
457                 /// Begins an edit operation on a DataRow object.
458                 /// </summary>
459                 [MonoTODO]
460                 public void BeginEdit () 
461                 {
462                         if (rowState == DataRowState.Deleted)
463                                 throw new DeletedRowInaccessibleException ();
464                         if (!HasVersion (DataRowVersion.Proposed)) {
465                                 proposed = new object[_table.Columns.Count];
466                                 Array.Copy (current, proposed, current.Length);
467                         }
468                         //TODO: Suspend validation
469                         editing = true;
470                 }
471
472                 /// <summary>
473                 /// Cancels the current edit on the row.
474                 /// </summary>
475                 [MonoTODO]
476                 public void CancelEdit () 
477                 {
478                         editing = false;
479                         //TODO: Events
480                         if (HasVersion (DataRowVersion.Proposed)) {
481                                 proposed = null;
482                                 if (rowState == DataRowState.Modified)
483                                     rowState = DataRowState.Unchanged;
484                         }
485                 }
486
487                 /// <summary>
488                 /// Clears the errors for the row, including the RowError and errors set with
489                 /// SetColumnError.
490                 /// </summary>
491                 public void ClearErrors () 
492                 {
493                         rowError = String.Empty;
494                         columnErrors = new String[_table.Columns.Count];
495                 }
496
497                 /// <summary>
498                 /// Deletes the DataRow.
499                 /// </summary>
500                 [MonoTODO]
501                 public void Delete () 
502                 {
503                         _table.DeletingDataRow(this, DataRowAction.Delete);
504                         switch (rowState) {
505                         case DataRowState.Added:
506                                 Table.Rows.RemoveInternal (this);
507                                 break;
508                         case DataRowState.Deleted:
509                                 break;
510                         default:
511                                 // check what to do with child rows
512                                 CheckChildRows(DataRowAction.Delete);
513                                 rowState = DataRowState.Deleted;
514                                 break;
515                         }
516                                 _table.DeletedDataRow(this, DataRowAction.Delete);
517                 }
518
519                 // check the child rows of this row before deleting the row.
520                 private void CheckChildRows(DataRowAction action)
521                 {
522                         
523                         // in this method we find the row that this row is in a reltion with them.
524                         // in shortly we find all child rows of this row.
525                         // then we function according to the DeleteRule of the foriegnkey.
526
527
528                         // 1. find if this row is attached to dataset.
529                         // 2. find if EnforceConstraints is true.
530                         // 3. find if there are any constraint on the table that the row is in.
531                         if (_table.DataSet != null && _table.DataSet.EnforceConstraints && _table.Constraints.Count > 0)
532                         {
533                                 foreach (DataTable table in _table.DataSet.Tables)
534                                 {
535                                         // loop on all constraints of the table.
536                                         ConstraintCollection constraintsCollection = table.Constraints;
537                                         for (int i = 0; i < constraintsCollection.Count; i++)
538                                         {
539                                                 ForeignKeyConstraint fk = null;
540                                                 if (constraintsCollection[i] is ForeignKeyConstraint)
541                                                 {
542                                                         fk = (ForeignKeyConstraint)constraintsCollection[i];
543                                                         if (fk.RelatedTable == _table)
544                                                         {
545                                                                 //we create a dummy relation because we do not want to duplicate code of GetChild().
546                                                                 // we use the dummy relation to find child rows.
547                                                                 DataRelation rel = new DataRelation("dummy", fk.RelatedColumns, fk.Columns, false);
548                                                                 Rule rule;
549                                                                 if (action == DataRowAction.Delete)
550                                                                         rule = fk.DeleteRule;
551                                                                 else
552                                                                         rule = fk.UpdateRule;
553                                                                 CheckChildRows(rel, action, rule);
554                                                         }
555                                                 }                       
556                                         }
557                                 }
558                         }
559                 }
560
561                 private void CheckChildRows(DataRelation rel, DataRowAction action, Rule rule)
562                 {                               
563                         DataRow[] childRows = GetChildRows(rel);
564                         switch (rule)
565                         {
566                                 case Rule.Cascade:  // delete or change all relted rows.
567                                         if (childRows != null)
568                                         {
569                                                 for (int j = 0; j < childRows.Length; j++)
570                                                 {
571                                                         // if action is delete we delete all child rows
572                                                         if (action == DataRowAction.Delete)
573                                                         {
574                                                                 if (childRows[j].RowState != DataRowState.Deleted)
575                                                                         childRows[j].Delete();
576                                                         }
577                                                         // if action is change we change the values in the child row
578                                                         else if (action == DataRowAction.Change)
579                                                         {
580                                                                 // change only the values in the key columns
581                                                                 // set the childcolumn value to the new parent row value
582                                                                 for (int k = 0; k < rel.ChildColumns.Length; k++)
583                                                                         childRows[j][rel.ChildColumns[k]] = this[rel.ParentColumns[k], DataRowVersion.Proposed];
584                                                         }
585                                                 }
586                                         }
587                                         break;
588                                 case Rule.None: // throw an exception if there are any child rows.
589                                         if (childRows != null)
590                                         {
591                                                 for (int j = 0; j < childRows.Length; j++)
592                                                 {
593                                                         if (childRows[j].RowState != DataRowState.Deleted)
594                                                         {
595                                                                 string changeStr = "Cannot change this row because constraints are enforced on relation " + rel.RelationName +", and changing this row will strand child rows.";
596                                                                 string delStr = "Cannot delete this row because constraints are enforced on relation " + rel.RelationName +", and deleting this row will strand child rows.";
597                                                                 string message = action == DataRowAction.Delete ? delStr : changeStr;
598                                                                 throw new InvalidConstraintException(message);
599                                                         }
600                                                 }
601                                         }
602                                         break;
603                                 case Rule.SetDefault: // set the values in the child rows to the defult value of the columns.
604                                         if (childRows != null)
605                                         {
606                                                 for (int j = 0; j < childRows.Length; j++)
607                                                 {
608                                                         DataRow child = childRows[j];
609                                                         if (childRows[j].RowState != DataRowState.Deleted)
610                                                         {
611                                                                 //set only the key columns to default
612                                                                 for (int k = 0; k < rel.ChildColumns.Length; k++)
613                                                                         child[rel.ChildColumns[k]] = rel.ChildColumns[k].DefaultValue;
614                                                         }
615                                                 }
616                                         }
617                                         break;
618                                 case Rule.SetNull: // set the values in the child row to null.
619                                         if (childRows != null)
620                                         {
621                                                 for (int j = 0; j < childRows.Length; j++)
622                                                 {
623                                                         DataRow child = childRows[j];
624                                                         if (childRows[j].RowState != DataRowState.Deleted)
625                                                         {
626                                                                 // set only the key columns to DBNull
627                                                                 for (int k = 0; k < rel.ChildColumns.Length; k++)
628                                                                         child.SetNull(rel.ChildColumns[k]);
629                                                         }
630                                                 }
631                                         }
632                                         break;
633                         }
634
635                 }
636
637                 /// <summary>
638                 /// Ends the edit occurring on the row.
639                 /// </summary>
640                 [MonoTODO]
641                 public void EndEdit () 
642                 {
643                         if (_inChangingEvent)
644                                 throw new InRowChangingEventException("Cannot call EndEdit inside an OnRowChanging event.");
645                         if (rowState == DataRowState.Detached)
646                         {
647                                 editing = false;
648                                 return;
649                         }
650
651                         if (HasVersion (DataRowVersion.Proposed))
652                         {
653                                 _inChangingEvent = true;
654                                 try
655                                 {
656                                         _table.ChangingDataRow(this, DataRowAction.Change);
657                                 }
658                                 finally
659                                 {
660                                         _inChangingEvent = false;
661                                 }
662                                 if (rowState == DataRowState.Unchanged)
663                                         rowState = DataRowState.Modified;
664                                 
665                                 //Calling next method validates UniqueConstraints
666                                 //and ForeignKeys.
667                                 try
668                                 {
669                                         if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
670                                                 _table.Rows.ValidateDataRowInternal(this);
671                                 }
672                                 catch (Exception e)
673                                 {
674                                         editing = false;
675                                         proposed = null;
676                                         throw e;
677                                 }
678                                 // check all child rows.
679                                 CheckChildRows(DataRowAction.Change);
680                                 current = proposed;
681                                 proposed = null;
682                                 editing = false;
683                                 _table.ChangedDataRow(this, DataRowAction.Change);
684                         }
685                 }
686
687                 /// <summary>
688                 /// Gets the child rows of this DataRow using the specified DataRelation.
689                 /// </summary>
690                 public DataRow[] GetChildRows (DataRelation relation) 
691                 {
692                         return GetChildRows (relation, DataRowVersion.Current);
693                 }
694
695                 /// <summary>
696                 /// Gets the child rows of a DataRow using the specified RelationName of a
697                 /// DataRelation.
698                 /// </summary>
699                 public DataRow[] GetChildRows (string relationName) 
700                 {
701                         return GetChildRows (Table.DataSet.Relations[relationName]);
702                 }
703
704                 /// <summary>
705                 /// Gets the child rows of a DataRow using the specified DataRelation, and
706                 /// DataRowVersion.
707                 /// </summary>
708                 public DataRow[] GetChildRows (DataRelation relation, DataRowVersion version) 
709                 {
710                         if (relation == null)
711                                 return new DataRow[0];
712
713                         if (this.Table == null || RowState == DataRowState.Detached)
714                                 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.");
715
716                         if (relation.DataSet != this.Table.DataSet)
717                                 throw new ArgumentException();
718
719                         // TODO: Caching for better preformance
720                         ArrayList rows = new ArrayList();
721                         DataColumn[] parentColumns = relation.ParentColumns;
722                         DataColumn[] childColumns = relation.ChildColumns;
723                         int numColumn = parentColumns.Length;
724                         if (HasVersion(version)) 
725                         {
726                                 foreach (DataRow row in relation.ChildTable.Rows) 
727                                 {
728                                         bool allColumnsMatch = false;
729                                         if (row.HasVersion(DataRowVersion.Default))
730                                         {
731                                                 allColumnsMatch = true;
732                                                 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) 
733                                                 {
734                                                         if (!this[parentColumns[columnCnt], version].Equals(
735                                                                 row[childColumns[columnCnt], DataRowVersion.Default])) 
736                                                         {
737                                                                 allColumnsMatch = false;
738                                                                 break;
739                                                         }
740                                                 }
741                                         }
742                                         if (allColumnsMatch) rows.Add(row);
743                                 }
744                         }
745                         return rows.ToArray(typeof(DataRow)) as DataRow[];
746                 }
747
748                 /// <summary>
749                 /// Gets the child rows of a DataRow using the specified RelationName of a
750                 /// DataRelation, and DataRowVersion.
751                 /// </summary>
752                 public DataRow[] GetChildRows (string relationName, DataRowVersion version) 
753                 {
754                         return GetChildRows (Table.DataSet.Relations[relationName], version);
755                 }
756
757                 /// <summary>
758                 /// Gets the error description of the specified DataColumn.
759                 /// </summary>
760                 public string GetColumnError (DataColumn column) 
761                 {
762                         return GetColumnError (_table.Columns.IndexOf(column));
763                 }
764
765                 /// <summary>
766                 /// Gets the error description for the column specified by index.
767                 /// </summary>
768                 public string GetColumnError (int columnIndex) 
769                 {
770                         if (columnIndex < 0 || columnIndex >= columnErrors.Length)
771                                 throw new IndexOutOfRangeException ();
772
773                         string retVal = columnErrors[columnIndex];
774                         if (retVal == null)
775                                 retVal = string.Empty;
776                         return retVal;
777                 }
778
779                 /// <summary>
780                 /// Gets the error description for the column, specified by name.
781                 /// </summary>
782                 public string GetColumnError (string columnName) 
783                 {
784                         return GetColumnError (_table.Columns.IndexOf(columnName));
785                 }
786
787                 /// <summary>
788                 /// Gets an array of columns that have errors.
789                 /// </summary>
790                 public DataColumn[] GetColumnsInError () 
791                 {
792                         ArrayList dataColumns = new ArrayList ();
793
794                         for (int i = 0; i < columnErrors.Length; i += 1)
795                         {
796                                 if (columnErrors[i] != null && columnErrors[i] != String.Empty)
797                                         dataColumns.Add (_table.Columns[i]);
798                         }
799
800                         return (DataColumn[])(dataColumns.ToArray (typeof(DataColumn)));
801                 }
802
803                 /// <summary>
804                 /// Gets the parent row of a DataRow using the specified DataRelation.
805                 /// </summary>
806                 public DataRow GetParentRow (DataRelation relation) 
807                 {
808                         return GetParentRow (relation, DataRowVersion.Current);
809                 }
810
811                 /// <summary>
812                 /// Gets the parent row of a DataRow using the specified RelationName of a
813                 /// DataRelation.
814                 /// </summary>
815                 public DataRow GetParentRow (string relationName) 
816                 {
817                         return GetParentRow (relationName, DataRowVersion.Current);
818                 }
819
820                 /// <summary>
821                 /// Gets the parent row of a DataRow using the specified DataRelation, and
822                 /// DataRowVersion.
823                 /// </summary>
824                 public DataRow GetParentRow (DataRelation relation, DataRowVersion version) 
825                 {
826                         DataRow[] rows = GetParentRows(relation, version);
827                         if (rows.Length == 0) return null;
828                         return rows[0];
829                 }
830
831                 /// <summary>
832                 /// Gets the parent row of a DataRow using the specified RelationName of a 
833                 /// DataRelation, and DataRowVersion.
834                 /// </summary>
835                 public DataRow GetParentRow (string relationName, DataRowVersion version) 
836                 {
837                         return GetParentRow (Table.DataSet.Relations[relationName], version);
838                 }
839
840                 /// <summary>
841                 /// Gets the parent rows of a DataRow using the specified DataRelation.
842                 /// </summary>
843                 public DataRow[] GetParentRows (DataRelation relation) 
844                 {
845                         return GetParentRows (relation, DataRowVersion.Current);
846                 }
847
848                 /// <summary>
849                 /// Gets the parent rows of a DataRow using the specified RelationName of a 
850                 /// DataRelation.
851                 /// </summary>
852                 public DataRow[] GetParentRows (string relationName) 
853                 {
854                         return GetParentRows (relationName, DataRowVersion.Current);
855                 }
856
857                 /// <summary>
858                 /// Gets the parent rows of a DataRow using the specified DataRelation, and
859                 /// DataRowVersion.
860                 /// </summary>
861                 public DataRow[] GetParentRows (DataRelation relation, DataRowVersion version) 
862                 {
863                         // TODO: Caching for better preformance
864                         if (relation == null)
865                                 return new DataRow[0];
866
867                         if (this.Table == null || RowState == DataRowState.Detached)
868                                 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.");
869
870                         if (relation.DataSet != this.Table.DataSet)
871                                 throw new ArgumentException();
872
873                         ArrayList rows = new ArrayList();
874                         DataColumn[] parentColumns = relation.ParentColumns;
875                         DataColumn[] childColumns = relation.ChildColumns;
876                         int numColumn = parentColumns.Length;
877                         if (HasVersion(version))
878                         {
879                                 foreach (DataRow row in relation.ParentTable.Rows) 
880                                 {
881                                         bool allColumnsMatch = false;
882                                         if (row.HasVersion(DataRowVersion.Default))
883                                         {
884                                                 allColumnsMatch = true;
885                                                 for (int columnCnt = 0; columnCnt < numColumn; columnCnt++) 
886                                                 {
887                                                         if (!this[childColumns[columnCnt], version].Equals(
888                                                                 row[parentColumns[columnCnt], DataRowVersion.Default])) 
889                                                         {
890                                                                 allColumnsMatch = false;
891                                                                 break;
892                                                         }
893                                                 }
894                                         }
895                                         if (allColumnsMatch) rows.Add(row);
896                                 }
897                         }
898                         return rows.ToArray(typeof(DataRow)) as DataRow[];
899                 }
900
901                 /// <summary>
902                 /// Gets the parent rows of a DataRow using the specified RelationName of a 
903                 /// DataRelation, and DataRowVersion.
904                 /// </summary>
905                 public DataRow[] GetParentRows (string relationName, DataRowVersion version) 
906                 {
907                         return GetParentRows (Table.DataSet.Relations[relationName], version);
908                 }
909
910                 /// <summary>
911                 /// Gets a value indicating whether a specified version exists.
912                 /// </summary>
913                 public bool HasVersion (DataRowVersion version) 
914                 {
915                         switch (version)
916                         {
917                                 case DataRowVersion.Default:
918                                         if (rowState == DataRowState.Deleted)
919                                                 return false;
920                                         if (rowState == DataRowState.Detached)
921                                                 return proposed != null;
922                                         return true;
923                                 case DataRowVersion.Proposed:
924                                         if (rowState == DataRowState.Deleted)
925                                                 return false;
926                                         return (proposed != null);
927                                 case DataRowVersion.Current:
928                                         if (rowState == DataRowState.Deleted || rowState == DataRowState.Detached)
929                                                 return false;
930                                         return (current != null);
931                                 case DataRowVersion.Original:
932                                         if (rowState == DataRowState.Detached)
933                                                 return false;
934                                         return (original != null);
935                         }
936                         return false;
937                 }
938
939                 /// <summary>
940                 /// Gets a value indicating whether the specified DataColumn contains a null value.
941                 /// </summary>
942                 public bool IsNull (DataColumn column) 
943                 {
944                         object o = this[column];
945                         return (o == null || o == DBNull.Value);
946                 }
947
948                 /// <summary>
949                 /// Gets a value indicating whether the column at the specified index contains a null
950                 /// value.
951                 /// </summary>
952                 public bool IsNull (int columnIndex) 
953                 {
954                         object o = this[columnIndex];
955                         return (o == null || o == DBNull.Value);
956                 }
957
958                 /// <summary>
959                 /// Gets a value indicating whether the named column contains a null value.
960                 /// </summary>
961                 public bool IsNull (string columnName) 
962                 {
963                         object o = this[columnName];
964                         return (o == null || o == DBNull.Value);
965                 }
966
967                 /// <summary>
968                 /// Gets a value indicating whether the specified DataColumn and DataRowVersion
969                 /// contains a null value.
970                 /// </summary>
971                 public bool IsNull (DataColumn column, DataRowVersion version) 
972                 {
973                         object o = this[column, version];
974                         return (o == null || o == DBNull.Value);
975                 }
976
977                 /// <summary>
978                 /// Rejects all changes made to the row since AcceptChanges was last called.
979                 /// </summary>
980                 public void RejectChanges () 
981                 {
982                         if (RowState == DataRowState.Detached)
983                                 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.");
984                         // If original is null, then nothing has happened since AcceptChanges
985                         // was last called.  We have no "original" to go back to.
986                         if (original != null)
987                         {
988                                 Array.Copy (original, current, _table.Columns.Count);
989                                
990                                 _table.ChangedDataRow (this, DataRowAction.Rollback);
991                                 CancelEdit ();
992                                 switch (rowState)
993                                 {
994                                         case DataRowState.Added:
995                                                 _table.Rows.RemoveInternal (this);
996                                                 break;
997                                         case DataRowState.Modified:
998                                                 rowState = DataRowState.Unchanged;
999                                                 break;
1000                                         case DataRowState.Deleted:
1001                                                 rowState = DataRowState.Unchanged;
1002                                                 break;
1003                                 } 
1004                                 
1005                         }                       
1006                         else {
1007                                 // If rows are just loaded via Xml the original values are null.
1008                                 // So in this case we have to remove all columns.
1009                                 // FIXME: I'm not realy sure, does this break something else, but
1010                                 // if so: FIXME ;)
1011                                 
1012                                 if ((rowState & DataRowState.Added) > 0)
1013                                         _table.Rows.RemoveInternal (this);
1014                         }
1015                 }
1016
1017                 /// <summary>
1018                 /// Sets the error description for a column specified as a DataColumn.
1019                 /// </summary>
1020                 public void SetColumnError (DataColumn column, string error) 
1021                 {
1022                         SetColumnError (_table.Columns.IndexOf (column), error);
1023                 }
1024
1025                 /// <summary>
1026                 /// Sets the error description for a column specified by index.
1027                 /// </summary>
1028                 public void SetColumnError (int columnIndex, string error) 
1029                 {
1030                         if (columnIndex < 0 || columnIndex >= columnErrors.Length)
1031                                 throw new IndexOutOfRangeException ();
1032                         columnErrors[columnIndex] = error;
1033                 }
1034
1035                 /// <summary>
1036                 /// Sets the error description for a column specified by name.
1037                 /// </summary>
1038                 public void SetColumnError (string columnName, string error) 
1039                 {
1040                         SetColumnError (_table.Columns.IndexOf (columnName), error);
1041                 }
1042
1043                 /// <summary>
1044                 /// Sets the value of the specified DataColumn to a null value.
1045                 /// </summary>
1046                 protected void SetNull (DataColumn column) 
1047                 {
1048                         this[column] = DBNull.Value;
1049                 }
1050
1051                 /// <summary>
1052                 /// Sets the parent row of a DataRow with specified new parent DataRow.
1053                 /// </summary>
1054                 [MonoTODO]
1055                 public void SetParentRow (DataRow parentRow) 
1056                 {
1057                         SetParentRow(parentRow, null);
1058                 }
1059
1060                 /// <summary>
1061                 /// Sets the parent row of a DataRow with specified new parent DataRow and
1062                 /// DataRelation.
1063                 /// </summary>
1064                 [MonoTODO]
1065                 public void SetParentRow (DataRow parentRow, DataRelation relation) 
1066                 {
1067                         if (_table == null || parentRow.Table == null || RowState == DataRowState.Detached)
1068                                 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.");
1069
1070                         if (parentRow != null && _table.DataSet != parentRow.Table.DataSet)
1071                                 throw new ArgumentException();
1072                         
1073                         BeginEdit();
1074                         if (relation == null)
1075                         {
1076                                 foreach (DataRelation parentRel in _table.ParentRelations)
1077                                 {
1078                                         DataColumn[] childCols = parentRel.ChildKeyConstraint.Columns;
1079                                         DataColumn[] parentCols = parentRel.ChildKeyConstraint.RelatedColumns;
1080                                         
1081                                         for (int i = 0; i < parentCols.Length; i++)
1082                                         {
1083                                                 if (parentRow == null)
1084                                                         this[childCols[i].Ordinal] = DBNull.Value;
1085                                                 else
1086                                                         this[childCols[i].Ordinal] = parentRow[parentCols[i]];
1087                                         }
1088                                         
1089                                 }
1090                         }
1091                         else
1092                         {
1093                                 DataColumn[] childCols = relation.ChildKeyConstraint.Columns;
1094                                 DataColumn[] parentCols = relation.ChildKeyConstraint.RelatedColumns;
1095                                         
1096                                 for (int i = 0; i < parentCols.Length; i++)
1097                                 {
1098                                         if (parentRow == null)
1099                                                 this[childCols[i].Ordinal] = DBNull.Value;
1100                                         else
1101                                                 this[childCols[i].Ordinal] = parentRow[parentCols[i]];
1102                                 }
1103                         }
1104                         EndEdit();
1105                 }
1106                 
1107                 //Copy all values of this DataaRow to the row parameter.
1108                 internal void CopyValuesToRow(DataRow row)
1109                 {
1110                                                 
1111                         if (row == null)
1112                                 throw new ArgumentNullException("row");
1113                         if (row == this)
1114                                 throw new ArgumentException("'row' is the same as this object");
1115
1116                         DataColumnCollection columns = Table.Columns;
1117                         
1118                         for(int i = 0; i < columns.Count; i++){
1119
1120                                 string columnName = columns[i].ColumnName;
1121                                 int index = row.Table.Columns.IndexOf(columnName);
1122                                 //if a column with the same name exists in both rows copy the values
1123                                 if(index != -1) {
1124                                         if (HasVersion(DataRowVersion.Original))
1125                                         {
1126                                                 if (row.original == null)
1127                                                         row.original = new object[row.Table.Columns.Count];
1128                                                 row.original[index] = row.SetColumnValue(original[i], index);
1129                                         }
1130                                         if (HasVersion(DataRowVersion.Current))
1131                                         {
1132                                                 if (row.current == null)
1133                                                         row.current = new object[row.Table.Columns.Count];
1134                                                 row.current[index] = row.SetColumnValue(current[i], index);
1135                                         }
1136                                         if (HasVersion(DataRowVersion.Proposed))
1137                                         {
1138                                                 if (row.proposed == null)
1139                                                         row.proposed = new object[row.Table.Columns.Count];
1140                                                 row.proposed[index] = row.SetColumnValue(proposed[i], index);
1141                                         }
1142                                         
1143                                         //Saving the current value as the column value
1144                                         row[index] = row.current[index];
1145                                         
1146                                 }
1147                         }
1148
1149                         row.rowState = RowState;
1150                         row.RowError = RowError;
1151                         row.columnErrors = columnErrors;
1152                 }
1153
1154                 
1155                 public void CollectionChanged(object sender, System.ComponentModel.CollectionChangeEventArgs args)
1156                 {
1157                         // if a column is added we hava to add an additional value the 
1158                         // the priginal, current and propoed arrays.
1159                         // this scenario can happened in merge operation.
1160
1161                         if (args.Action == System.ComponentModel.CollectionChangeAction.Add)
1162                         {
1163                                 object[] tmp;
1164                                 if (current != null)
1165                                 {
1166                                         tmp = new object[current.Length + 1];
1167                                         Array.Copy (current, tmp, current.Length);
1168                                         tmp[tmp.Length - 1] = DBNull.Value;
1169                                         current = tmp;
1170                                 }
1171                                 if (proposed != null)
1172                                 {
1173                                         tmp = new object[proposed.Length + 1];
1174                                         Array.Copy (proposed, tmp, proposed.Length);
1175                                         tmp[tmp.Length - 1] = DBNull.Value;
1176                                         proposed = tmp;
1177                                 }
1178                                 if(original != null)
1179                                 {
1180                                         tmp = new object[original.Length + 1];
1181                                         Array.Copy (original, tmp, original.Length);
1182                                         tmp[tmp.Length - 1] = DBNull.Value;
1183                                         original = tmp;
1184                                 }
1185
1186                         }
1187                 }
1188
1189                 internal bool IsRowChanged(DataRowState rowState) {
1190                         if((RowState & rowState) != 0)
1191                                 return true;
1192
1193                         //we need to find if child rows of this row changed.
1194                         //if yes - we should return true
1195
1196                         // if the rowState is deleted we should get the original version of the row
1197                         // else - we should get the current version of the row.
1198                         DataRowVersion version = (rowState == DataRowState.Deleted) ? DataRowVersion.Original : DataRowVersion.Current;
1199                         int count = Table.ChildRelations.Count;
1200                         for (int i = 0; i < count; i++){
1201                                 DataRelation rel = Table.ChildRelations[i];
1202                                 DataRow[] childRows = GetChildRows(rel, version);
1203                                 for (int j = 0; j < childRows.Length; j++){
1204                                         if (childRows[j].IsRowChanged(rowState))
1205                                                 return true;
1206                                 }
1207                         }
1208
1209                         return false;
1210                 }
1211
1212                 internal bool HasParentCollection
1213                 {
1214                         get
1215                         {
1216                                 return _hasParentCollection;
1217                         }
1218                         set
1219                         {
1220                                 _hasParentCollection = value;
1221                         }
1222                 }
1223
1224                 internal void CheckNullConstraints()
1225                 {
1226                         if (_nullConstraintViolation)
1227                         {
1228                                 for (int i = 0; i < proposed.Length; i++)
1229                                 {
1230                                         if (this[i] == DBNull.Value && !_table.Columns[i].AllowDBNull)
1231                                                 throw new NoNullAllowedException(_nullConstraintMessage);
1232                                 }
1233                                 _nullConstraintViolation = false;
1234                         }
1235                 }
1236
1237                 #endregion // Methods
1238         }
1239
1240         
1241
1242 }