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