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