System.Data/DataRow.cs: AcceptChanges: raise row changing & row changed events.
[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
594                         _table.ChangingDataRow (this, DataRowAction.Commit);
595                         switch (rowState) {
596                                 case DataRowState.Unchanged:
597                                         return;
598                         case DataRowState.Added:
599                         case DataRowState.Modified:
600                                 rowState = DataRowState.Unchanged;
601                                 break;
602                         case DataRowState.Deleted:
603                                 _table.Rows.RemoveInternal (this);
604                                 DetachRow();
605                                 break;
606                         case DataRowState.Detached:
607                                 throw new RowNotInTableException("Cannot perform this operation on a row not in the table.");
608                         }
609                         // Accept from detached
610                         if (_original != _current)
611                                 Original = Current;
612
613                         _table.ChangedDataRow (this, DataRowAction.Commit);
614                 }
615
616                 /// <summary>
617                 /// Begins an edit operation on a DataRow object.
618                 /// </summary>
619                 public void BeginEdit () 
620                 {
621                         if (_inChangingEvent)
622                                 throw new InRowChangingEventException("Cannot call BeginEdit inside an OnRowChanging event.");
623                         if (rowState == DataRowState.Deleted)
624                                 throw new DeletedRowInaccessibleException ();
625                         if (!HasVersion (DataRowVersion.Proposed)) {
626                                 _proposed = Table.RecordCache.NewRecord();
627                                 foreach(DataColumn column in Table.Columns) {
628                                         column.DataContainer.CopyValue(_current,_proposed);
629                                 }
630                         }
631                         // setting editing to true stops validations on the row
632                         editing = true;
633                 }
634
635                 /// <summary>
636                 /// Cancels the current edit on the row.
637                 /// </summary>
638                 public void CancelEdit () 
639                 {
640                          if (_inChangingEvent)
641                                 throw new InRowChangingEventException("Cannot call CancelEdit inside an OnRowChanging event.");
642                         editing = false;
643                         if (HasVersion (DataRowVersion.Proposed)) {
644                                 Table.RecordCache.DisposeRecord(_proposed);
645                                 _proposed = -1;
646                                 if (rowState == DataRowState.Modified) {
647                                     rowState = DataRowState.Unchanged;
648                                 }
649                         }
650                 }
651
652                 /// <summary>
653                 /// Clears the errors for the row, including the RowError and errors set with
654                 /// SetColumnError.
655                 /// </summary>
656                 public void ClearErrors () 
657                 {
658                         rowError = String.Empty;
659                         ColumnErrors.Clear();
660                 }
661
662                 /// <summary>
663                 /// Deletes the DataRow.
664                 /// </summary>
665                 public void Delete () 
666                 {
667                         _table.DeletingDataRow(this, DataRowAction.Delete);
668                         switch (rowState) {
669                         case DataRowState.Added:
670                                 // check what to do with child rows
671                                 CheckChildRows(DataRowAction.Delete);
672                                 _table.DeleteRowFromIndexes (this);
673                                 Table.Rows.RemoveInternal (this);
674
675                                 // if row was in Added state we move it to Detached.
676                                 DetachRow();
677                                 break;
678                         case DataRowState.Deleted:
679                                 break;          
680                         default:
681                                 // check what to do with child rows
682                                 CheckChildRows(DataRowAction.Delete);
683                                 _table.DeleteRowFromIndexes (this);
684                                 rowState = DataRowState.Deleted;
685                                 break;
686                         }
687                         _table.DeletedDataRow(this, DataRowAction.Delete);
688                 }
689
690                 // check the child rows of this row before deleting the row.
691                 private void CheckChildRows(DataRowAction action)
692                 {
693                         
694                         // in this method we find the row that this row is in a relation with them.
695                         // in shortly we find all child rows of this row.
696                         // then we function according to the DeleteRule of the foriegnkey.
697
698
699                         // 1. find if this row is attached to dataset.
700                         // 2. find if EnforceConstraints is true.
701                         // 3. find if there are any constraint on the table that the row is in.
702                         if (_table.DataSet != null && _table.DataSet.EnforceConstraints && _table.Constraints.Count > 0)
703                         {
704                                 foreach (DataTable table in _table.DataSet.Tables)
705                                 {
706                                         // loop on all ForeignKeyConstrain of the table.
707                                         foreach (ForeignKeyConstraint fk in table.Constraints.ForeignKeyConstraints)
708                                         {
709                                                 if (fk.RelatedTable == _table)
710                                                 {
711                                                         Rule rule;
712                                                         if (action == DataRowAction.Delete)
713                                                                 rule = fk.DeleteRule;
714                                                         else
715                                                                 rule = fk.UpdateRule;
716                                                         CheckChildRows(fk, action, rule);
717                                                 }                       
718                                         }
719                                 }
720                         }
721                 }
722
723                 private void CheckChildRows(ForeignKeyConstraint fkc, DataRowAction action, Rule rule)
724                 {                               
725                         DataRow[] childRows = GetChildRows(fkc, DataRowVersion.Default);
726                         switch (rule)
727                         {
728                                 case Rule.Cascade:  // delete or change all relted rows.
729                                         if (childRows != null)
730                                         {
731                                                 for (int j = 0; j < childRows.Length; j++)
732                                                 {
733                                                         // if action is delete we delete all child rows
734                                                         if (action == DataRowAction.Delete)
735                                                         {
736                                                                 if (childRows[j].RowState != DataRowState.Deleted)
737                                                                         childRows[j].Delete();
738                                                         }
739                                                         // if action is change we change the values in the child row
740                                                         else if (action == DataRowAction.Change)
741                                                         {
742                                                                 // change only the values in the key columns
743                                                                 // set the childcolumn value to the new parent row value
744                                                                 for (int k = 0; k < fkc.Columns.Length; k++)
745                                                                         childRows[j][fkc.Columns[k]] = this[fkc.RelatedColumns[k], DataRowVersion.Proposed];
746                                                         }
747                                                 }
748                                         }
749                                         break;
750                                 case Rule.None: // throw an exception if there are any child rows.
751                                         if (childRows != null)
752                                         {
753                                                 for (int j = 0; j < childRows.Length; j++)
754                                                 {
755                                                         if (childRows[j].RowState != DataRowState.Deleted)
756                                                         {
757                                                                 string changeStr = "Cannot change this row because constraints are enforced on relation " + fkc.ConstraintName +", and changing this row will strand child rows.";
758                                                                 string delStr = "Cannot delete this row because constraints are enforced on relation " + fkc.ConstraintName +", and deleting this row will strand child rows.";
759                                                                 string message = action == DataRowAction.Delete ? delStr : changeStr;
760                                                                 throw new InvalidConstraintException(message);
761                                                         }
762                                                 }
763                                         }
764                                         break;
765                                 case Rule.SetDefault: // set the values in the child rows to the defult value of the columns.
766                                         if (childRows != null && childRows.Length > 0) {
767                                                 int defaultValuesRowIndex = childRows[0].Table.DefaultValuesRowIndex;
768                                                 foreach(DataRow childRow in childRows) {
769                                                         if (childRow.RowState != DataRowState.Deleted) {
770                                                                 int defaultIdx = childRow.IndexFromVersion(DataRowVersion.Default);
771                                                                 foreach(DataColumn column in fkc.Columns) {
772                                                                         column.DataContainer.CopyValue(defaultValuesRowIndex,defaultIdx);
773                                                                 }
774                                                         }
775                                                 }
776                                         }
777                                         break;
778                                 case Rule.SetNull: // set the values in the child row to null.
779                                         if (childRows != null)
780                                         {
781                                                 for (int j = 0; j < childRows.Length; j++)
782                                                 {
783                                                         DataRow child = childRows[j];
784                                                         if (childRows[j].RowState != DataRowState.Deleted)
785                                                         {
786                                                                 // set only the key columns to DBNull
787                                                                 for (int k = 0; k < fkc.Columns.Length; k++)
788                                                                         child.SetNull(fkc.Columns[k]);
789                                                         }
790                                                 }
791                                         }
792                                         break;
793                         }
794
795                 }
796
797                 /// <summary>
798                 /// Ends the edit occurring on the row.
799                 /// </summary>
800                 public void EndEdit () 
801                 {
802                         if (_inChangingEvent)
803                                 throw new InRowChangingEventException("Cannot call EndEdit inside an OnRowChanging event.");
804                         if (rowState == DataRowState.Detached)
805                         {
806                                 editing = false;
807                                 return;
808                         }
809                         
810                         CheckReadOnlyStatus();
811                         if (HasVersion (DataRowVersion.Proposed))
812                         {
813                                 _inChangingEvent = true;
814                                 try
815                                 {
816                                         _table.ChangingDataRow(this, DataRowAction.Change);
817                                 }
818                                 finally
819                                 {
820                                         _inChangingEvent = false;
821                                 }
822                                 if (rowState == DataRowState.Unchanged)
823                                         rowState = DataRowState.Modified;
824                                 
825                                 //Calling next method validates UniqueConstraints
826                                 //and ForeignKeys.
827                                 try
828                                 {
829                                         if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
830                                                 _table.Rows.ValidateDataRowInternal(this);
831                                 }
832                                 catch (Exception e)
833                                 {
834                                         editing = false;
835                                         Table.RecordCache.DisposeRecord(_proposed);
836                                         _proposed = -1;
837                                         throw e;
838                                 }
839
840                                 // Now we are going to check all child rows of current row.
841                                 // In the case the cascade is true the child rows will look up for
842                                 // parent row. since lookup in index is always on current,
843                                 // we have to move proposed version of current row to current
844                                 // in the case of check child row failure we are rolling 
845                                 // current row state back.
846                                 int backup = _current;
847                                 _current = _proposed;
848                                 bool editing_backup = editing;
849                                 editing = false;
850                                 try {
851                                         // check all child rows.
852                                         CheckChildRows(DataRowAction.Change);
853                                         _proposed = -1;
854                                         if (_original != backup) {
855                                                 Table.RecordCache.DisposeRecord(backup);
856                                         }
857                                 }
858                                 catch (Exception ex) {
859                                         // if check child rows failed - rollback to previous state
860                                         // i.e. restore proposed and current versions
861                                         _proposed = _current;
862                                         _current = backup;
863                                         editing = editing_backup;
864                                         // since we failed - propagate an exception
865                                         throw ex;
866                                 }
867                                 _table.ChangedDataRow(this, DataRowAction.Change);
868                         }
869                 }
870
871                 /// <summary>
872                 /// Gets the child rows of this DataRow using the specified DataRelation.
873                 /// </summary>
874                 public DataRow[] GetChildRows (DataRelation relation) 
875                 {
876                         return GetChildRows (relation, DataRowVersion.Default);
877                 }
878
879                 /// <summary>
880                 /// Gets the child rows of a DataRow using the specified RelationName of a
881                 /// DataRelation.
882                 /// </summary>
883                 public DataRow[] GetChildRows (string relationName) 
884                 {
885                         return GetChildRows (Table.DataSet.Relations[relationName]);
886                 }
887
888                 /// <summary>
889                 /// Gets the child rows of a DataRow using the specified DataRelation, and
890                 /// DataRowVersion.
891                 /// </summary>
892                 public DataRow[] GetChildRows (DataRelation relation, DataRowVersion version) 
893                 {
894                         if (relation == null)
895                                 return new DataRow[0];
896
897                         //if (this.Table == null || RowState == DataRowState.Detached)
898                         if (this.Table == null)
899                                 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.");
900
901                         if (relation.DataSet != this.Table.DataSet)
902                                 throw new ArgumentException();
903
904                         if (_table != relation.ParentTable)
905                                 throw new InvalidConstraintException ("GetChildRow requires a row whose Table is " + relation.ParentTable + ", but the specified row's table is " + _table);
906
907                         if (relation.ChildKeyConstraint != null)
908                                 return GetChildRows (relation.ChildKeyConstraint, version);
909
910                         ArrayList rows = new ArrayList();
911                         DataColumn[] parentColumns = relation.ParentColumns;
912                         DataColumn[] childColumns = relation.ChildColumns;
913                         int numColumn = parentColumns.Length;
914                         if (HasVersion(version))
915                         {
916                                 object[] vals = new object[parentColumns.Length];
917                                 for (int i = 0; i < vals.Length; i++)
918                                         vals[i] = this[parentColumns[i], version];
919                                 
920                                 foreach (DataRow row in relation.ChildTable.Rows) 
921                                 {
922                                         bool allColumnsMatch = false;
923                                         if (row.HasVersion(DataRowVersion.Default))
924                                         {
925                                                 allColumnsMatch = true;
926                                                 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) 
927                                                 {
928                                                         if (!vals[columnCnt].Equals(
929                                                                 row[childColumns[columnCnt], DataRowVersion.Default])) 
930                                                         {
931                                                                 allColumnsMatch = false;
932                                                                 break;
933                                                         }
934                                                 }
935                                         }
936                                         if (allColumnsMatch) rows.Add(row);
937                                 }
938                         }else
939                                 throw new VersionNotFoundException("There is no " + version + " data to accces.");
940
941                         DataRow[] result = relation.ChildTable.NewRowArray(rows.Count);
942                         rows.CopyTo(result, 0);
943                         return result;
944                 }
945
946                 /// <summary>
947                 /// Gets the child rows of a DataRow using the specified RelationName of a
948                 /// DataRelation, and DataRowVersion.
949                 /// </summary>
950                 public DataRow[] GetChildRows (string relationName, DataRowVersion version) 
951                 {
952                         return GetChildRows (Table.DataSet.Relations[relationName], version);
953                 }
954
955                 private DataRow[] GetChildRows (ForeignKeyConstraint fkc, DataRowVersion version) 
956                 {
957                         ArrayList rows = new ArrayList();
958                         DataColumn[] parentColumns = fkc.RelatedColumns;
959                         DataColumn[] childColumns = fkc.Columns;
960                         int numColumn = parentColumns.Length;
961                         if (HasVersion(version)) {
962                                 Index index = fkc.Index;
963                                 if (index != null) {
964                                         // get the child rows from the index
965                                         Node[] childNodes = index.FindAllSimple (parentColumns, IndexFromVersion(version));
966                                         for (int i = 0; i < childNodes.Length; i++) {
967                                                 rows.Add (childNodes[i].Row);
968                                         }
969                                 }
970                                 else { // if there is no index we search manualy.
971                                         int curIndex = IndexFromVersion(DataRowVersion.Default);
972                                         int tmpRecord = fkc.Table.RecordCache.NewRecord();
973
974                                         try {
975                                                 for (int i = 0; i < numColumn; i++) {
976                                                         // according to MSDN: the DataType value for both columns must be identical.
977                                                         childColumns[i].DataContainer.CopyValue(parentColumns[i].DataContainer, curIndex, tmpRecord);
978                                                 }
979
980                                                 foreach (DataRow row in fkc.Table.Rows) {
981                                                         bool allColumnsMatch = false;
982                                                         if (row.HasVersion(DataRowVersion.Default)) {
983                                                                 allColumnsMatch = true;
984                                                                 int childIndex = row.IndexFromVersion(DataRowVersion.Default);
985                                                                 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) {
986                                                                         if (childColumns[columnCnt].DataContainer.CompareValues(childIndex, tmpRecord) != 0) {
987                                                                                 allColumnsMatch = false;
988                                                                                 break;
989                                                                         }
990                                                                 }
991                                                         }
992                                                         if (allColumnsMatch) {
993                                                                 rows.Add(row);
994                                                         }
995                                                 }
996                                         }
997                                         finally {
998                                                 fkc.Table.RecordCache.DisposeRecord(tmpRecord);
999                                         }
1000                                 }
1001                         }else
1002                                 throw new VersionNotFoundException("There is no " + version + " data to accces.");
1003
1004                         DataRow[] result = fkc.Table.NewRowArray(rows.Count);
1005                         rows.CopyTo(result, 0);
1006                         return result;
1007                 }
1008
1009                 /// <summary>
1010                 /// Gets the error description of the specified DataColumn.
1011                 /// </summary>
1012                 public string GetColumnError (DataColumn column) 
1013                 {
1014                         return GetColumnError (_table.Columns.IndexOf(column));
1015                 }
1016
1017                 /// <summary>
1018                 /// Gets the error description for the column specified by index.
1019                 /// </summary>
1020                 public string GetColumnError (int columnIndex) 
1021                 {
1022                         if (columnIndex < 0 || columnIndex >= Table.Columns.Count)
1023                                 throw new IndexOutOfRangeException ();
1024
1025                         string retVal = null;
1026                         if (columnIndex < ColumnErrors.Count) {
1027                                 retVal = (String) ColumnErrors[columnIndex];
1028                         }
1029                         return (retVal != null) ? retVal : String.Empty;
1030                 }
1031
1032                 /// <summary>
1033                 /// Gets the error description for the column, specified by name.
1034                 /// </summary>
1035                 public string GetColumnError (string columnName) 
1036                 {
1037                         return GetColumnError (_table.Columns.IndexOf(columnName));
1038                 }
1039
1040                 /// <summary>
1041                 /// Gets an array of columns that have errors.
1042                 /// </summary>
1043                 public DataColumn[] GetColumnsInError () 
1044                 {
1045                         ArrayList dataColumns = new ArrayList ();
1046
1047                         int columnOrdinal = 0;
1048                         foreach(String columnError in ColumnErrors) {
1049                                 if (columnError != null && columnError != String.Empty) {
1050                                         dataColumns.Add (_table.Columns[columnOrdinal]);
1051                                 }
1052                                 columnOrdinal++;
1053                         }
1054
1055                         return (DataColumn[])(dataColumns.ToArray (typeof(DataColumn)));
1056                 }
1057
1058                 /// <summary>
1059                 /// Gets the parent row of a DataRow using the specified DataRelation.
1060                 /// </summary>
1061                 public DataRow GetParentRow (DataRelation relation) 
1062                 {
1063                         return GetParentRow (relation, DataRowVersion.Default);
1064                 }
1065
1066                 /// <summary>
1067                 /// Gets the parent row of a DataRow using the specified RelationName of a
1068                 /// DataRelation.
1069                 /// </summary>
1070                 public DataRow GetParentRow (string relationName) 
1071                 {
1072                         return GetParentRow (relationName, DataRowVersion.Default);
1073                 }
1074
1075                 /// <summary>
1076                 /// Gets the parent row of a DataRow using the specified DataRelation, and
1077                 /// DataRowVersion.
1078                 /// </summary>
1079                 public DataRow GetParentRow (DataRelation relation, DataRowVersion version) 
1080                 {
1081                         DataRow[] rows = GetParentRows(relation, version);
1082                         if (rows.Length == 0) return null;
1083                         return rows[0];
1084                 }
1085
1086                 /// <summary>
1087                 /// Gets the parent row of a DataRow using the specified RelationName of a 
1088                 /// DataRelation, and DataRowVersion.
1089                 /// </summary>
1090                 public DataRow GetParentRow (string relationName, DataRowVersion version) 
1091                 {
1092                         return GetParentRow (Table.DataSet.Relations[relationName], version);
1093                 }
1094
1095                 /// <summary>
1096                 /// Gets the parent rows of a DataRow using the specified DataRelation.
1097                 /// </summary>
1098                 public DataRow[] GetParentRows (DataRelation relation) 
1099                 {
1100                         return GetParentRows (relation, DataRowVersion.Default);
1101                 }
1102
1103                 /// <summary>
1104                 /// Gets the parent rows of a DataRow using the specified RelationName of a 
1105                 /// DataRelation.
1106                 /// </summary>
1107                 public DataRow[] GetParentRows (string relationName) 
1108                 {
1109                         return GetParentRows (relationName, DataRowVersion.Default);
1110                 }
1111
1112                 /// <summary>
1113                 /// Gets the parent rows of a DataRow using the specified DataRelation, and
1114                 /// DataRowVersion.
1115                 /// </summary>
1116                 public DataRow[] GetParentRows (DataRelation relation, DataRowVersion version) 
1117                 {
1118                         // TODO: Caching for better preformance
1119                         if (relation == null)
1120                                 return new DataRow[0];
1121
1122                         if (this.Table == null)
1123                                 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.");
1124
1125                         if (relation.DataSet != this.Table.DataSet)
1126                                 throw new ArgumentException();
1127
1128                         if (_table != relation.ChildTable)
1129                                 throw new InvalidConstraintException ("GetParentRows requires a row whose Table is " + relation.ChildTable + ", but the specified row's table is " + _table);
1130
1131                         ArrayList rows = new ArrayList();
1132                         DataColumn[] parentColumns = relation.ParentColumns;
1133                         DataColumn[] childColumns = relation.ChildColumns;
1134                         int numColumn = parentColumns.Length;
1135                         if (HasVersion(version)) {
1136                                 Index indx = relation.ParentTable.GetIndexByColumns (parentColumns);
1137                                 if (indx != null && 
1138     (Table == null || Table.DataSet == null || 
1139      Table.DataSet.EnforceConstraints)) { // get the child rows from the index
1140                                         Node[] childNodes = indx.FindAllSimple(childColumns, IndexFromVersion(version));
1141                                         for (int i = 0; i < childNodes.Length; i++) {
1142                                                 rows.Add (childNodes[i].Row);
1143                                         }
1144                                 }
1145                                 else { // no index so we have to search manualy.
1146                                         int curIndex = IndexFromVersion(DataRowVersion.Default);
1147                                         int tmpRecord = relation.ParentTable.RecordCache.NewRecord();
1148                                         try {
1149                                                 for (int i = 0; i < numColumn; i++) {
1150                                                         // according to MSDN: the DataType value for both columns must be identical.
1151                                                         parentColumns[i].DataContainer.CopyValue(childColumns[i].DataContainer, curIndex, tmpRecord);
1152                                                 }
1153
1154                                                 foreach (DataRow row in relation.ParentTable.Rows) {
1155                                                         bool allColumnsMatch = false;
1156                                                         if (row.HasVersion(DataRowVersion.Default)) {
1157                                                                 allColumnsMatch = true;
1158                                                                 int parentIndex = row.IndexFromVersion(DataRowVersion.Default);
1159                                                                 for (int columnCnt = 0; columnCnt < numColumn; columnCnt++) {
1160                                                                         if (parentColumns[columnCnt].DataContainer.CompareValues(parentIndex, tmpRecord) != 0) {
1161                                                                                 allColumnsMatch = false;
1162                                                                                 break;
1163                                                                         }
1164                                                                 }
1165                                                         }
1166                                                         if (allColumnsMatch) {
1167                                                                 rows.Add(row);
1168                                                         }
1169                                                 }
1170                                         }
1171                                         finally {
1172                                                 relation.ParentTable.RecordCache.DisposeRecord(tmpRecord);
1173                                         }
1174                                 }
1175                         }else
1176                                 throw new VersionNotFoundException("There is no " + version + " data to accces.");
1177
1178                         DataRow[] result = relation.ParentTable.NewRowArray(rows.Count);
1179                         rows.CopyTo(result, 0);
1180                         return result;
1181                 }
1182
1183                 /// <summary>
1184                 /// Gets the parent rows of a DataRow using the specified RelationName of a 
1185                 /// DataRelation, and DataRowVersion.
1186                 /// </summary>
1187                 public DataRow[] GetParentRows (string relationName, DataRowVersion version) 
1188                 {
1189                         return GetParentRows (Table.DataSet.Relations[relationName], version);
1190                 }
1191
1192                 /// <summary>
1193                 /// Gets a value indicating whether a specified version exists.
1194                 /// </summary>
1195                 public bool HasVersion (DataRowVersion version) 
1196                 {
1197                         switch (version) {
1198                         case DataRowVersion.Default:
1199                                 if (rowState == DataRowState.Deleted && !_inExpressionEvaluation)
1200                                         return false;
1201                                 if (rowState == DataRowState.Detached)
1202                                         return _proposed >= 0;
1203                                 return true;
1204                         case DataRowVersion.Proposed:
1205                                 if (rowState == DataRowState.Deleted && !_inExpressionEvaluation)
1206                                         return false;
1207                                 return _proposed >= 0;
1208                         case DataRowVersion.Current:
1209                                 if ((rowState == DataRowState.Deleted && !_inExpressionEvaluation) || rowState == DataRowState.Detached)
1210                                         return false;
1211                                 return _current >= 0;
1212                         case DataRowVersion.Original:
1213                                 if (rowState == DataRowState.Detached)
1214                                         return false;
1215                                 return _original >= 0;
1216                         }
1217                         return false;
1218                 }
1219
1220                 /// <summary>
1221                 /// Gets a value indicating whether the specified DataColumn contains a null value.
1222                 /// </summary>
1223                 public bool IsNull (DataColumn column) 
1224                 {
1225                         return IsNull(column, DataRowVersion.Default);
1226                 }
1227
1228                 /// <summary>
1229                 /// Gets a value indicating whether the column at the specified index contains a null
1230                 /// value.
1231                 /// </summary>
1232                 public bool IsNull (int columnIndex) 
1233                 {
1234                         return IsNull(Table.Columns[columnIndex]);
1235                 }
1236
1237                 /// <summary>
1238                 /// Gets a value indicating whether the named column contains a null value.
1239                 /// </summary>
1240                 public bool IsNull (string columnName) 
1241                 {
1242                         return IsNull(Table.Columns[columnName]);
1243                 }
1244
1245                 /// <summary>
1246                 /// Gets a value indicating whether the specified DataColumn and DataRowVersion
1247                 /// contains a null value.
1248                 /// </summary>
1249                 public bool IsNull (DataColumn column, DataRowVersion version) 
1250                 {
1251                         return column.DataContainer.IsNull(IndexFromVersion(version));
1252                 }
1253
1254                 /// <summary>
1255                 /// Returns a value indicating whether all of the row columns specified contain a null value.
1256                 /// </summary>
1257                 internal bool IsNullColumns(DataColumn[] columns)
1258                 {
1259                         bool allNull = true;
1260                         for (int i = 0; i < columns.Length; i++) 
1261                         {
1262                                 if (!IsNull(columns[i])) 
1263                                 {
1264                                         allNull = false;
1265                                         break;
1266                                 }
1267                         }
1268                         return allNull;
1269                 }
1270
1271                 /// <summary>
1272                 /// Rejects all changes made to the row since AcceptChanges was last called.
1273                 /// </summary>
1274                 public void RejectChanges () 
1275                 {
1276                         if (RowState == DataRowState.Detached)
1277                                 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.");
1278                         // If original is null, then nothing has happened since AcceptChanges
1279                         // was last called.  We have no "original" to go back to.
1280                         if (HasVersion(DataRowVersion.Original)) {
1281                                 if (_current >= 0 ) {
1282                                         Table.RecordCache.DisposeRecord(_current);
1283                                 }
1284                                 _current = _original;
1285                                
1286                                 _table.ChangedDataRow (this, DataRowAction.Rollback);
1287                                 CancelEdit ();
1288                                 switch (rowState)
1289                                 {
1290                                         case DataRowState.Added:
1291                                                 _table.DeleteRowFromIndexes (this);
1292                                                 _table.Rows.RemoveInternal (this);
1293                                                 break;
1294                                         case DataRowState.Modified:
1295                                                 if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
1296                                                         _table.Rows.ValidateDataRowInternal(this);
1297                                                 rowState = DataRowState.Unchanged;
1298                                                 break;
1299                                         case DataRowState.Deleted:
1300                                                 rowState = DataRowState.Unchanged;
1301                                                 if ((_table.DataSet == null || _table.DataSet.EnforceConstraints) && !_table._duringDataLoad)
1302                                                         _table.Rows.ValidateDataRowInternal(this);
1303                                                 break;
1304                                 } 
1305                                 
1306                         }                       
1307                         else {
1308                                 // If rows are just loaded via Xml the original values are null.
1309                                 // So in this case we have to remove all columns.
1310                                 // FIXME: I'm not realy sure, does this break something else, but
1311                                 // if so: FIXME ;)
1312                                 
1313                                 if ((rowState & DataRowState.Added) > 0)
1314                                 {
1315                                         _table.DeleteRowFromIndexes (this);
1316                                         _table.Rows.RemoveInternal (this);
1317                                         // if row was in Added state we move it to Detached.
1318                                         DetachRow();
1319                                 }
1320                         }
1321                 }
1322
1323                 /// <summary>
1324                 /// Sets the error description for a column specified as a DataColumn.
1325                 /// </summary>
1326                 public void SetColumnError (DataColumn column, string error) 
1327                 {
1328                         SetColumnError (_table.Columns.IndexOf (column), error);
1329                 }
1330
1331                 /// <summary>
1332                 /// Sets the error description for a column specified by index.
1333                 /// </summary>
1334                 public void SetColumnError (int columnIndex, string error) 
1335                 {
1336                         if (columnIndex < 0 || columnIndex >= Table.Columns.Count)
1337                                 throw new IndexOutOfRangeException ();
1338
1339                         while(ColumnErrors.Count < columnIndex) {
1340                                 ColumnErrors.Add(null);
1341                         }
1342                         ColumnErrors.Add(error);
1343                 }
1344
1345                 /// <summary>
1346                 /// Sets the error description for a column specified by name.
1347                 /// </summary>
1348                 public void SetColumnError (string columnName, string error) 
1349                 {
1350                         SetColumnError (_table.Columns.IndexOf (columnName), error);
1351                 }
1352
1353                 /// <summary>
1354                 /// Sets the value of the specified DataColumn to a null value.
1355                 /// </summary>
1356                 protected void SetNull (DataColumn column) 
1357                 {
1358                         this[column] = DBNull.Value;
1359                 }
1360
1361                 /// <summary>
1362                 /// Sets the parent row of a DataRow with specified new parent DataRow.
1363                 /// </summary>
1364                 public void SetParentRow (DataRow parentRow) 
1365                 {
1366                         SetParentRow(parentRow, null);
1367                 }
1368
1369                 /// <summary>
1370                 /// Sets the parent row of a DataRow with specified new parent DataRow and
1371                 /// DataRelation.
1372                 /// </summary>
1373                 public void SetParentRow (DataRow parentRow, DataRelation relation) 
1374                 {
1375                         if (_table == null || parentRow.Table == null)
1376                                 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.");
1377
1378                         if (parentRow != null && _table.DataSet != parentRow.Table.DataSet)
1379                                 throw new ArgumentException();
1380                         
1381                         BeginEdit();
1382                         if (relation == null)
1383                         {
1384                                 foreach (DataRelation parentRel in _table.ParentRelations)
1385                                 {
1386                                         DataColumn[] childCols = parentRel.ChildColumns;
1387                                         DataColumn[] parentCols = parentRel.ParentColumns;
1388                                         
1389                                         for (int i = 0; i < parentCols.Length; i++)
1390                                         {
1391                                                 if (parentRow == null)
1392                                                         this[childCols[i].Ordinal] = DBNull.Value;
1393                                                 else
1394                                                         this[childCols[i].Ordinal] = parentRow[parentCols[i]];
1395                                         }
1396                                         
1397                                 }
1398                         }
1399                         else
1400                         {
1401                                 DataColumn[] childCols = relation.ChildColumns;
1402                                 DataColumn[] parentCols = relation.ParentColumns;
1403                                         
1404                                 for (int i = 0; i < parentCols.Length; i++)
1405                                 {
1406                                         if (parentRow == null)
1407                                                 this[childCols[i].Ordinal] = DBNull.Value;
1408                                         else
1409                                                 this[childCols[i].Ordinal] = parentRow[parentCols[i]];
1410                                 }
1411                         }
1412                         EndEdit();
1413                 }
1414                 
1415                 //Copy all values of this DataaRow to the row parameter.
1416                 internal void CopyValuesToRow(DataRow row)
1417                 {
1418                         if (row == null)
1419                                 throw new ArgumentNullException("row");
1420                         if (row == this)
1421                                 throw new ArgumentException("'row' is the same as this object");
1422
1423                         foreach(DataColumn column in Table.Columns) {
1424                                 DataColumn targetColumn = row.Table.Columns[column.ColumnName];
1425                                 //if a column with the same name exists in both rows copy the values
1426                                 if(targetColumn != null) {
1427                                         int index = targetColumn.Ordinal;
1428                                         if (HasVersion(DataRowVersion.Original)) {
1429                                                 if (row._original < 0) {
1430                                                         row._original = row.Table.RecordCache.NewRecord();
1431                                                 }
1432                                                 object val = column[_original];
1433                                                 row.CheckValue(val, targetColumn);
1434                                                 targetColumn[row._original] = val;
1435                                         }
1436                                         if (HasVersion(DataRowVersion.Current)) {
1437                                                 if (row._current < 0) {
1438                                                         row._current = row.Table.RecordCache.NewRecord();
1439                                                 }
1440                                                 object val = column[_current];
1441                                                 row.CheckValue(val, targetColumn);
1442                                                 targetColumn[row._current] = val;
1443                                         }
1444                                         if (HasVersion(DataRowVersion.Proposed)) {
1445                                                 if (row._proposed < 0) {
1446                                                         row._proposed = row.Table.RecordCache.NewRecord();
1447                                                 }
1448                                                 object val = column[row._proposed];
1449                                                 row.CheckValue(val, targetColumn);
1450                                                 targetColumn[row._proposed] = val;
1451                                         }
1452                                         
1453                                         //Saving the current value as the column value
1454                                         object defaultVal = column [IndexFromVersion (DataRowVersion.Default)];
1455                                         row [index] = defaultVal;
1456                                 }
1457                         }
1458                         CopyState(row);
1459                 }
1460
1461                 // Copy row state - rowState and errors
1462                 internal void CopyState(DataRow row)
1463                 {
1464                         row.rowState = RowState;
1465                         row.RowError = RowError;
1466                         row.ColumnErrors = (ArrayList)ColumnErrors.Clone();
1467                 }
1468
1469                 internal bool IsRowChanged(DataRowState rowState) {
1470                         if((RowState & rowState) != 0)
1471                                 return true;
1472
1473                         //we need to find if child rows of this row changed.
1474                         //if yes - we should return true
1475
1476                         // if the rowState is deleted we should get the original version of the row
1477                         // else - we should get the current version of the row.
1478                         DataRowVersion version = (rowState == DataRowState.Deleted) ? DataRowVersion.Original : DataRowVersion.Current;
1479                         int count = Table.ChildRelations.Count;
1480                         for (int i = 0; i < count; i++){
1481                                 DataRelation rel = Table.ChildRelations[i];
1482                                 DataRow[] childRows = GetChildRows(rel, version);
1483                                 for (int j = 0; j < childRows.Length; j++){
1484                                         if (childRows[j].IsRowChanged(rowState))
1485                                                 return true;
1486                                 }
1487                         }
1488
1489                         return false;
1490                 }
1491
1492                 internal bool HasParentCollection
1493                 {
1494                         get
1495                         {
1496                                 return _hasParentCollection;
1497                         }
1498                         set
1499                         {
1500                                 _hasParentCollection = value;
1501                         }
1502                 }
1503
1504                 internal void CheckNullConstraints()
1505                 {
1506                         if (_nullConstraintViolation) {
1507                                 if (HasVersion(DataRowVersion.Proposed)) {
1508                                         foreach(DataColumn column in Table.Columns) {
1509                                                 if (IsNull(column) && !column.AllowDBNull) {
1510                                                         throw new NoNullAllowedException(_nullConstraintMessage);
1511                                         }
1512                                 }
1513                                 }
1514                                 _nullConstraintViolation = false;
1515                         }
1516                 }
1517                 
1518                 internal void CheckReadOnlyStatus()
1519                 {
1520                         if (HasVersion(DataRowVersion.Proposed)) {
1521                                 int defaultIdx = IndexFromVersion(DataRowVersion.Default); 
1522                                 foreach(DataColumn column in Table.Columns) {
1523                                         if ((column.DataContainer.CompareValues(defaultIdx,_proposed) != 0) && column.ReadOnly) {
1524                                         throw new ReadOnlyException();
1525                         }
1526                 }
1527                         }                       
1528                 }
1529         
1530                 #endregion // Methods
1531
1532 #if NET_2_0
1533                 /// <summary>
1534                 ///    This method loads a given value into the existing row affecting versions,
1535                 ///    state based on the LoadOption.  The matrix of changes for this method are as
1536                 ///    mentioned in the DataTable.Load (IDataReader, LoadOption) method.
1537                 /// </summary>
1538                 [MonoTODO ("Raise necessary Events")]
1539                 internal void Load (object [] values, LoadOption loadOption, bool is_new)
1540                 {
1541                         DataRowAction action = DataRowAction.Change;
1542
1543                         int temp = Table.RecordCache.NewRecord ();
1544                         for (int i = 0 ; i < Table.Columns.Count; i++)
1545                                 SetValue (i, values [i], temp);
1546
1547                         if (is_new) { // new row
1548                                 if (editing || RowState == DataRowState.Detached)
1549                                         Proposed = temp;
1550                                 else
1551                                         Current = temp;
1552                                 return;
1553                         }
1554
1555                         if (loadOption == LoadOption.OverwriteChanges 
1556                             || (loadOption == LoadOption.PreserveChanges
1557                                 && rowState == DataRowState.Unchanged)) {
1558                                 Original = temp;
1559                                 if (editing)
1560                                         Proposed = temp;
1561                                 else
1562                                         Current = temp;
1563                                 rowState = DataRowState.Unchanged;
1564                                 action = DataRowAction.ChangeCurrentAndOriginal;
1565                                 return;
1566                         }
1567
1568                         if (loadOption == LoadOption.PreserveChanges) {
1569                                 if (rowState != DataRowState.Deleted) {
1570                                         Original = temp;
1571                                         rowState = DataRowState.Modified;
1572                                         action   = DataRowAction.ChangeOriginal;
1573                                 }
1574                                 return;
1575                         }
1576                                 
1577                         bool not_used = true;
1578                         // Upsert
1579                         if (rowState != DataRowState.Deleted) {
1580                                 int index = editing ? _proposed : _current;
1581                                 if (! RecordCache.CompareRecords (Table, index, temp)) {
1582                                         if (editing)
1583                                                 Proposed = temp;
1584                                         else
1585                                                 Current = temp;
1586                                         not_used = false;
1587                                         if (rowState == DataRowState.Unchanged)
1588                                                 rowState = DataRowState.Modified;
1589                                 }
1590                         }
1591                                 
1592                         if (not_used)
1593                                 Table.RecordCache.DisposeRecord (temp);
1594                 }
1595 #endif // NET_2_0
1596         }
1597
1598         
1599
1600 }