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