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