In Test/System.Data:
[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 //   Sureshkumar T <tsureshkumar@novell.com>
11 //
12 // (C) Ximian, Inc 2002
13 // (C) Daniel Morgan 2002, 2003
14 // Copyright (C) 2002 Tim Coleman
15 //
16
17 //
18 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
19 //
20 // Permission is hereby granted, free of charge, to any person obtaining
21 // a copy of this software and associated documentation files (the
22 // "Software"), to deal in the Software without restriction, including
23 // without limitation the rights to use, copy, modify, merge, publish,
24 // distribute, sublicense, and/or sell copies of the Software, and to
25 // permit persons to whom the Software is furnished to do so, subject to
26 // the following conditions:
27 // 
28 // The above copyright notice and this permission notice shall be
29 // included in all copies or substantial portions of the Software.
30 // 
31 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
32 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
33 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
34 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
35 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
36 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
37 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
38 //
39
40 using System;
41 using System.Data.Common;
42 using System.Collections;
43 using System.Globalization;
44 using System.Xml;
45
46 namespace System.Data {
47         /// <summary>
48         /// Represents a row of data in a DataTable.
49         /// </summary>
50         [Serializable]
51         public class DataRow
52         {
53                 #region Fields
54
55                 private DataTable _table;
56
57                 internal int _original = -1;
58                 internal int _current = -1;
59                 internal int _proposed = -1;
60
61                 private ArrayList _columnErrors;
62                 private string rowError;
63                 private DataRowState rowState;
64                 internal int xmlRowID = 0;
65                 internal bool _nullConstraintViolation;
66                 private string _nullConstraintMessage;
67                 private bool editing = false;
68                 private bool _hasParentCollection;
69                 private bool _inChangingEvent;
70                 private int _rowId;
71
72                 private XmlDataDocument.XmlDataElement mappedElement;
73                 internal bool _inExpressionEvaluation = false;
74
75                 #endregion // Fields
76
77                 #region Constructors
78
79                 /// <summary>
80                 /// This member supports the .NET Framework infrastructure and is not intended to be 
81                 /// used directly from your code.
82                 /// </summary>
83                 protected internal DataRow (DataRowBuilder builder)
84                 {
85                         _table = builder.Table;
86                         // Get the row id from the builder.
87                         _rowId = builder._rowId;
88
89                         _proposed = _table.RecordCache.NewRecord();
90                         // Initialise the data columns of the row with the dafault values, if any 
91                         // TODO : should proposed version be available immediately after record creation ?
92                         foreach(DataColumn column in _table.Columns) {
93                                 column.DataContainer.CopyValue(_table.DefaultValuesRowIndex,_proposed);
94                         }
95                         
96                         rowError = String.Empty;
97
98                         //on first creating a DataRow it is always detached.
99                         rowState = DataRowState.Detached;
100                         
101                         ArrayList aiColumns = _table.Columns.AutoIncrmentColumns;
102                         foreach (DataColumn dc in aiColumns) {
103                                 this [dc] = dc.AutoIncrementValue();
104                         }
105
106                         // create mapped XmlDataElement
107                         DataSet ds = _table.DataSet;
108                         if (ds != null && ds._xmlDataDocument != null)
109                                 mappedElement = new XmlDataDocument.XmlDataElement (this, _table.Prefix, _table.TableName, _table.Namespace, ds._xmlDataDocument);
110                 }
111
112                 internal DataRow(DataTable table,int rowId)
113                 {
114                         _table = table;
115                         _rowId = rowId;
116                 }
117
118                 #endregion // Constructors
119
120                 #region Properties
121
122                 private ArrayList ColumnErrors
123                 {
124                         get {
125                                 if (_columnErrors == null) {
126                                         _columnErrors = new ArrayList();
127                                 }
128                                 return _columnErrors;
129                         }
130
131                         set {
132                                 _columnErrors = value;
133                         }
134                 }
135
136                 /// <summary>
137                 /// Gets a value indicating whether there are errors in a row.
138                 /// </summary>
139                 public bool HasErrors {
140                         get {
141                                 if (RowError != string.Empty)
142                                         return true;
143
144                                 foreach(String columnError in ColumnErrors) {
145                                         if (columnError != null && columnError != string.Empty) {
146                                                 return true;
147                                 }
148                                 }
149                                 return false;
150                         }
151                 }
152
153                 /// <summary>
154                 /// Gets or sets the data stored in the column specified by name.
155                 /// </summary>
156                 public object this[string columnName] {
157                         get { return this[columnName, DataRowVersion.Default]; }
158                         set {
159                                 int columnIndex = _table.Columns.IndexOf (columnName);
160                                 if (columnIndex == -1)
161                                         throw new IndexOutOfRangeException ();
162                                 this[columnIndex] = value;
163                         }
164                 }
165
166                 /// <summary>
167                 /// Gets or sets the data stored in specified DataColumn
168                 /// </summary>
169                 public object this[DataColumn column] {
170
171                         get {
172                                 return this[column, DataRowVersion.Default];} 
173                         set {
174                                 int columnIndex = _table.Columns.IndexOf (column);
175                                 if (columnIndex == -1)
176                                         throw new ArgumentException ("The column does not belong to this table.");
177                                 this[columnIndex] = value;
178                         }
179                 }
180
181                 /// <summary>
182                 /// Gets or sets the data stored in column specified by index.
183                 /// </summary>
184                 public object this[int columnIndex] {
185                         get { return this[columnIndex, DataRowVersion.Default]; }
186                         set {
187                                 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
188                                         throw new IndexOutOfRangeException ();
189                                 if (rowState == DataRowState.Deleted)
190                                         throw new DeletedRowInaccessibleException ();
191
192                                 DataColumn column = _table.Columns[columnIndex];
193                                 _table.ChangingDataColumn (this, column, value);
194                                 
195                                 if (value == null && column.DataType != typeof(string)) {
196                                         throw new ArgumentException("Cannot set column " + column.ColumnName + " to be null, Please use DBNull instead");
197                                 }
198                                 
199                                 CheckValue (value, column);
200
201                                 bool orginalEditing = editing;
202                                 if (!orginalEditing) {
203                                         BeginEdit ();
204                                 }
205                                 
206                                 column[_proposed] = value;
207                                 _table.ChangedDataColumn (this, column, value);
208                                 if (!orginalEditing) {
209                                         EndEdit ();
210                                 }
211                         }
212                 }
213
214                 /// <summary>
215                 /// Gets the specified version of data stored in the named column.
216                 /// </summary>
217                 public object this[string columnName, DataRowVersion version] {
218                         get {
219                                 int columnIndex = _table.Columns.IndexOf (columnName);
220                                 if (columnIndex == -1)
221                                         throw new IndexOutOfRangeException ();
222                                 return this[columnIndex, version];
223                         }
224                 }
225
226                 /// <summary>
227                 /// Gets the specified version of data stored in the specified DataColumn.
228                 /// </summary>
229                 public object this[DataColumn column, DataRowVersion version] {
230                         get {
231                                 if (column.Table != Table)
232                                         throw new ArgumentException ("The column does not belong to this table.");
233                                 int columnIndex = column.Ordinal;
234                                 return this[columnIndex, version];
235                         }
236                 }
237
238                 /// <summary>
239                 /// Sets the index into the container records for the original version. Apart
240                 /// from that, it makes sure it pools the record used earlier if they are not
241                 /// used by other versions.
242                 /// </summary>
243                 internal int Original 
244                 {
245                         get { return _original;}
246                         set {
247                                 if (_original == value) 
248                                         return;
249                                 
250                                 if (_original >= 0 
251                                     && _current != _original
252                                     && _proposed != _original)
253                                         Table.RecordCache.DisposeRecord (_original);
254                                 _original = value;
255                         }
256                 }
257
258                 /// <summary>
259                 /// Sets the index into the container records for the proposed version. Apart
260                 /// from that, it makes sure it pools the record used earlier if they are not
261                 /// used by other versions.
262                 internal int Proposed
263                 {
264                         get { return _proposed;}
265                         set {
266                                 if (_proposed == value)
267                                         return;
268                                 if (_proposed >= 0
269                                     && _proposed != _current
270                                     && _proposed != _original)
271                                         Table.RecordCache.DisposeRecord (_proposed);
272                                 _proposed = value;
273                         }
274                 }
275
276                 /// <summary>
277                 /// Sets the index into the container records for the current version. Apart
278                 /// from that, it makes sure it pools the record used earlier if they are not
279                 /// used by other versions.
280                 internal int Current 
281                 {
282                         get { return _current;}
283                         set {
284                                 if (_current == value)
285                                         return;
286                                 if (_current >= 0
287                                     && _current != _original
288                                     && _current != _proposed)
289                                         Table.RecordCache.DisposeRecord (_current);
290                                 _current = value;
291                         }
292                 }
293
294                 /// <summary>
295                 /// Set a value for the column into the offset specified by the version.<br>
296                 /// If the value is auto increment or null, necessary auto increment value
297                 /// or the default value will be used.
298                 /// </summary>
299                 internal void SetValue (int column, object value, int version)
300                 {
301                         DataColumn dc = Table.Columns[column];
302
303                         if (value == null && ! dc.AutoIncrement) // set default value / auto increment
304                                 value = dc.DefaultValue;
305
306                         Table.ChangingDataColumn (this, dc, value);     
307                         CheckValue (value, dc);
308                         if ( ! dc.AutoIncrement)
309                                 dc [version] = value;
310                         else if (_proposed >= 0 && _proposed != version) // proposed holds the AI
311                                 dc [version] = dc [_proposed];
312                 }
313
314                 /// <summary>
315                 /// Gets the data stored in the column, specified by index and version of the data to
316                 /// retrieve.
317                 /// </summary>
318                 public object this[int columnIndex, DataRowVersion version] {
319                         get {
320                                 if (columnIndex < 0 || columnIndex > _table.Columns.Count)
321                                         throw new IndexOutOfRangeException ();
322                                 // Accessing deleted rows
323                                 if (!_inExpressionEvaluation && rowState == DataRowState.Deleted && version != DataRowVersion.Original)
324                                         throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
325                                 
326                                 DataColumn column = _table.Columns[columnIndex];
327                                 if (column.Expression != String.Empty) {
328                                         object o = column.CompiledExpression.Eval (this);
329                                         return Convert.ChangeType (o, column.DataType);
330                                 }
331
332                                 if (rowState == DataRowState.Detached && version == DataRowVersion.Default && _proposed < 0)
333                                         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.");
334                                 
335                                 int recordIndex = IndexFromVersion(version);
336
337                                 if (recordIndex >= 0) {
338                                         return column[recordIndex];
339                                 }
340                                 
341                                 throw new VersionNotFoundException (Locale.GetText ("There is no " + version.ToString () + " data to access."));
342                         }
343                 }
344                 
345                 /// <summary>
346                 /// Gets or sets all of the values for this row through an array.
347                 /// </summary>
348                 public object[] ItemArray {
349                         get { 
350                                 // row not in table
351                                 if (rowState == DataRowState.Detached)
352                                         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.");
353                                 // Accessing deleted rows
354                                 if (rowState == DataRowState.Deleted)
355                                         throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");
356                                 
357                                 object[] items = new object[_table.Columns.Count];
358                                 foreach(DataColumn column in _table.Columns) {
359                                         items[column.Ordinal] = column[_current];
360                                 }
361                                 return items;
362                         }
363                         set {
364                                 if (value.Length > _table.Columns.Count)
365                                         throw new ArgumentException ();
366
367                                 if (rowState == DataRowState.Deleted)
368                                         throw new DeletedRowInaccessibleException ();
369                                 
370                                 bool orginalEditing = editing;
371                                 if (!orginalEditing) { 
372                                         BeginEdit ();
373                                 }
374                                 object newVal = null;
375                                 DataColumnChangeEventArgs e = new DataColumnChangeEventArgs();
376                                 foreach(DataColumn column in _table.Columns) {
377                                         int i = column.Ordinal;
378                                         newVal = (i < value.Length) ? value[i] : null;
379                                         
380                                         e.Initialize(this, column, newVal);
381                                         _table.RaiseOnColumnChanged(e);
382                                         CheckValue (e.ProposedValue, column);
383                                         column[_proposed] = e.ProposedValue;
384                                 }
385                                 if (!orginalEditing) {
386                                         EndEdit ();
387                                 }
388                         }
389                 }
390
391                 internal bool IsEditing {
392                         get { return editing; }
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 { 
401                                 return rowState; 
402                         }
403                 }
404
405                 /// <summary>
406                 /// Gets the DataTable for which this row has a schema.
407                 /// </summary>
408                 public DataTable Table {
409                         get { 
410                                 return _table; 
411                         }
412                 }
413
414                 /// <summary>
415                 /// Gets and sets index of row. This is used from 
416                 /// XmlDataDocument.
417                 // </summary>
418                 internal int XmlRowID {
419                         get { 
420                                 return xmlRowID; 
421                         }
422                         set { 
423                                 xmlRowID = value; 
424                         }
425                 }
426                 
427                 /// <summary>
428                 /// Gets and sets index of row.
429                 // </summary>
430                 internal int RowID {
431                         get { 
432                                 return _rowId; 
433                         }
434                         set { 
435                                 _rowId = value; 
436                         }
437                 }
438
439                 #endregion // Properties
440
441                 #region Methods
442
443                 //FIXME?: Couldn't find a way to set the RowState when adding the DataRow
444                 //to a Datatable so I added this method. Delete if there is a better way.
445                 internal void AttachRow() {
446                         if (_current >= 0) {
447                                 Table.RecordCache.DisposeRecord(_current);
448                         }
449                         _current = _proposed;
450                         _proposed = -1;
451                         rowState = DataRowState.Added;
452                 }
453
454                 //FIXME?: Couldn't find a way to set the RowState when removing the DataRow
455                 //from a Datatable so I added this method. Delete if there is a better way.
456                 internal void DetachRow() {
457                         if (_proposed >= 0) {
458                                 _table.RecordCache.DisposeRecord(_proposed);
459                                 _proposed = -1;
460                         }
461                         _rowId = -1;
462                         _hasParentCollection = false;
463                         rowState = DataRowState.Detached;
464                 }
465
466                 private void CheckValue (object v, DataColumn col) 
467                 {               
468                         if (_hasParentCollection && col.ReadOnly) {
469                                 throw new ReadOnlyException ();
470                         }
471
472                         if (v == null || v == DBNull.Value) {
473                                 if (col.AllowDBNull || col.AutoIncrement || col.DefaultValue != DBNull.Value) {
474                                         return;
475                                 }
476
477                                 //Constraint violations during data load is raise in DataTable EndLoad
478                                 this._nullConstraintViolation = true;
479                                 if (this.Table._duringDataLoad) {
480                                         this.Table._nullConstraintViolationDuringDataLoad = true;
481                                 }
482                                 _nullConstraintMessage = "Column '" + col.ColumnName + "' does not allow nulls.";
483                         
484                         }
485                 }
486
487                 internal void SetValuesFromDataRecord(IDataRecord record, int[] mapping)
488                 {
489 //                      bool orginalEditing = editing;
490 //                      if (!orginalEditing) { 
491 //                              BeginEdit ();
492 //                      }
493                         
494                         if (!HasVersion(DataRowVersion.Proposed)) {
495                                 _proposed = Table.RecordCache.NewRecord();
496                         }
497
498                         try {
499                                 for(int i=0; i < Table.Columns.Count; i++) {
500                                         DataColumn column = Table.Columns[i];
501                                         if (mapping [i] < 0) { // no mapping
502                                                 if (! column.AutoIncrement)
503                                                         column.DataContainer [_proposed] = column.DefaultValue;
504                                                 continue;
505                                         }
506
507                                         column.DataContainer.SetItemFromDataRecord(_proposed, record,mapping[i]);
508                                         if ( column.AutoIncrement ) { 
509                                                 column.UpdateAutoIncrementValue(column.DataContainer.GetInt64(_proposed));
510                                         }
511                                 }
512                         }
513                         catch (Exception e){
514                                 Table.RecordCache.DisposeRecord(_proposed);
515                                 _proposed = -1;
516                                 throw e;
517                         }
518
519 //                      if (!orginalEditing) {
520 //                              EndEdit ();
521 //                      }
522                 }
523
524                 /// <summary>
525                 /// Gets or sets the custom error description for a row.
526                 /// </summary>
527                 public string RowError {
528                         get { 
529                                 return rowError; 
530                         }
531                         set { 
532                                 rowError = value; 
533                         }
534                 }
535
536                 internal int IndexFromVersion(DataRowVersion version)
537                 {
538                         if (HasVersion(version))
539                         {
540                                 int recordIndex;
541                                 switch (version) {
542                                         case DataRowVersion.Default:
543                                                 if (editing || rowState == DataRowState.Detached) {
544                                                         recordIndex = _proposed;
545                                                 }
546                                                 else {
547                                                         recordIndex = _current;
548                                                 }
549                                                 break;
550                                         case DataRowVersion.Proposed:
551                                                 recordIndex = _proposed;
552                                                 break;
553                                         case DataRowVersion.Current:
554                                                 recordIndex = _current;
555                                                 break;
556                                         case DataRowVersion.Original:
557                                                 recordIndex = _original;
558                                                 break;
559                                         default:
560                                                 throw new ArgumentException ();
561                                 }
562                                 return recordIndex;
563                         }
564                         return -1;
565                 }
566
567                 internal XmlDataDocument.XmlDataElement DataElement {
568                         get { return mappedElement; }
569                         set { mappedElement = value; }
570                 }
571
572                 internal void SetOriginalValue (string columnName, object val)
573                 {
574                         DataColumn column = _table.Columns[columnName];
575                         _table.ChangingDataColumn (this, column, val);
576                                 
577                         if (_original < 0 || _original == _current) { 
578                                 // This really creates a new record version if one does not exist
579                                 _original = Table.RecordCache.NewRecord();
580                         }
581                         CheckValue (val, column);
582                         column[_original] = val;
583                         rowState = DataRowState.Modified;
584                 }
585
586                 /// <summary>
587                 /// Commits all the changes made to this row since the last time AcceptChanges was
588                 /// called.
589                 /// </summary>
590                 public void AcceptChanges () 
591                 {
592                         EndEdit(); // in case it hasn't been called
593                         switch (rowState) {
594                                 case DataRowState.Unchanged:
595                                         return;
596                         case DataRowState.Added:
597                         case DataRowState.Modified:
598                                 rowState = DataRowState.Unchanged;
599                                 break;
600                         case DataRowState.Deleted:
601                                 _table.Rows.RemoveInternal (this);
602                                 DetachRow();
603                                 break;
604                         case DataRowState.Detached:
605                                 throw new RowNotInTableException("Cannot perform this operation on a row not in the table.");
606                         }
607                         // Accept from detached
608                         if (_original != _current)
609                                 Original = Current;
610                 }
611
612                 /// <summary>
613                 /// Begins an edit operation on a DataRow object.
614                 /// </summary>
615                 public void BeginEdit () 
616                 {
617                         if (_inChangingEvent)
618                                 throw new InRowChangingEventException("Cannot call BeginEdit inside an OnRowChanging event.");
619                         if (rowState == DataRowState.Deleted)
620                                 throw new DeletedRowInaccessibleException ();
621                         if (!HasVersion (DataRowVersion.Proposed)) {
622                                 _proposed = Table.RecordCache.NewRecord();
623                                 foreach(DataColumn column in Table.Columns) {
624                                         column.DataContainer.CopyValue(_current,_proposed);
625                                 }
626                         }
627                         // setting editing to true stops validations on the row
628                         editing = true;
629                 }
630
631                 /// <summary>
632                 /// Cancels the current edit on the row.
633                 /// </summary>
634                 public void CancelEdit () 
635                 {
636                          if (_inChangingEvent)
637                                 throw new InRowChangingEventException("Cannot call CancelEdit inside an OnRowChanging event.");
638                         editing = false;
639                         if (HasVersion (DataRowVersion.Proposed)) {
640                                 Table.RecordCache.DisposeRecord(_proposed);
641                                 _proposed = -1;
642                                 if (rowState == DataRowState.Modified) {
643                                     rowState = DataRowState.Unchanged;
644                                 }
645                         }
646                 }
647
648                 /// <summary>
649                 /// Clears the errors for the row, including the RowError and errors set with
650                 /// SetColumnError.
651                 /// </summary>
652                 public void ClearErrors () 
653                 {
654                         rowError = String.Empty;
655                         ColumnErrors.Clear();
656                 }
657
658                 /// <summary>
659                 /// Deletes the DataRow.
660                 /// </summary>
661                 public void Delete () 
662                 {
663                         _table.DeletingDataRow(this, DataRowAction.Delete);
664                         switch (rowState) {
665                         case DataRowState.Added:
666                                 // check what to do with child rows
667                                 CheckChildRows(DataRowAction.Delete);
668                                 _table.DeleteRowFromIndexes (this);
669                                 Table.Rows.RemoveInternal (this);
670
671                                 // if row was in Added state we move it to Detached.
672                                 DetachRow();
673                                 break;
674                         case DataRowState.Deleted:
675                                 break;          
676                         default:
677                                 // check what to do with child rows
678                                 CheckChildRows(DataRowAction.Delete);
679                                 _table.DeleteRowFromIndexes (this);
680                                 rowState = DataRowState.Deleted;
681                                 break;
682                         }
683                         _table.DeletedDataRow(this, DataRowAction.Delete);
684                 }
685
686                 // check the child rows of this row before deleting the row.
687                 private void CheckChildRows(DataRowAction action)
688                 {
689                         
690                         // in this method we find the row that this row is in a relation with them.
691                         // in shortly we find all child rows of this row.
692                         // then we function according to the DeleteRule of the foriegnkey.
693
694
695                         // 1. find if this row is attached to dataset.
696                         // 2. find if EnforceConstraints is true.
697                         // 3. find if there are any constraint on the table that the row is in.
698                         if (_table.DataSet != null && _table.DataSet.EnforceConstraints && _table.Constraints.Count > 0)
699                         {
700                                 foreach (DataTable table in _table.DataSet.Tables)
701                                 {
702                                         // loop on all ForeignKeyConstrain of the table.
703                                         foreach (ForeignKeyConstraint fk in table.Constraints.ForeignKeyConstraints)
704                                         {
705                                                 if (fk.RelatedTable == _table)
706                                                 {
707                                                         Rule rule;
708                                                         if (action == DataRowAction.Delete)
709                                                                 rule = fk.DeleteRule;
710                                                         else
711                                                                 rule = fk.UpdateRule;
712                                                         CheckChildRows(fk, action, rule);
713                                                 }                       
714                                         }
715                                 }
716                         }
717                 }
718
719                 private void CheckChildRows(ForeignKeyConstraint fkc, DataRowAction action, Rule rule)
720                 {                               
721                         DataRow[] childRows = GetChildRows(fkc, DataRowVersion.Default);
722                         switch (rule)
723                         {
724                                 case Rule.Cascade:  // delete or change all relted rows.
725                                         if (childRows != null)
726                                         {
727                                                 for (int j = 0; j < childRows.Length; j++)
728                                                 {
729                                                         // if action is delete we delete all child rows
730                                                         if (action == DataRowAction.Delete)
731                                                         {
732                                                                 if (childRows[j].RowState != DataRowState.Deleted)
733                                                                         childRows[j].Delete();
734                                                         }
735                                                         // if action is change we change the values in the child row
736                                                         else if (action == DataRowAction.Change)
737                                                         {
738                                                                 // change only the values in the key columns
739                                                                 // set the childcolumn value to the new parent row value
740                                                                 for (int k = 0; k < fkc.Columns.Length; k++)
741                                                                         childRows[j][fkc.Columns[k]] = this[fkc.RelatedColumns[k], DataRowVersion.Proposed];
742                                                         }
743                                                 }
744                                         }
745                                         break;
746                                 case Rule.None: // throw an exception if there are any child rows.
747                                         if (childRows != null)
748                                         {
749                                                 for (int j = 0; j < childRows.Length; j++)
750                                                 {
751                                                         if (childRows[j].RowState != DataRowState.Deleted)
752                                                         {
753                                                                 string changeStr = "Cannot change this row because constraints are enforced on relation " + fkc.ConstraintName +", and changing this row will strand child rows.";
754                                                                 string delStr = "Cannot delete this row because constraints are enforced on relation " + fkc.ConstraintName +", and deleting this row will strand child rows.";
755                                                                 string message = action == DataRowAction.Delete ? delStr : changeStr;
756                                                                 throw new InvalidConstraintException(message);
757                                                         }
758                                                 }
759                                         }
760                                         break;
761                                 case Rule.SetDefault: // set the values in the child rows to the defult value of the columns.
762                                         if (childRows != null && childRows.Length > 0) {
763                                                 int defaultValuesRowIndex = childRows[0].Table.DefaultValuesRowIndex;
764                                                 foreach(DataRow childRow in childRows) {
765                                                         if (childRow.RowState != DataRowState.Deleted) {
766                                                                 int defaultIdx = childRow.IndexFromVersion(DataRowVersion.Default);
767                                                                 foreach(DataColumn column in fkc.Columns) {
768                                                                         column.DataContainer.CopyValue(defaultValuesRowIndex,defaultIdx);
769                                                                 }
770                                                         }
771                                                 }
772                                         }
773                                         break;
774                                 case Rule.SetNull: // set the values in the child row to null.
775                                         if (childRows != null)
776                                         {
777                                                 for (int j = 0; j < childRows.Length; j++)
778                                                 {
779                                                         DataRow child = childRows[j];
780                                                         if (childRows[j].RowState != DataRowState.Deleted)
781                                                         {
782                                                                 // set only the key columns to DBNull
783                                                                 for (int k = 0; k < fkc.Columns.Length; k++)
784                                                                         child.SetNull(fkc.Columns[k]);
785                                                         }
786                                                 }
787                                         }
788                                         break;
789                         }
790
791                 }
792
793                 /// <summary>
794                 /// Ends the edit occurring on the row.
795                 /// </summary>
796                 public void EndEdit () 
797                 {
798                         if (_inChangingEvent)
799                                 throw new InRowChangingEventException("Cannot call EndEdit inside an OnRowChanging event.");
800                         if (rowState == DataRowState.Detached)
801                         {
802                                 editing = false;
803                                 return;
804                         }
805                         
806                         CheckReadOnlyStatus();
807                         if (HasVersion (DataRowVersion.Proposed))
808                         {
809                                 _inChangingEvent = true;
810                                 try
811                                 {
812                                         _table.ChangingDataRow(this, DataRowAction.Change);
813                                 }
814                                 finally
815                                 {
816                                         _inChangingEvent = false;
817                                 }
818                                 if (rowState == DataRowState.Unchanged)
819                                         rowState = DataRowState.Modified;
820                                 
821                                 //Calling next method validates UniqueConstraints
822                                 //and ForeignKeys.
823                                 try
824                                 {
825                                         if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
826                                                 _table.Rows.ValidateDataRowInternal(this);
827                                 }
828                                 catch (Exception e)
829                                 {
830                                         editing = false;
831                                         Table.RecordCache.DisposeRecord(_proposed);
832                                         _proposed = -1;
833                                         throw e;
834                                 }
835
836                                 // Now we are going to check all child rows of current row.
837                                 // In the case the cascade is true the child rows will look up for
838                                 // parent row. since lookup in index is always on current,
839                                 // we have to move proposed version of current row to current
840                                 // in the case of check child row failure we are rolling 
841                                 // current row state back.
842                                 int backup = _current;
843                                 _current = _proposed;
844                                 bool editing_backup = editing;
845                                 editing = false;
846                                 try {
847                                         // check all child rows.
848                                         CheckChildRows(DataRowAction.Change);
849                                         _proposed = -1;
850                                         if (_original != backup) {
851                                                 Table.RecordCache.DisposeRecord(backup);
852                                         }
853                                 }
854                                 catch (Exception ex) {
855                                         // if check child rows failed - rollback to previous state
856                                         // i.e. restore proposed and current versions
857                                         _proposed = _current;
858                                         _current = backup;
859                                         editing = editing_backup;
860                                         // since we failed - propagate an exception
861                                         throw ex;
862                                 }
863                                 _table.ChangedDataRow(this, DataRowAction.Change);
864                         }
865                 }
866
867                 /// <summary>
868                 /// Gets the child rows of this DataRow using the specified DataRelation.
869                 /// </summary>
870                 public DataRow[] GetChildRows (DataRelation relation) 
871                 {
872                         return GetChildRows (relation, DataRowVersion.Default);
873                 }
874
875                 /// <summary>
876                 /// Gets the child rows of a DataRow using the specified RelationName of a
877                 /// DataRelation.
878                 /// </summary>
879                 public DataRow[] GetChildRows (string relationName) 
880                 {
881                         return GetChildRows (Table.DataSet.Relations[relationName]);
882                 }
883
884                 /// <summary>
885                 /// Gets the child rows of a DataRow using the specified DataRelation, and
886                 /// DataRowVersion.
887                 /// </summary>
888                 public DataRow[] GetChildRows (DataRelation relation, DataRowVersion version) 
889                 {
890                         if (relation == null)
891                                 return new DataRow[0];
892
893                         //if (this.Table == null || RowState == DataRowState.Detached)
894                         if (this.Table == null)
895                                 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.");
896
897                         if (relation.DataSet != this.Table.DataSet)
898                                 throw new ArgumentException();
899
900                         if (_table != relation.ParentTable)
901                                 throw new InvalidConstraintException ("GetChildRow requires a row whose Table is " + relation.ParentTable + ", but the specified row's table is " + _table);
902
903                         if (relation.ChildKeyConstraint != null)
904                                 return GetChildRows (relation.ChildKeyConstraint, version);
905
906                         ArrayList rows = new ArrayList();
907                         DataColumn[] parentColumns = relation.ParentColumns;
908                         DataColumn[] childColumns = relation.ChildColumns;
909                         int numColumn = parentColumns.Length;
910                         if (HasVersion(version))
911                         {
912                                 object[] vals = new object[parentColumns.Length];
913                                 for (int i = 0; i < vals.Length; i++)
914                                         vals[i] = this[parentColumns[i], version];
915                                 
916                                 foreach (DataRow row in relation.ChildTable.Rows) 
917                                 {
918                                         bool allColumnsMatch = false;
919                                         if (row.HasVersion(DataRowVersion.Default))
920                                         {
921                                                 allColumnsMatch = true;
922                                                 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) 
923                                                 {
924                                                         if (!vals[columnCnt].Equals(
925                                                                 row[childColumns[columnCnt], DataRowVersion.Default])) 
926                                                         {
927                                                                 allColumnsMatch = false;
928                                                                 break;
929                                                         }
930                                                 }
931                                         }
932                                         if (allColumnsMatch) rows.Add(row);
933                                 }
934                         }else
935                                 throw new VersionNotFoundException("There is no " + version + " data to accces.");
936
937                         DataRow[] result = relation.ChildTable.NewRowArray(rows.Count);
938                         rows.CopyTo(result, 0);
939                         return result;
940                 }
941
942                 /// <summary>
943                 /// Gets the child rows of a DataRow using the specified RelationName of a
944                 /// DataRelation, and DataRowVersion.
945                 /// </summary>
946                 public DataRow[] GetChildRows (string relationName, DataRowVersion version) 
947                 {
948                         return GetChildRows (Table.DataSet.Relations[relationName], version);
949                 }
950
951                 private DataRow[] GetChildRows (ForeignKeyConstraint fkc, DataRowVersion version) 
952                 {
953                         ArrayList rows = new ArrayList();
954                         DataColumn[] parentColumns = fkc.RelatedColumns;
955                         DataColumn[] childColumns = fkc.Columns;
956                         int numColumn = parentColumns.Length;
957                         if (HasVersion(version)) {
958                                 Index index = fkc.Index;
959                                 if (index != null) {
960                                         // get the child rows from the index
961                                         Node[] childNodes = index.FindAllSimple (parentColumns, IndexFromVersion(version));
962                                         for (int i = 0; i < childNodes.Length; i++) {
963                                                 rows.Add (childNodes[i].Row);
964                                         }
965                                 }
966                                 else { // if there is no index we search manualy.
967                                         int curIndex = IndexFromVersion(DataRowVersion.Default);
968                                         int tmpRecord = fkc.Table.RecordCache.NewRecord();
969
970                                         try {
971                                                 for (int i = 0; i < numColumn; i++) {
972                                                         // according to MSDN: the DataType value for both columns must be identical.
973                                                         childColumns[i].DataContainer.CopyValue(parentColumns[i].DataContainer, curIndex, tmpRecord);
974                                                 }
975
976                                                 foreach (DataRow row in fkc.Table.Rows) {
977                                                         bool allColumnsMatch = false;
978                                                         if (row.HasVersion(DataRowVersion.Default)) {
979                                                                 allColumnsMatch = true;
980                                                                 int childIndex = row.IndexFromVersion(DataRowVersion.Default);
981                                                                 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) {
982                                                                         if (childColumns[columnCnt].DataContainer.CompareValues(childIndex, tmpRecord) != 0) {
983                                                                                 allColumnsMatch = false;
984                                                                                 break;
985                                                                         }
986                                                                 }
987                                                         }
988                                                         if (allColumnsMatch) {
989                                                                 rows.Add(row);
990                                                         }
991                                                 }
992                                         }
993                                         finally {
994                                                 fkc.Table.RecordCache.DisposeRecord(tmpRecord);
995                                         }
996                                 }
997                         }else
998                                 throw new VersionNotFoundException("There is no " + version + " data to accces.");
999
1000                         DataRow[] result = fkc.Table.NewRowArray(rows.Count);
1001                         rows.CopyTo(result, 0);
1002                         return result;
1003                 }
1004
1005                 /// <summary>
1006                 /// Gets the error description of the specified DataColumn.
1007                 /// </summary>
1008                 public string GetColumnError (DataColumn column) 
1009                 {
1010                         return GetColumnError (_table.Columns.IndexOf(column));
1011                 }
1012
1013                 /// <summary>
1014                 /// Gets the error description for the column specified by index.
1015                 /// </summary>
1016                 public string GetColumnError (int columnIndex) 
1017                 {
1018                         if (columnIndex < 0 || columnIndex >= Table.Columns.Count)
1019                                 throw new IndexOutOfRangeException ();
1020
1021                         string retVal = null;
1022                         if (columnIndex < ColumnErrors.Count) {
1023                                 retVal = (String) ColumnErrors[columnIndex];
1024                         }
1025                         return (retVal != null) ? retVal : String.Empty;
1026                 }
1027
1028                 /// <summary>
1029                 /// Gets the error description for the column, specified by name.
1030                 /// </summary>
1031                 public string GetColumnError (string columnName) 
1032                 {
1033                         return GetColumnError (_table.Columns.IndexOf(columnName));
1034                 }
1035
1036                 /// <summary>
1037                 /// Gets an array of columns that have errors.
1038                 /// </summary>
1039                 public DataColumn[] GetColumnsInError () 
1040                 {
1041                         ArrayList dataColumns = new ArrayList ();
1042
1043                         int columnOrdinal = 0;
1044                         foreach(String columnError in ColumnErrors) {
1045                                 if (columnError != null && columnError != String.Empty) {
1046                                         dataColumns.Add (_table.Columns[columnOrdinal]);
1047                                 }
1048                                 columnOrdinal++;
1049                         }
1050
1051                         return (DataColumn[])(dataColumns.ToArray (typeof(DataColumn)));
1052                 }
1053
1054                 /// <summary>
1055                 /// Gets the parent row of a DataRow using the specified DataRelation.
1056                 /// </summary>
1057                 public DataRow GetParentRow (DataRelation relation) 
1058                 {
1059                         return GetParentRow (relation, DataRowVersion.Default);
1060                 }
1061
1062                 /// <summary>
1063                 /// Gets the parent row of a DataRow using the specified RelationName of a
1064                 /// DataRelation.
1065                 /// </summary>
1066                 public DataRow GetParentRow (string relationName) 
1067                 {
1068                         return GetParentRow (relationName, DataRowVersion.Default);
1069                 }
1070
1071                 /// <summary>
1072                 /// Gets the parent row of a DataRow using the specified DataRelation, and
1073                 /// DataRowVersion.
1074                 /// </summary>
1075                 public DataRow GetParentRow (DataRelation relation, DataRowVersion version) 
1076                 {
1077                         DataRow[] rows = GetParentRows(relation, version);
1078                         if (rows.Length == 0) return null;
1079                         return rows[0];
1080                 }
1081
1082                 /// <summary>
1083                 /// Gets the parent row of a DataRow using the specified RelationName of a 
1084                 /// DataRelation, and DataRowVersion.
1085                 /// </summary>
1086                 public DataRow GetParentRow (string relationName, DataRowVersion version) 
1087                 {
1088                         return GetParentRow (Table.DataSet.Relations[relationName], version);
1089                 }
1090
1091                 /// <summary>
1092                 /// Gets the parent rows of a DataRow using the specified DataRelation.
1093                 /// </summary>
1094                 public DataRow[] GetParentRows (DataRelation relation) 
1095                 {
1096                         return GetParentRows (relation, DataRowVersion.Default);
1097                 }
1098
1099                 /// <summary>
1100                 /// Gets the parent rows of a DataRow using the specified RelationName of a 
1101                 /// DataRelation.
1102                 /// </summary>
1103                 public DataRow[] GetParentRows (string relationName) 
1104                 {
1105                         return GetParentRows (relationName, DataRowVersion.Default);
1106                 }
1107
1108                 /// <summary>
1109                 /// Gets the parent rows of a DataRow using the specified DataRelation, and
1110                 /// DataRowVersion.
1111                 /// </summary>
1112                 public DataRow[] GetParentRows (DataRelation relation, DataRowVersion version) 
1113                 {
1114                         // TODO: Caching for better preformance
1115                         if (relation == null)
1116                                 return new DataRow[0];
1117
1118                         if (this.Table == null)
1119                                 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.");
1120
1121                         if (relation.DataSet != this.Table.DataSet)
1122                                 throw new ArgumentException();
1123
1124                         if (_table != relation.ChildTable)
1125                                 throw new InvalidConstraintException ("GetParentRows requires a row whose Table is " + relation.ChildTable + ", but the specified row's table is " + _table);
1126
1127                         ArrayList rows = new ArrayList();
1128                         DataColumn[] parentColumns = relation.ParentColumns;
1129                         DataColumn[] childColumns = relation.ChildColumns;
1130                         int numColumn = parentColumns.Length;
1131                         if (HasVersion(version)) {
1132                                 Index indx = relation.ParentTable.GetIndexByColumns (parentColumns);
1133                                 if (indx != null && 
1134     (Table == null || Table.DataSet == null || 
1135      Table.DataSet.EnforceConstraints)) { // get the child rows from the index
1136                                         Node[] childNodes = indx.FindAllSimple(childColumns, IndexFromVersion(version));
1137                                         for (int i = 0; i < childNodes.Length; i++) {
1138                                                 rows.Add (childNodes[i].Row);
1139                                         }
1140                                 }
1141                                 else { // no index so we have to search manualy.
1142                                         int curIndex = IndexFromVersion(DataRowVersion.Default);
1143                                         int tmpRecord = relation.ParentTable.RecordCache.NewRecord();
1144                                         try {
1145                                                 for (int i = 0; i < numColumn; i++) {
1146                                                         // according to MSDN: the DataType value for both columns must be identical.
1147                                                         parentColumns[i].DataContainer.CopyValue(childColumns[i].DataContainer, curIndex, tmpRecord);
1148                                                 }
1149
1150                                                 foreach (DataRow row in relation.ParentTable.Rows) {
1151                                                         bool allColumnsMatch = false;
1152                                                         if (row.HasVersion(DataRowVersion.Default)) {
1153                                                                 allColumnsMatch = true;
1154                                                                 int parentIndex = row.IndexFromVersion(DataRowVersion.Default);
1155                                                                 for (int columnCnt = 0; columnCnt < numColumn; columnCnt++) {
1156                                                                         if (parentColumns[columnCnt].DataContainer.CompareValues(parentIndex, tmpRecord) != 0) {
1157                                                                                 allColumnsMatch = false;
1158                                                                                 break;
1159                                                                         }
1160                                                                 }
1161                                                         }
1162                                                         if (allColumnsMatch) {
1163                                                                 rows.Add(row);
1164                                                         }
1165                                                 }
1166                                         }
1167                                         finally {
1168                                                 relation.ParentTable.RecordCache.DisposeRecord(tmpRecord);
1169                                         }
1170                                 }
1171                         }else
1172                                 throw new VersionNotFoundException("There is no " + version + " data to accces.");
1173
1174                         DataRow[] result = relation.ParentTable.NewRowArray(rows.Count);
1175                         rows.CopyTo(result, 0);
1176                         return result;
1177                 }
1178
1179                 /// <summary>
1180                 /// Gets the parent rows of a DataRow using the specified RelationName of a 
1181                 /// DataRelation, and DataRowVersion.
1182                 /// </summary>
1183                 public DataRow[] GetParentRows (string relationName, DataRowVersion version) 
1184                 {
1185                         return GetParentRows (Table.DataSet.Relations[relationName], version);
1186                 }
1187
1188                 /// <summary>
1189                 /// Gets a value indicating whether a specified version exists.
1190                 /// </summary>
1191                 public bool HasVersion (DataRowVersion version) 
1192                 {
1193                         switch (version) {
1194                         case DataRowVersion.Default:
1195                                 if (rowState == DataRowState.Deleted && !_inExpressionEvaluation)
1196                                         return false;
1197                                 if (rowState == DataRowState.Detached)
1198                                         return _proposed >= 0;
1199                                 return true;
1200                         case DataRowVersion.Proposed:
1201                                 if (rowState == DataRowState.Deleted && !_inExpressionEvaluation)
1202                                         return false;
1203                                 return _proposed >= 0;
1204                         case DataRowVersion.Current:
1205                                 if ((rowState == DataRowState.Deleted && !_inExpressionEvaluation) || rowState == DataRowState.Detached)
1206                                         return false;
1207                                 return _current >= 0;
1208                         case DataRowVersion.Original:
1209                                 if (rowState == DataRowState.Detached)
1210                                         return false;
1211                                 return _original >= 0;
1212                         }
1213                         return false;
1214                 }
1215
1216                 /// <summary>
1217                 /// Gets a value indicating whether the specified DataColumn contains a null value.
1218                 /// </summary>
1219                 public bool IsNull (DataColumn column) 
1220                 {
1221                         return IsNull(column, DataRowVersion.Default);
1222                 }
1223
1224                 /// <summary>
1225                 /// Gets a value indicating whether the column at the specified index contains a null
1226                 /// value.
1227                 /// </summary>
1228                 public bool IsNull (int columnIndex) 
1229                 {
1230                         return IsNull(Table.Columns[columnIndex]);
1231                 }
1232
1233                 /// <summary>
1234                 /// Gets a value indicating whether the named column contains a null value.
1235                 /// </summary>
1236                 public bool IsNull (string columnName) 
1237                 {
1238                         return IsNull(Table.Columns[columnName]);
1239                 }
1240
1241                 /// <summary>
1242                 /// Gets a value indicating whether the specified DataColumn and DataRowVersion
1243                 /// contains a null value.
1244                 /// </summary>
1245                 public bool IsNull (DataColumn column, DataRowVersion version) 
1246                 {
1247                         return column.DataContainer.IsNull(IndexFromVersion(version));
1248                 }
1249
1250                 /// <summary>
1251                 /// Returns a value indicating whether all of the row columns specified contain a null value.
1252                 /// </summary>
1253                 internal bool IsNullColumns(DataColumn[] columns)
1254                 {
1255                         bool allNull = true;
1256                         for (int i = 0; i < columns.Length; i++) 
1257                         {
1258                                 if (!IsNull(columns[i])) 
1259                                 {
1260                                         allNull = false;
1261                                         break;
1262                                 }
1263                         }
1264                         return allNull;
1265                 }
1266
1267                 /// <summary>
1268                 /// Rejects all changes made to the row since AcceptChanges was last called.
1269                 /// </summary>
1270                 public void RejectChanges () 
1271                 {
1272                         if (RowState == DataRowState.Detached)
1273                                 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.");
1274                         // If original is null, then nothing has happened since AcceptChanges
1275                         // was last called.  We have no "original" to go back to.
1276                         if (HasVersion(DataRowVersion.Original)) {
1277                                 if (_current >= 0 ) {
1278                                         Table.RecordCache.DisposeRecord(_current);
1279                                 }
1280                                 _current = _original;
1281                                
1282                                 _table.ChangedDataRow (this, DataRowAction.Rollback);
1283                                 CancelEdit ();
1284                                 switch (rowState)
1285                                 {
1286                                         case DataRowState.Added:
1287                                                 _table.DeleteRowFromIndexes (this);
1288                                                 _table.Rows.RemoveInternal (this);
1289                                                 break;
1290                                         case DataRowState.Modified:
1291                                                 if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
1292                                                         _table.Rows.ValidateDataRowInternal(this);
1293                                                 rowState = DataRowState.Unchanged;
1294                                                 break;
1295                                         case DataRowState.Deleted:
1296                                                 rowState = DataRowState.Unchanged;
1297                                                 if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
1298                                                         _table.Rows.ValidateDataRowInternal(this);
1299                                                 break;
1300                                 } 
1301                                 
1302                         }                       
1303                         else {
1304                                 // If rows are just loaded via Xml the original values are null.
1305                                 // So in this case we have to remove all columns.
1306                                 // FIXME: I'm not realy sure, does this break something else, but
1307                                 // if so: FIXME ;)
1308                                 
1309                                 if ((rowState & DataRowState.Added) > 0)
1310                                 {
1311                                         _table.DeleteRowFromIndexes (this);
1312                                         _table.Rows.RemoveInternal (this);
1313                                         // if row was in Added state we move it to Detached.
1314                                         DetachRow();
1315                                 }
1316                         }
1317                 }
1318
1319                 /// <summary>
1320                 /// Sets the error description for a column specified as a DataColumn.
1321                 /// </summary>
1322                 public void SetColumnError (DataColumn column, string error) 
1323                 {
1324                         SetColumnError (_table.Columns.IndexOf (column), error);
1325                 }
1326
1327                 /// <summary>
1328                 /// Sets the error description for a column specified by index.
1329                 /// </summary>
1330                 public void SetColumnError (int columnIndex, string error) 
1331                 {
1332                         if (columnIndex < 0 || columnIndex >= Table.Columns.Count)
1333                                 throw new IndexOutOfRangeException ();
1334
1335                         while(ColumnErrors.Count < columnIndex) {
1336                                 ColumnErrors.Add(null);
1337                         }
1338                         ColumnErrors.Add(error);
1339                 }
1340
1341                 /// <summary>
1342                 /// Sets the error description for a column specified by name.
1343                 /// </summary>
1344                 public void SetColumnError (string columnName, string error) 
1345                 {
1346                         SetColumnError (_table.Columns.IndexOf (columnName), error);
1347                 }
1348
1349                 /// <summary>
1350                 /// Sets the value of the specified DataColumn to a null value.
1351                 /// </summary>
1352                 protected void SetNull (DataColumn column) 
1353                 {
1354                         this[column] = DBNull.Value;
1355                 }
1356
1357                 /// <summary>
1358                 /// Sets the parent row of a DataRow with specified new parent DataRow.
1359                 /// </summary>
1360                 public void SetParentRow (DataRow parentRow) 
1361                 {
1362                         SetParentRow(parentRow, null);
1363                 }
1364
1365                 /// <summary>
1366                 /// Sets the parent row of a DataRow with specified new parent DataRow and
1367                 /// DataRelation.
1368                 /// </summary>
1369                 public void SetParentRow (DataRow parentRow, DataRelation relation) 
1370                 {
1371                         if (_table == null || parentRow.Table == null)
1372                                 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.");
1373
1374                         if (parentRow != null && _table.DataSet != parentRow.Table.DataSet)
1375                                 throw new ArgumentException();
1376                         
1377                         BeginEdit();
1378                         if (relation == null)
1379                         {
1380                                 foreach (DataRelation parentRel in _table.ParentRelations)
1381                                 {
1382                                         DataColumn[] childCols = parentRel.ChildColumns;
1383                                         DataColumn[] parentCols = parentRel.ParentColumns;
1384                                         
1385                                         for (int i = 0; i < parentCols.Length; i++)
1386                                         {
1387                                                 if (parentRow == null)
1388                                                         this[childCols[i].Ordinal] = DBNull.Value;
1389                                                 else
1390                                                         this[childCols[i].Ordinal] = parentRow[parentCols[i]];
1391                                         }
1392                                         
1393                                 }
1394                         }
1395                         else
1396                         {
1397                                 DataColumn[] childCols = relation.ChildColumns;
1398                                 DataColumn[] parentCols = relation.ParentColumns;
1399                                         
1400                                 for (int i = 0; i < parentCols.Length; i++)
1401                                 {
1402                                         if (parentRow == null)
1403                                                 this[childCols[i].Ordinal] = DBNull.Value;
1404                                         else
1405                                                 this[childCols[i].Ordinal] = parentRow[parentCols[i]];
1406                                 }
1407                         }
1408                         EndEdit();
1409                 }
1410                 
1411                 //Copy all values of this DataaRow to the row parameter.
1412                 internal void CopyValuesToRow(DataRow row)
1413                 {
1414                         if (row == null)
1415                                 throw new ArgumentNullException("row");
1416                         if (row == this)
1417                                 throw new ArgumentException("'row' is the same as this object");
1418
1419                         foreach(DataColumn column in Table.Columns) {
1420                                 DataColumn targetColumn = row.Table.Columns[column.ColumnName];
1421                                 //if a column with the same name exists in both rows copy the values
1422                                 if(targetColumn != null) {
1423                                         int index = targetColumn.Ordinal;
1424                                         if (HasVersion(DataRowVersion.Original)) {
1425                                                 if (row._original < 0) {
1426                                                         row._original = row.Table.RecordCache.NewRecord();
1427                                                 }
1428                                                 object val = column[_original];
1429                                                 row.CheckValue(val, targetColumn);
1430                                                 targetColumn[row._original] = val;
1431                                         }
1432                                         if (HasVersion(DataRowVersion.Current)) {
1433                                                 if (row._current < 0) {
1434                                                         row._current = row.Table.RecordCache.NewRecord();
1435                                                 }
1436                                                 object val = column[_current];
1437                                                 row.CheckValue(val, targetColumn);
1438                                                 targetColumn[row._current] = val;
1439                                         }
1440                                         if (HasVersion(DataRowVersion.Proposed)) {
1441                                                 if (row._proposed < 0) {
1442                                                         row._proposed = row.Table.RecordCache.NewRecord();
1443                                                 }
1444                                                 object val = column[row._proposed];
1445                                                 row.CheckValue(val, targetColumn);
1446                                                 targetColumn[row._proposed] = val;
1447                                         }
1448                                         
1449                                         //Saving the current value as the column value
1450                                         object defaultVal = column [IndexFromVersion (DataRowVersion.Default)];
1451                                         row [index] = defaultVal;
1452                                 }
1453                         }
1454                         CopyState(row);
1455                 }
1456
1457                 // Copy row state - rowState and errors
1458                 internal void CopyState(DataRow row)
1459                 {
1460                         row.rowState = RowState;
1461                         row.RowError = RowError;
1462                         row.ColumnErrors = (ArrayList)ColumnErrors.Clone();
1463                 }
1464
1465                 internal bool IsRowChanged(DataRowState rowState) {
1466                         if((RowState & rowState) != 0)
1467                                 return true;
1468
1469                         //we need to find if child rows of this row changed.
1470                         //if yes - we should return true
1471
1472                         // if the rowState is deleted we should get the original version of the row
1473                         // else - we should get the current version of the row.
1474                         DataRowVersion version = (rowState == DataRowState.Deleted) ? DataRowVersion.Original : DataRowVersion.Current;
1475                         int count = Table.ChildRelations.Count;
1476                         for (int i = 0; i < count; i++){
1477                                 DataRelation rel = Table.ChildRelations[i];
1478                                 DataRow[] childRows = GetChildRows(rel, version);
1479                                 for (int j = 0; j < childRows.Length; j++){
1480                                         if (childRows[j].IsRowChanged(rowState))
1481                                                 return true;
1482                                 }
1483                         }
1484
1485                         return false;
1486                 }
1487
1488                 internal bool HasParentCollection
1489                 {
1490                         get
1491                         {
1492                                 return _hasParentCollection;
1493                         }
1494                         set
1495                         {
1496                                 _hasParentCollection = value;
1497                         }
1498                 }
1499
1500                 internal void CheckNullConstraints()
1501                 {
1502                         if (_nullConstraintViolation) {
1503                                 if (HasVersion(DataRowVersion.Proposed)) {
1504                                         foreach(DataColumn column in Table.Columns) {
1505                                                 if (IsNull(column) && !column.AllowDBNull) {
1506                                                         throw new NoNullAllowedException(_nullConstraintMessage);
1507                                         }
1508                                 }
1509                                 }
1510                                 _nullConstraintViolation = false;
1511                         }
1512                 }
1513                 
1514                 internal void CheckReadOnlyStatus()
1515                 {
1516                         if (HasVersion(DataRowVersion.Proposed)) {
1517                                 int defaultIdx = IndexFromVersion(DataRowVersion.Default); 
1518                                 foreach(DataColumn column in Table.Columns) {
1519                                         if ((column.DataContainer.CompareValues(defaultIdx,_proposed) != 0) && column.ReadOnly) {
1520                                         throw new ReadOnlyException();
1521                         }
1522                 }
1523                         }                       
1524                 }
1525         
1526                 #endregion // Methods
1527
1528 #if NET_2_0
1529                 /// <summary>
1530                 ///    This method loads a given value into the existing row affecting versions,
1531                 ///    state based on the LoadOption.  The matrix of changes for this method are as
1532                 ///    mentioned in the DataTable.Load (IDataReader, LoadOption) method.
1533                 /// </summary>
1534                 [MonoTODO ("Raise necessary Events")]
1535                 internal void Load (object [] values, LoadOption loadOption, bool is_new)
1536                 {
1537                         DataRowAction action = DataRowAction.Change;
1538
1539                         int temp = Table.RecordCache.NewRecord ();
1540                         for (int i = 0 ; i < Table.Columns.Count; i++)
1541                                 SetValue (i, values [i], temp);
1542
1543                         if (is_new) { // new row
1544                                 if (editing || RowState == DataRowState.Detached)
1545                                         Proposed = temp;
1546                                 else
1547                                         Current = temp;
1548                                 return;
1549                         }
1550
1551                         if (loadOption == LoadOption.OverwriteChanges 
1552                             || (loadOption == LoadOption.PreserveChanges
1553                                 && rowState == DataRowState.Unchanged)) {
1554                                 Original = temp;
1555                                 if (editing)
1556                                         Proposed = temp;
1557                                 else
1558                                         Current = temp;
1559                                 rowState = DataRowState.Unchanged;
1560                                 action = DataRowAction.ChangeCurrentAndOriginal;
1561                                 return;
1562                         }
1563
1564                         if (loadOption == LoadOption.PreserveChanges) {
1565                                 if (rowState != DataRowState.Deleted) {
1566                                         Original = temp;
1567                                         rowState = DataRowState.Modified;
1568                                         action   = DataRowAction.ChangeOriginal;
1569                                 }
1570                                 return;
1571                         }
1572                                 
1573                         bool not_used = true;
1574                         // Upsert
1575                         if (rowState != DataRowState.Deleted) {
1576                                 int index = editing ? _proposed : _current;
1577                                 if (! RecordCache.CompareRecords (Table, index, temp)) {
1578                                         if (editing)
1579                                                 Proposed = temp;
1580                                         else
1581                                                 Current = temp;
1582                                         not_used = false;
1583                                         if (rowState == DataRowState.Unchanged)
1584                                                 rowState = DataRowState.Modified;
1585                                 }
1586                         }
1587                                 
1588                         if (not_used)
1589                                 Table.RecordCache.DisposeRecord (temp);
1590                 }
1591 #endif // NET_2_0
1592         }
1593
1594         
1595
1596 }