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