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