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