Update Reference Sources to .NET Framework 4.6.1
[mono.git] / mcs / class / referencesource / System.Data / System / Data / DataRow.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="DataRow.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">[....]</owner>
6 // <owner current="true" primary="false">[....]</owner>
7 //------------------------------------------------------------------------------
8
9 namespace System.Data {
10     using System;
11     using System.Collections;
12     using System.ComponentModel;
13     using System.Diagnostics;
14     using System.Globalization;
15     using System.Xml;
16
17
18     /// <devdoc>
19     /// <para>Represents a row of data in a <see cref='System.Data.DataTable'/>.</para>
20     /// </devdoc>
21     public class DataRow {
22         private readonly DataTable _table;
23         private readonly DataColumnCollection _columns;
24
25         internal int  oldRecord = -1;
26         internal int  newRecord = -1;
27         internal int  tempRecord;
28         internal long  _rowID = -1;
29
30         internal DataRowAction _action;
31
32         internal bool  inChangingEvent;
33         internal bool  inDeletingEvent;
34         internal bool  inCascade;
35
36         private DataColumn _lastChangedColumn; // last successfully changed column
37         private int _countColumnChange;        // number of columns changed during edit mode
38         
39         private DataError  error;
40         private object    _element;
41         
42         private int _rbTreeNodeId; // if row is not detached, Id used for computing index in rows collection
43
44         private static int _objectTypeCount; // Bid counter
45         internal readonly int ObjectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
46
47         /// <devdoc>
48         ///    <para>
49         ///       Initializes a new instance of the DataRow.
50         ///    </para>
51         ///    <para>
52         ///       Constructs a row from the builder. Only for internal usage..
53         ///    </para>
54         /// </devdoc>
55         protected internal DataRow (DataRowBuilder builder) {
56             tempRecord = builder._record;
57             _table = builder._table;
58             _columns = _table.Columns;
59         }
60
61         internal XmlBoundElement  Element {
62             get {
63                 return (XmlBoundElement) _element;
64             }
65             set {
66                 _element = value;
67             }
68         }
69
70         internal DataColumn LastChangedColumn {
71             get { // last successfully changed column or if multiple columns changed: null
72                 if (_countColumnChange != 1) {
73                     return null;
74                 }
75                 return _lastChangedColumn;
76             }
77             set {
78                 _countColumnChange++;
79                 _lastChangedColumn = value;
80             }
81         }
82
83         internal bool HasPropertyChanged {
84             get { return (0 < _countColumnChange); }
85         }
86
87         internal int RBTreeNodeId {
88             get {
89                 return _rbTreeNodeId;
90             }
91             set {
92                 Bid.Trace("<ds.DataRow.set_RBTreeNodeId|INFO> %d#, value=%d\n", ObjectID, value);
93                 _rbTreeNodeId = value;
94             }
95         }
96
97         /// <devdoc>
98         ///    <para>Gets or sets the custom error description for a row.</para>
99         /// </devdoc>
100         public string RowError {
101             get {
102                 return(error == null ? String.Empty :error.Text);
103             }
104             set {
105                 Bid.Trace("<ds.DataRow.set_RowError|API> %d#, value='%ls'\n", ObjectID, value);
106                 if (error == null) {
107                     if (!Common.ADP.IsEmpty(value)) {
108                         error = new DataError(value);
109                     }
110                     RowErrorChanged();
111                 }
112                 else if(error.Text != value) {
113                     error.Text = value;
114                     RowErrorChanged();
115                 }
116             }
117         }
118
119         private void RowErrorChanged() {
120             // We don't know wich record was used by view index. try to use both.
121             if (oldRecord != -1)
122                 _table.RecordChanged(oldRecord);
123             if (newRecord != -1)
124                 _table.RecordChanged(newRecord);
125         }
126
127         internal long rowID {
128             get {
129                 return _rowID;
130             }
131             set {
132                 ResetLastChangedColumn();
133                 _rowID = value;
134             }
135         }
136
137         /// <devdoc>
138         ///    <para>Gets the current state of the row in regards to its relationship to the table.</para>
139         /// </devdoc>
140         public DataRowState RowState {
141             get {
142                 /*
143                 if (oldRecord == -1 && newRecord == -1)
144                     state = DataRowState.Detached; // 2
145                 else if (oldRecord == newRecord)
146                     state = DataRowState.Unchanged; // 2
147                 else if (oldRecord == -1)
148                     state = DataRowState.Added; // 4
149                 else if (newRecord == -1)
150                     state = DataRowState.Deleted; // 4
151                 else
152                     state = DataRowState.Modified; // 4
153                 */
154                 if (oldRecord == newRecord) {
155                     if (oldRecord == -1) {
156                         return DataRowState.Detached; // 2
157                     }
158                     if (0 < _columns.ColumnsImplementingIChangeTrackingCount) {
159                         foreach(DataColumn dc in _columns.ColumnsImplementingIChangeTracking) {
160                             object value = this[dc];
161                             if ((DBNull.Value != value) && ((IChangeTracking)value).IsChanged) {
162                                 return DataRowState.Modified; // 3 + _columns.columnsImplementingIChangeTracking.Count
163                             }
164                         }
165                     }
166                     return DataRowState.Unchanged; // 3
167                 }
168                 else if (oldRecord == -1) {
169                     return DataRowState.Added; // 2
170                 }
171                 else if (newRecord == -1) {
172                     return DataRowState.Deleted; // 3
173                 }
174                 return DataRowState.Modified; // 3
175
176             }
177         }
178
179         /// <devdoc>
180         /// <para>Gets the <see cref='System.Data.DataTable'/>
181         /// for which this row has a schema.</para>
182         /// </devdoc>
183         public DataTable Table {
184             get {
185                 return _table;
186             }
187         }
188
189         /// <devdoc>
190         ///    <para>Gets or sets the data stored in the column specified by index.</para>
191         /// </devdoc>
192         public object this[int columnIndex] {
193             get {
194                 DataColumn column = _columns[columnIndex];
195                 int record = GetDefaultRecord();
196                 _table.recordManager.VerifyRecord(record, this);
197                 VerifyValueFromStorage(column, DataRowVersion.Default, column[record]);
198                 return column[record];
199             }
200             set {
201                 DataColumn column = _columns[columnIndex];
202                 this[column] = value;
203             }
204         }
205
206         internal void CheckForLoops(DataRelation rel){
207             // don't check for loops in the diffgram
208             // because there may be some holes in the rowCollection
209             // and index creation may fail. The check will be done
210             // after all the loading is done _and_ we are sure there
211             // are no holes in the collection.
212             if (_table.fInLoadDiffgram || (_table.DataSet != null && _table.DataSet.fInLoadDiffgram))
213               return;
214             int count = _table.Rows.Count, i = 0;
215             // need to optimize this for count > 100
216             DataRow parent = this.GetParentRow(rel);
217             while (parent != null) {
218                 if ((parent == this) || (i>count))
219                     throw ExceptionBuilder.NestedCircular(_table.TableName);
220                 i++;
221                 parent = parent.GetParentRow(rel);
222             }
223         }
224
225         internal int GetNestedParentCount() {
226             int count = 0;
227             DataRelation[] nestedParentRelations = _table.NestedParentRelations;
228             foreach(DataRelation rel in nestedParentRelations) {
229                 if (rel == null) // don't like this but done for backward code compatability
230                     continue;
231                 if (rel.ParentTable == _table) // self-nested table
232                     this.CheckForLoops(rel);
233                 DataRow row = this.GetParentRow(rel);
234                 if (row != null) {
235                     count++;
236                 }
237             }
238             return count ;
239             // Rule 1: At all times, only ONE FK  "(in a row) can be non-Null
240             // we wont allow a row to have multiple parents, as we cant handle it , also in diffgram
241         }
242
243         /// <devdoc>
244         ///    <para>Gets or sets the data stored in the column specified by
245         ///       name.</para>
246         /// </devdoc>
247         public object this[string columnName] {
248             get {
249                 DataColumn column = GetDataColumn(columnName);
250                 int record = GetDefaultRecord();
251                 _table.recordManager.VerifyRecord(record, this);
252                 VerifyValueFromStorage(column, DataRowVersion.Default, column[record]);
253                 return column[record];
254             }
255             set {
256                 DataColumn column = GetDataColumn(columnName);
257                 this[column] = value;
258             }
259         }
260
261         /// <devdoc>
262         ///    <para>Gets or sets
263         ///       the data stored in the specified <see cref='System.Data.DataColumn'/>.</para>
264         /// </devdoc>
265         public object this[DataColumn column] {
266             get {
267                 CheckColumn(column);
268                 int record = GetDefaultRecord();
269                 _table.recordManager.VerifyRecord(record, this);
270                 VerifyValueFromStorage(column, DataRowVersion.Default, column[record]);
271                 return column[record];
272             }
273             set {
274                 CheckColumn(column);
275                 if (inChangingEvent) {
276                     throw ExceptionBuilder.EditInRowChanging();
277                 }
278                 if ((-1 != rowID) && column.ReadOnly) {
279                     throw ExceptionBuilder.ReadOnly(column.ColumnName);
280                 }
281
282                 // allow users to tailor the proposed value, or throw an exception.
283                 // note we intentionally do not try/catch this event.
284                 // note: we also allow user to do anything at this point
285                 // infinite loops are possible if user calls Item or ItemArray during the event
286                 DataColumnChangeEventArgs e = null;
287                 if (_table.NeedColumnChangeEvents) {
288                     e = new DataColumnChangeEventArgs(this, column, value);
289                     _table.OnColumnChanging(e);
290                 }
291
292                 if (column.Table != _table) {
293                     // user removed column from table during OnColumnChanging event
294                     throw ExceptionBuilder.ColumnNotInTheTable(column.ColumnName, _table.TableName);
295                 }
296                 if ((-1 != rowID) && column.ReadOnly) {
297                     // user adds row to table during OnColumnChanging event
298                     throw ExceptionBuilder.ReadOnly(column.ColumnName);
299                 }
300                 
301                 object proposed = ((null != e) ? e.ProposedValue : value);
302                 if (null == proposed) {
303                     if (column.IsValueType) { // WebData 105963
304                         throw ExceptionBuilder.CannotSetToNull(column);
305                     }
306                     proposed = DBNull.Value;
307                 }
308
309                 bool immediate = BeginEditInternal();
310                 try {
311                     int record = GetProposedRecordNo();
312                     _table.recordManager.VerifyRecord(record, this);
313                     column[record] = proposed;
314                 }
315                 catch (Exception e1){
316                     // 
317                     if (Common.ADP.IsCatchableOrSecurityExceptionType(e1)) {
318                         if (immediate) {
319                             Debug.Assert(!inChangingEvent, "how are we in a changing event to cancel?");
320                             Debug.Assert(-1 != tempRecord, "how no propsed record to cancel?");
321                             CancelEdit(); // WebData 107154
322                         }
323                     }
324                     throw;
325                 }
326                 LastChangedColumn = column;
327
328                 // note: we intentionally do not try/catch this event.
329                 // infinite loops are possible if user calls Item or ItemArray during the event
330                 if (null != e) {
331                     _table.OnColumnChanged(e); // user may call CancelEdit or EndEdit
332                 }
333
334                 if (immediate) {
335                     Debug.Assert(!inChangingEvent, "how are we in a changing event to end?");
336                     EndEdit();
337                 }
338             }
339         }
340
341         /// <devdoc>
342         ///    <para>Gets the data stored
343         ///       in the column, specified by index and version of the data to retrieve.</para>
344         /// </devdoc>
345         public object this[int columnIndex, DataRowVersion version] {
346             get {
347                 DataColumn column = _columns[columnIndex];
348                 int record = GetRecordFromVersion(version);
349                 _table.recordManager.VerifyRecord(record, this);
350                 VerifyValueFromStorage(column, version, column[record]);
351                 return column[record];
352             }
353         }
354
355         /// <devdoc>
356         ///    <para> Gets the specified version of data stored in
357         ///       the named column.</para>
358         /// </devdoc>
359         public object this[string columnName, DataRowVersion version] {
360             get {
361                 DataColumn column = GetDataColumn(columnName);
362                 int record = GetRecordFromVersion(version);
363                 _table.recordManager.VerifyRecord(record, this);
364                 VerifyValueFromStorage(column, version, column[record]);
365                 return column[record];
366             }
367         }
368
369         /// <devdoc>
370         /// <para>Gets the specified version of data stored in the specified <see cref='System.Data.DataColumn'/>.</para>
371         /// </devdoc>
372         public object this[DataColumn column, DataRowVersion version] {
373             get {
374                 CheckColumn(column);
375                 int record = GetRecordFromVersion(version);
376                 _table.recordManager.VerifyRecord(record, this);
377                 VerifyValueFromStorage(column, version, column[record]);
378                 return column[record];
379             }
380         }
381
382         /// <devdoc>
383         ///    <para>Gets
384         ///       or sets all of the values for this row through an array.</para>
385         /// </devdoc>
386         public object[] ItemArray {
387             get {
388                 int record = GetDefaultRecord();
389                 _table.recordManager.VerifyRecord(record, this);
390                 object[] values = new object[_columns.Count];
391                 for (int i = 0; i < values.Length; i++) {
392                     DataColumn column = _columns[i];
393                     VerifyValueFromStorage(column, DataRowVersion.Default, column[record]);
394                     values[i] = column[record];
395                 }
396                 return values;
397             }
398             set {
399                 if (null == value) { // WebData 104372
400                     throw ExceptionBuilder.ArgumentNull("ItemArray");
401                 }
402                 if (_columns.Count < value.Length) {
403                     throw ExceptionBuilder.ValueArrayLength();
404                 }
405                 DataColumnChangeEventArgs e = null;
406                 if (_table.NeedColumnChangeEvents) {
407                     e = new DataColumnChangeEventArgs(this);
408                 }
409                 bool immediate = BeginEditInternal();
410
411                 for (int i = 0; i < value.Length; ++i) {
412                     // Empty means don't change the row.
413                     if (null != value[i]) {
414                         // may throw exception if user removes column from table during event
415                         DataColumn column = _columns[i];
416
417                         if ((-1 != rowID) && column.ReadOnly) {
418                             throw ExceptionBuilder.ReadOnly(column.ColumnName);
419                         }
420
421                         // allow users to tailor the proposed value, or throw an exception.
422                         // note: we intentionally do not try/catch this event.
423                         // note: we also allow user to do anything at this point
424                         // infinite loops are possible if user calls Item or ItemArray during the event
425                         if (null != e) {
426                             e.InitializeColumnChangeEvent(column, value[i]);
427                             _table.OnColumnChanging(e);
428                         }
429
430                         if (column.Table != _table) {
431                             // user removed column from table during OnColumnChanging event
432                             throw ExceptionBuilder.ColumnNotInTheTable(column.ColumnName, _table.TableName);
433                         }
434                         if ((-1 != rowID) && column.ReadOnly) {
435                             // user adds row to table during OnColumnChanging event
436                             throw ExceptionBuilder.ReadOnly(column.ColumnName);
437                         }
438                         if (tempRecord == -1) {
439                             // user affected CancelEdit or EndEdit during OnColumnChanging event of the last value
440                             BeginEditInternal();
441                         }
442
443                         object proposed = (null != e) ? e.ProposedValue : value[i];
444                         if (null == proposed) {
445                             if (column.IsValueType) { // WebData 105963
446                                 throw ExceptionBuilder.CannotSetToNull(column);
447                             }
448                             proposed = DBNull.Value;
449                         }
450
451                         try {
452                             // must get proposed record after each event because user may have
453                             // called EndEdit(), AcceptChanges(), BeginEdit() during the event
454                             int record = GetProposedRecordNo();
455                             _table.recordManager.VerifyRecord(record, this);
456                             column[record] = proposed;
457                         }
458                         catch (Exception e1) {
459                             // 
460                             if (Common.ADP.IsCatchableOrSecurityExceptionType(e1)) {
461                                 if (immediate) {
462                                     Debug.Assert(!inChangingEvent, "how are we in a changing event to cancel?");
463                                     Debug.Assert(-1 != tempRecord, "how no propsed record to cancel?");
464                                     CancelEdit(); // WebData 107154
465                                 }
466                             }
467                             throw;
468                         }
469                         LastChangedColumn = column;
470
471                         // note: we intentionally do not try/catch this event.
472                         // infinite loops are possible if user calls Item or ItemArray during the event
473                         if (null != e) {
474                             _table.OnColumnChanged(e);  // user may call CancelEdit or EndEdit
475                         }
476                     }
477                 }
478
479                 // proposed breaking change: if (immediate){ EndEdit(); } because table currently always fires RowChangedEvent
480                 Debug.Assert(!inChangingEvent, "how are we in a changing event to end?");
481                 EndEdit();
482             }
483         }
484
485         /// <devdoc>
486         ///    <para>Commits all the changes made to this row
487         ///       since the last time <see cref='System.Data.DataRow.AcceptChanges'/> was called.</para>
488         /// </devdoc>
489         public void AcceptChanges() {
490             IntPtr hscp;
491             Bid.ScopeEnter(out hscp, "<ds.DataRow.AcceptChanges|API> %d#\n", ObjectID);
492             try {
493                 EndEdit();
494
495                 if (this.RowState != DataRowState.Detached && this.RowState != DataRowState.Deleted) {
496                     if (_columns.ColumnsImplementingIChangeTrackingCount > 0) {
497                         foreach(DataColumn dc in _columns.ColumnsImplementingIChangeTracking) {
498                             object value = this[dc];
499                             if (DBNull.Value != value) {
500                                 IChangeTracking tracking = (IChangeTracking)value;
501                                 if (tracking.IsChanged) {
502                                     tracking.AcceptChanges();
503                                 }
504                             }
505                         }
506                     }
507                 }
508                 _table.CommitRow(this);
509             }
510             finally {
511                 Bid.ScopeLeave(ref hscp);
512             }
513         }
514
515         /// <devdoc>
516         /// <para>Begins an edit operation on a <see cref='System.Data.DataRow'/>object.</para>
517         /// </devdoc>
518         [
519         EditorBrowsableAttribute(EditorBrowsableState.Advanced),
520         ]
521         public void BeginEdit() {
522             BeginEditInternal();
523         }
524
525         private bool BeginEditInternal() {
526             if (inChangingEvent) {
527                 throw ExceptionBuilder.BeginEditInRowChanging();
528             }
529             if (tempRecord != -1) {
530                 if (tempRecord < _table.recordManager.LastFreeRecord) {
531                     return false; // we will not call EndEdit
532                 }
533                 else {
534                     // partial fix for detached row after Table.Clear scenario
535                     // in debug, it will have asserted earlier, but with this
536                     // it will go get a new record for editing
537                     tempRecord = -1;
538                 }
539                 // shifted VerifyRecord to first make the correction, then verify
540                 _table.recordManager.VerifyRecord(tempRecord, this);
541             }
542
543             if (oldRecord != -1 && newRecord == -1) {
544                 throw ExceptionBuilder.DeletedRowInaccessible();
545             }
546
547             // 
548
549             ResetLastChangedColumn(); // shouldn't have to do this
550
551             tempRecord = _table.NewRecord(newRecord);
552             Debug.Assert(-1 != tempRecord, "missing temp record");
553             Debug.Assert(0 == _countColumnChange, "unexpected column change count");
554             Debug.Assert(null == _lastChangedColumn, "unexpected last column change");
555             return true;
556         }
557
558         /// <devdoc>
559         ///    <para>Cancels the current edit on the row.</para>
560         /// </devdoc>
561         [
562         EditorBrowsableAttribute(EditorBrowsableState.Advanced),
563         ]
564         public void CancelEdit() {
565             if (inChangingEvent) {
566                 throw ExceptionBuilder.CancelEditInRowChanging();
567             }
568
569             _table.FreeRecord(ref tempRecord);
570             Debug.Assert(-1 == tempRecord, "unexpected temp record");
571             ResetLastChangedColumn();
572         }
573
574         private void CheckColumn(DataColumn column) {
575             if (column == null) {
576                 throw ExceptionBuilder.ArgumentNull("column");
577             }
578
579             if (column.Table != _table) {
580                 throw ExceptionBuilder.ColumnNotInTheTable(column.ColumnName, _table.TableName);
581             }
582         }
583
584         /// <devdoc>
585         /// Throws a RowNotInTableException if row isn't in table.
586         /// </devdoc>
587         internal void CheckInTable() {
588             if (rowID == -1) {
589                 throw ExceptionBuilder.RowNotInTheTable();
590             }
591         }
592
593         /// <devdoc>
594         ///    <para>Deletes the row.</para>
595         /// </devdoc>
596         public void Delete() {
597             if (inDeletingEvent) {
598                 throw ExceptionBuilder.DeleteInRowDeleting();
599             }
600
601             if (newRecord == -1)
602                 return;
603
604             _table.DeleteRow(this);
605         }
606
607         /// <devdoc>
608         ///    <para>Ends the edit occurring on the row.</para>
609         /// </devdoc>
610         [
611         EditorBrowsableAttribute(EditorBrowsableState.Advanced),
612         ]
613         public void EndEdit() {
614             if (inChangingEvent) {
615                 throw ExceptionBuilder.EndEditInRowChanging();
616             }
617
618             if (newRecord == -1) {
619                 return; // this is meaningless, detatched row case
620             }
621
622             if (tempRecord != -1) {
623                 try {
624                     // suppressing the ensure property changed because it's possible that no values have been modified
625                     _table.SetNewRecord(this, tempRecord, suppressEnsurePropertyChanged: true);
626                 }
627                 finally {
628                     // a constraint violation may be thrown during SetNewRecord
629                     ResetLastChangedColumn();
630                 }
631             }
632         }
633
634         /// <devdoc>
635         ///    <para>Sets the error description for a column specified by index.</para>
636         /// </devdoc>
637         public void SetColumnError(int columnIndex, string error) {
638             DataColumn column = _columns[columnIndex];
639             if (column == null)
640                 throw ExceptionBuilder.ColumnOutOfRange(columnIndex);
641
642             SetColumnError(column, error);
643         }
644
645         /// <devdoc>
646         ///    <para>Sets
647         ///       the error description for a column specified by name.</para>
648         /// </devdoc>
649         public void SetColumnError(string columnName, string error) {
650             DataColumn column = GetDataColumn(columnName);
651             SetColumnError(column, error);
652         }
653
654         /// <devdoc>
655         /// <para>Sets the error description for a column specified as a <see cref='System.Data.DataColumn'/>.</para>
656         /// </devdoc>
657         public void SetColumnError(DataColumn column, string error) {
658             CheckColumn(column);
659             
660             IntPtr hscp;
661             Bid.ScopeEnter(out hscp, "<ds.DataRow.SetColumnError|API> %d#, column=%d, error='%ls'\n", ObjectID, column.ObjectID, error);
662             try {            
663                 if (this.error == null)  this.error = new DataError();
664                 if(GetColumnError(column) != error) {
665                     this.error.SetColumnError(column, error);
666                     RowErrorChanged();
667                 }
668             }
669             finally {
670                 Bid.ScopeLeave(ref hscp);
671             }
672         }
673
674         /// <devdoc>
675         ///    <para>Gets the error description for the column specified
676         ///       by index.</para>
677         /// </devdoc>
678         public string GetColumnError(int columnIndex) {
679             DataColumn column = _columns[columnIndex];
680             return GetColumnError(column);
681         }
682
683         /// <devdoc>
684         ///    <para>Gets the error description for a column, specified by name.</para>
685         /// </devdoc>
686         public string GetColumnError(string columnName) {
687             DataColumn column = GetDataColumn(columnName);
688             return GetColumnError(column);
689         }
690
691         /// <devdoc>
692         ///    <para>Gets the error description of
693         ///       the specified <see cref='System.Data.DataColumn'/>.</para>
694         /// </devdoc>
695         public string GetColumnError(DataColumn column) {
696             CheckColumn(column);
697             if (error == null)  error = new DataError();
698             return error.GetColumnError(column);
699         }
700
701         /// <summary>
702         /// Clears the errors for the row, including the <see cref='System.Data.DataRow.RowError'/>
703         /// and errors set with <see cref='System.Data.DataRow.SetColumnError(DataColumn, string)'/>
704         /// </summary>
705         public void ClearErrors() {
706             if (error != null) {
707                 error.Clear();
708                 RowErrorChanged();
709             }
710         }
711
712         internal void ClearError(DataColumn column) {
713             if (error != null) {
714                 error.Clear(column);
715                 RowErrorChanged();
716             }
717         }
718
719         /// <devdoc>
720         ///    <para>Gets a value indicating whether there are errors in a columns collection.</para>
721         /// </devdoc>
722         public bool HasErrors {
723             get {
724                 return(error == null ? false : error.HasErrors);
725             }
726         }
727
728         /// <devdoc>
729         ///    <para>Gets an array of columns that have errors.</para>
730         /// </devdoc>
731         public DataColumn[] GetColumnsInError() {
732             if (error == null)
733                 return DataTable.zeroColumns;
734             else
735                 return error.GetColumnsInError();
736         }
737
738
739         public DataRow[] GetChildRows(string relationName) {
740             return GetChildRows(_table.ChildRelations[relationName], DataRowVersion.Default);
741         }
742
743         public DataRow[] GetChildRows(string relationName, DataRowVersion version) {
744             return GetChildRows(_table.ChildRelations[relationName], version);
745         }
746
747         /// <devdoc>
748         /// <para>Gets the child rows of this <see cref='System.Data.DataRow'/> using the
749         ///    specified <see cref='System.Data.DataRelation'/>
750         ///    .</para>
751         /// </devdoc>
752         public DataRow[] GetChildRows(DataRelation relation) {
753             return GetChildRows(relation, DataRowVersion.Default);
754         }
755
756         /// <devdoc>
757         /// <para>Gets the child rows of this <see cref='System.Data.DataRow'/> using the specified <see cref='System.Data.DataRelation'/> and the specified <see cref='System.Data.DataRowVersion'/></para>
758         /// </devdoc>
759         public DataRow[] GetChildRows(DataRelation relation, DataRowVersion version) {
760             if (relation == null)
761                 return _table.NewRowArray(0);
762
763             //if (-1 == rowID)
764             //    throw ExceptionBuilder.RowNotInTheTable();
765
766             if (relation.DataSet != _table.DataSet)
767                 throw ExceptionBuilder.RowNotInTheDataSet();
768             if (relation.ParentKey.Table != _table)
769                 throw ExceptionBuilder.RelationForeignTable(relation.ParentTable.TableName, _table.TableName);
770             return DataRelation.GetChildRows(relation.ParentKey, relation.ChildKey, this, version);
771         }
772
773         internal DataColumn GetDataColumn(string columnName) {
774             DataColumn column = _columns[columnName];
775             if (null != column) {
776                 return column;
777             }
778             throw ExceptionBuilder.ColumnNotInTheTable(columnName, _table.TableName);
779         }
780
781         public DataRow GetParentRow(string relationName) {
782             return GetParentRow(_table.ParentRelations[relationName], DataRowVersion.Default);
783         }
784
785         public DataRow GetParentRow(string relationName, DataRowVersion version) {
786             return GetParentRow(_table.ParentRelations[relationName], version);
787         }
788
789         /// <devdoc>
790         /// <para>Gets the parent row of this <see cref='System.Data.DataRow'/> using the specified <see cref='System.Data.DataRelation'/> .</para>
791         /// </devdoc>
792         public DataRow GetParentRow(DataRelation relation) {
793             return GetParentRow(relation, DataRowVersion.Default);
794         }
795
796         /// <devdoc>
797         /// <para>Gets the parent row of this <see cref='System.Data.DataRow'/>
798         /// using the specified <see cref='System.Data.DataRelation'/> and <see cref='System.Data.DataRowVersion'/>.</para>
799         /// </devdoc>
800         public DataRow GetParentRow(DataRelation relation, DataRowVersion version) {
801             if (relation == null)
802                 return null;
803
804             //if (-1 == rowID)
805             //    throw ExceptionBuilder.RowNotInTheTable();
806
807             if (relation.DataSet != _table.DataSet)
808                 throw ExceptionBuilder.RelationForeignRow();
809
810             if (relation.ChildKey.Table != _table)
811                 throw ExceptionBuilder.GetParentRowTableMismatch(relation.ChildTable.TableName, _table.TableName);
812
813             return DataRelation.GetParentRow(relation.ParentKey, relation.ChildKey, this, version);
814         }
815         // a multiple nested child table's row can have only one non-null FK per row. So table has multiple
816         // parents, but a row can have only one parent. Same nested row cannot below to 2 parent rows.
817         internal DataRow GetNestedParentRow(DataRowVersion version) {
818             // 1) Walk over all FKs and get the non-null. 2) Get the relation. 3) Get the parent Row.
819             DataRelation[] nestedParentRelations = _table.NestedParentRelations;
820             foreach(DataRelation rel in nestedParentRelations) {
821                 if (rel == null) // don't like this but done for backward code compatability
822                     continue;
823                 if (rel.ParentTable == _table) // self-nested table
824                     this.CheckForLoops(rel);
825                 DataRow row = this.GetParentRow(rel, version);
826                 if (row != null) {
827                     return row;
828                 }
829             }
830             return null;// Rule 1: At all times, only ONE FK  "(in a row) can be non-Null
831
832         }
833         // No Nested in 1-many
834
835         /// <devdoc>
836         ///    <para>[To be supplied.]</para>
837         /// </devdoc>
838         public DataRow[] GetParentRows(string relationName) {
839             return GetParentRows(_table.ParentRelations[relationName], DataRowVersion.Default);
840         }
841
842         /// <devdoc>
843         ///    <para>[To be supplied.]</para>
844         /// </devdoc>
845         public DataRow[] GetParentRows(string relationName, DataRowVersion version) {
846             return GetParentRows(_table.ParentRelations[relationName], version);
847         }
848
849         /// <devdoc>
850         ///    <para>
851         ///       Gets the parent rows of this <see cref='System.Data.DataRow'/> using the specified <see cref='System.Data.DataRelation'/> .
852         ///    </para>
853         /// </devdoc>
854         public DataRow[] GetParentRows(DataRelation relation) {
855             return GetParentRows(relation, DataRowVersion.Default);
856         }
857
858         /// <devdoc>
859         ///    <para>
860         ///       Gets the parent rows of this <see cref='System.Data.DataRow'/> using the specified <see cref='System.Data.DataRelation'/> .
861         ///    </para>
862         /// </devdoc>
863         public DataRow[] GetParentRows(DataRelation relation, DataRowVersion version) {
864             if (relation == null)
865                 return _table.NewRowArray(0);
866
867             //if (-1 == rowID)
868             //    throw ExceptionBuilder.RowNotInTheTable();
869
870             if (relation.DataSet != _table.DataSet)
871                 throw ExceptionBuilder.RowNotInTheDataSet();
872
873             if (relation.ChildKey.Table != _table)
874                 throw ExceptionBuilder.GetParentRowTableMismatch(relation.ChildTable.TableName, _table.TableName);
875
876             return DataRelation.GetParentRows(relation.ParentKey, relation.ChildKey, this, version);
877         }
878
879         internal object[] GetColumnValues(DataColumn[] columns) {
880             return GetColumnValues(columns, DataRowVersion.Default);
881         }
882
883         internal object[] GetColumnValues(DataColumn[] columns, DataRowVersion version) {
884             DataKey key = new DataKey(columns, false); // temporary key, don't copy columns
885             return GetKeyValues(key, version);
886         }
887
888         internal object[] GetKeyValues(DataKey key) {
889             int record = GetDefaultRecord();
890             return key.GetKeyValues(record);
891         }
892
893         internal object[] GetKeyValues(DataKey key, DataRowVersion version) {
894             int record = GetRecordFromVersion(version);
895             return key.GetKeyValues(record);
896         }
897
898         internal int GetCurrentRecordNo() {
899             if (newRecord == -1)
900                 throw ExceptionBuilder.NoCurrentData();
901             return newRecord;
902         }
903
904         internal int GetDefaultRecord() {
905             if (tempRecord != -1)
906                 return tempRecord;
907             if (newRecord != -1) {
908                 return newRecord;
909             }
910             // If row has oldRecord - this is deleted row.
911             if (oldRecord == -1)
912                 throw ExceptionBuilder.RowRemovedFromTheTable();
913             else
914                 throw ExceptionBuilder.DeletedRowInaccessible();
915         }
916
917         internal int GetOriginalRecordNo() {
918             if (oldRecord == -1)
919                 throw ExceptionBuilder.NoOriginalData();
920             return oldRecord;
921         }
922
923         private int GetProposedRecordNo() {
924             if (tempRecord == -1)
925                 throw ExceptionBuilder.NoProposedData();
926             return tempRecord;
927         }
928
929         internal int GetRecordFromVersion(DataRowVersion version) {
930             switch (version) {
931                 case DataRowVersion.Original:
932                     return GetOriginalRecordNo();
933                 case DataRowVersion.Current:
934                     return GetCurrentRecordNo();
935                 case DataRowVersion.Proposed:
936                     return GetProposedRecordNo();
937                 case DataRowVersion.Default:
938                     return GetDefaultRecord();
939                 default:
940                     throw ExceptionBuilder.InvalidRowVersion();
941             }
942         }
943
944         internal DataRowVersion GetDefaultRowVersion(DataViewRowState viewState) {
945             if (oldRecord == newRecord) {
946                 if (oldRecord == -1) {
947                     // should be DataView.addNewRow
948                     return DataRowVersion.Default;
949                 }
950                 Debug.Assert(0 != (DataViewRowState.Unchanged & viewState), "not DataViewRowState.Unchanged");
951                 return DataRowVersion.Default;
952             }
953             else if (oldRecord == -1) {
954                 Debug.Assert(0 != (DataViewRowState.Added & viewState), "not DataViewRowState.Added");
955                 return DataRowVersion.Default;
956             }
957             else if (newRecord == -1) {
958                 Debug.Assert(_action==DataRowAction.Rollback || 0 != (DataViewRowState.Deleted & viewState), "not DataViewRowState.Deleted");
959                 return DataRowVersion.Original;
960             }
961             else if (0 != (DataViewRowState.ModifiedCurrent & viewState)) {
962                 return DataRowVersion.Default;
963             }
964             Debug.Assert(0 != (DataViewRowState.ModifiedOriginal & viewState), "not DataViewRowState.ModifiedOriginal");
965             return DataRowVersion.Original;
966         }
967
968         internal DataViewRowState GetRecordState(int record) {
969             if (record == -1)
970                 return DataViewRowState.None;
971             if (record == oldRecord && record == newRecord)
972                 return DataViewRowState.Unchanged;
973             if (record == oldRecord)
974                 return(newRecord != -1) ? DataViewRowState.ModifiedOriginal : DataViewRowState.Deleted;
975             if (record == newRecord)
976                 return(oldRecord != -1) ? DataViewRowState.ModifiedCurrent : DataViewRowState.Added;
977             return DataViewRowState.None;
978         }
979
980         internal bool HasKeyChanged(DataKey key) {
981             return HasKeyChanged(key, DataRowVersion.Current, DataRowVersion.Proposed);
982         }
983
984         internal bool HasKeyChanged(DataKey key, DataRowVersion version1, DataRowVersion version2) {
985             if (!HasVersion(version1) || !HasVersion(version2))
986                 return true;
987             return !key.RecordsEqual(GetRecordFromVersion(version1), GetRecordFromVersion(version2));
988         }
989
990         /// <devdoc>
991         ///    <para>
992         ///       Gets a value indicating whether a specified version exists.
993         ///    </para>
994         /// </devdoc>
995         public bool HasVersion(DataRowVersion version) {
996             switch (version) {
997                 case DataRowVersion.Original:
998                     return(oldRecord != -1);
999                 case DataRowVersion.Current:
1000                     return(newRecord != -1);
1001                 case DataRowVersion.Proposed:
1002                     return(tempRecord != -1);
1003                 case DataRowVersion.Default:
1004                     return(tempRecord != -1 || newRecord != -1);
1005                 default:
1006                     throw ExceptionBuilder.InvalidRowVersion();
1007             }
1008         }
1009
1010         internal bool HasChanges() {
1011             if (!HasVersion(DataRowVersion.Original) || !HasVersion(DataRowVersion.Current)) {
1012                 return true; // if does not have original, its added row, if does not have current, its deleted row so it has changes
1013             }
1014             foreach(DataColumn dc in Table.Columns) {
1015                 if (dc.Compare(oldRecord, newRecord) != 0) {
1016                     return true;
1017                 }
1018             }
1019             return false;
1020         }
1021
1022         internal bool HaveValuesChanged(DataColumn[] columns) {
1023             return HaveValuesChanged(columns, DataRowVersion.Current, DataRowVersion.Proposed);
1024         }
1025
1026         internal bool HaveValuesChanged(DataColumn[] columns, DataRowVersion version1, DataRowVersion version2) {
1027             for (int i = 0; i < columns.Length; i++) {
1028                 CheckColumn(columns[i]);
1029             }
1030             DataKey key = new DataKey(columns, false); // temporary key, don't copy columns
1031             return HasKeyChanged(key, version1, version2);
1032         }
1033
1034         /// <devdoc>
1035         ///    <para>
1036         ///       Gets
1037         ///       a value indicating whether the column at the specified index contains a
1038         ///       null value.
1039         ///    </para>
1040         /// </devdoc>
1041         public bool IsNull(int columnIndex) {
1042             DataColumn column = _columns[columnIndex];
1043             int record = GetDefaultRecord();
1044             return column.IsNull(record);
1045         }
1046
1047         /// <devdoc>
1048         ///    <para>
1049         ///       Gets a value indicating whether the named column contains a null value.
1050         ///    </para>
1051         /// </devdoc>
1052         public bool IsNull(string columnName) {
1053             DataColumn column = GetDataColumn(columnName);
1054             int record = GetDefaultRecord();
1055             return column.IsNull(record);
1056         }
1057
1058         /// <devdoc>
1059         ///    <para>
1060         ///       Gets a value indicating whether the specified <see cref='System.Data.DataColumn'/>
1061         ///       contains a null value.
1062         ///    </para>
1063         /// </devdoc>
1064         public bool IsNull(DataColumn column) {
1065             CheckColumn(column);
1066             int record = GetDefaultRecord();
1067             return column.IsNull(record);
1068         }
1069
1070         /// <devdoc>
1071         ///    <para>[To be supplied.]</para>
1072         /// </devdoc>
1073         public bool IsNull(DataColumn column, DataRowVersion version) {
1074             CheckColumn(column);
1075             int record = GetRecordFromVersion(version);
1076             return column.IsNull(record);
1077         }
1078
1079         /// <devdoc>
1080         ///    <para>
1081         ///       Rejects all changes made to the row since <see cref='System.Data.DataRow.AcceptChanges'/>
1082         ///       was last called.
1083         ///    </para>
1084         /// </devdoc>
1085         public void RejectChanges() {
1086             IntPtr hscp;
1087             Bid.ScopeEnter(out hscp, "<ds.DataRow.RejectChanges|API> %d#\n", ObjectID);
1088             try {
1089                 if (this.RowState != DataRowState.Detached) {
1090                     if (_columns.ColumnsImplementingIChangeTrackingCount != _columns.ColumnsImplementingIRevertibleChangeTrackingCount) {
1091                         foreach(DataColumn dc in _columns.ColumnsImplementingIChangeTracking) {
1092                             if (!dc.ImplementsIRevertibleChangeTracking) {
1093                                 object value = null;
1094                                 if (this.RowState != DataRowState.Deleted)
1095                                     value = this[dc];
1096                                 else
1097                                     value = this[dc, DataRowVersion.Original];
1098                                 if (DBNull.Value != value){
1099                                     if (((IChangeTracking)value).IsChanged) {
1100                                         throw ExceptionBuilder.UDTImplementsIChangeTrackingButnotIRevertible(dc.DataType.AssemblyQualifiedName);
1101                                     }
1102                                 }
1103                             }
1104                         }
1105                     }
1106                     foreach(DataColumn dc in _columns.ColumnsImplementingIChangeTracking) {
1107                         object value = null;
1108                          if (this.RowState != DataRowState.Deleted)
1109                             value = this[dc];
1110                          else
1111                             value = this[dc, DataRowVersion.Original];
1112                         if (DBNull.Value != value) {
1113                             IChangeTracking tracking = (IChangeTracking)value;
1114                             if (tracking.IsChanged) {
1115                                 ((IRevertibleChangeTracking)value).RejectChanges();
1116                             }
1117                         }
1118                     }
1119                 }
1120                 _table.RollbackRow(this);
1121             }
1122             finally {
1123                 Bid.ScopeLeave(ref hscp);
1124             }
1125         }
1126         
1127         internal void ResetLastChangedColumn() {
1128             _lastChangedColumn = null;
1129             _countColumnChange = 0;
1130         }
1131
1132         internal void SetKeyValues(DataKey key, object[] keyValues) {
1133             bool fFirstCall = true;
1134             bool immediate = (tempRecord == -1);
1135
1136             for (int i = 0; i < keyValues.Length; i++) {
1137                 object value = this[key.ColumnsReference[i]];
1138                 if (!value.Equals(keyValues[i])) {
1139                     if (immediate && fFirstCall) {
1140                         fFirstCall = false;
1141                         BeginEditInternal();
1142                     }
1143                     this[key.ColumnsReference[i]] = keyValues[i];
1144                 }
1145             }
1146             if (!fFirstCall)
1147                 EndEdit();
1148         }
1149
1150         /// <devdoc>
1151         ///    <para>
1152         ///       Sets the specified column's value to a null value.
1153         ///    </para>
1154         /// </devdoc>
1155         protected void SetNull(DataColumn column) {
1156             this[column] = DBNull.Value;
1157         }
1158
1159         internal void SetNestedParentRow(DataRow parentRow, bool setNonNested) {
1160             if (parentRow == null) {
1161                 SetParentRowToDBNull();
1162                 return;
1163             }
1164
1165             foreach (DataRelation relation in _table.ParentRelations) {
1166                 if (relation.Nested || setNonNested) {
1167                     if (relation.ParentKey.Table == parentRow._table) {
1168                         object[] parentKeyValues = parentRow.GetKeyValues(relation.ParentKey);
1169                         this.SetKeyValues(relation.ChildKey, parentKeyValues);
1170
1171                         if (relation.Nested) {
1172                             if (parentRow._table == _table)
1173                                 this.CheckForLoops(relation);
1174                             else
1175                                 this.GetParentRow(relation);
1176                         }
1177                     }
1178                 }
1179             }
1180         }
1181         /// <devdoc>
1182         ///    <para>[To be supplied.]</para>
1183         /// </devdoc>
1184         public void SetParentRow(DataRow parentRow) {
1185             SetNestedParentRow(parentRow, true);
1186         }
1187
1188         /// <devdoc>
1189         ///    <para>
1190         ///       Sets current row's parent row with specified relation.
1191         ///    </para>
1192         /// </devdoc>
1193         public void SetParentRow(DataRow parentRow, DataRelation relation) {
1194             if (relation == null) {
1195                 SetParentRow(parentRow);
1196                 return;
1197             }
1198
1199             if (parentRow == null) {
1200                 SetParentRowToDBNull(relation);
1201                 return;
1202             }
1203
1204             //if (-1 == rowID)
1205             //    throw ExceptionBuilder.ChildRowNotInTheTable();
1206
1207             //if (-1 == parentRow.rowID)
1208             //    throw ExceptionBuilder.ParentRowNotInTheTable();
1209
1210             if (_table.DataSet != parentRow._table.DataSet)
1211                 throw ExceptionBuilder.ParentRowNotInTheDataSet();
1212
1213             if (relation.ChildKey.Table != _table)
1214                 throw ExceptionBuilder.SetParentRowTableMismatch(relation.ChildKey.Table.TableName, _table.TableName);
1215
1216             if (relation.ParentKey.Table != parentRow._table)
1217                 throw ExceptionBuilder.SetParentRowTableMismatch(relation.ParentKey.Table.TableName, parentRow._table.TableName);
1218
1219             object[] parentKeyValues = parentRow.GetKeyValues(relation.ParentKey);
1220             this.SetKeyValues(relation.ChildKey, parentKeyValues);
1221         }
1222
1223         internal void SetParentRowToDBNull() {
1224             //if (-1 == rowID)
1225             //    throw ExceptionBuilder.ChildRowNotInTheTable();
1226
1227             foreach (DataRelation relation in _table.ParentRelations)
1228                 SetParentRowToDBNull(relation);
1229         }
1230
1231         internal void SetParentRowToDBNull(DataRelation relation) {
1232             Debug.Assert(relation != null, "The relation should not be null here.");
1233
1234             //if (-1 == rowID)
1235             //    throw ExceptionBuilder.ChildRowNotInTheTable();
1236
1237             if (relation.ChildKey.Table != _table)
1238                 throw ExceptionBuilder.SetParentRowTableMismatch(relation.ChildKey.Table.TableName, _table.TableName);
1239
1240
1241             object[] parentKeyValues = new object[1];
1242             parentKeyValues[0] = DBNull.Value;
1243             this.SetKeyValues(relation.ChildKey, parentKeyValues);
1244         }
1245         public void SetAdded(){
1246             if (this.RowState == DataRowState.Unchanged) {
1247                 _table.SetOldRecord(this, -1);
1248             }
1249             else {
1250                 throw ExceptionBuilder.SetAddedAndModifiedCalledOnnonUnchanged();
1251             }
1252         }
1253
1254         public void SetModified(){
1255             if (this.RowState == DataRowState.Unchanged) {
1256                 tempRecord = _table.NewRecord(newRecord);
1257                 if (tempRecord != -1) {
1258                     // suppressing the ensure property changed because no values have changed
1259                     _table.SetNewRecord(this, tempRecord, suppressEnsurePropertyChanged: true);
1260                 }
1261             }
1262             else {
1263                 throw ExceptionBuilder.SetAddedAndModifiedCalledOnnonUnchanged();
1264             }
1265         }
1266
1267 /*
1268     RecordList contains the empty column storage needed. We need to copy the existing record values into this storage.
1269 */
1270         internal int CopyValuesIntoStore(ArrayList storeList, ArrayList nullbitList, int storeIndex) {
1271             int recordCount = 0;
1272             if (oldRecord != -1) {//Copy original record for the row in Unchanged, Modified, Deleted state.
1273                 for (int i = 0; i < _columns.Count; i++) {
1274                     _columns[i].CopyValueIntoStore(oldRecord, storeList[i], (BitArray) nullbitList[i], storeIndex);
1275                 }
1276                 recordCount++;
1277                 storeIndex++;
1278             }
1279
1280             DataRowState state = RowState;
1281             if ((DataRowState.Added == state) || (DataRowState.Modified == state)) { //Copy current record for the row in Added, Modified state.
1282                 for (int i = 0; i < _columns.Count; i++) {
1283                     _columns[i].CopyValueIntoStore(newRecord, storeList[i], (BitArray) nullbitList[i], storeIndex);
1284                 }
1285                 recordCount++;
1286                 storeIndex++;
1287             }
1288
1289             if (-1 != tempRecord) {//Copy temp record for the row in edit mode.                
1290                 for (int i = 0; i < _columns.Count; i++) {
1291                     _columns[i].CopyValueIntoStore(tempRecord, storeList[i], (BitArray)nullbitList[i], storeIndex);
1292                 }
1293                 recordCount++;
1294                 storeIndex++;
1295             }
1296             return recordCount;
1297         }
1298                 
1299         [Conditional("DEBUG")]
1300         private void VerifyValueFromStorage(DataColumn column, DataRowVersion version, object valueFromStorage) {
1301             // Dev11 900390: ignore deleted rows by adding "newRecord != -1" condition - we do not evaluate computed rows if they are deleted
1302             if (column.DataExpression != null && !inChangingEvent && tempRecord == -1 && newRecord != -1) 
1303             {
1304                 // for unchanged rows, check current if original is asked for.
1305                 // this is because by design, there is only single storage for an unchanged row.
1306                 if (version == DataRowVersion.Original && oldRecord == newRecord) {
1307                     version = DataRowVersion.Current;
1308                 }
1309                 // There are various known issues detected by this assert for non-default versions, 
1310                 // for example DevDiv2 bug 73753
1311                 // Since changes consitutute breaking change (either way customer will get another result), 
1312                 // we decided not to fix them in Dev 11
1313                 Debug.Assert(valueFromStorage.Equals(column.DataExpression.Evaluate(this, version)),
1314                     "Value from storage does lazily computed expression value"); 
1315             }
1316         } 
1317     }
1318
1319     public sealed class DataRowBuilder {
1320         internal readonly DataTable   _table;
1321         internal int                  _record;
1322
1323         internal DataRowBuilder(DataTable table, int record) {
1324             _table = table;
1325             _record = record;
1326         }
1327     }
1328 }