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