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