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