1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Diagnostics;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.ComponentModel;
9 using System.Globalization;
10 using System.Text;
11 
12 namespace System.Data
13 {
14     /// <summary>
15     /// Represents a databindable, customized view of a <see cref='System.Data.DataTable'/>
16     /// for sorting, filtering, searching, editing, and navigation.
17     /// </summary>
18     [DefaultProperty(nameof(Table))]
19     [DefaultEvent("PositionChanged")]
20     public partial class DataView : MarshalByValueComponent, IBindingListView, System.ComponentModel.ITypedList, ISupportInitializeNotification
21     {
22         private DataViewManager _dataViewManager;
23         private DataTable _table;
24         private bool _locked = false;
25         private Index _index;
26         private Dictionary<string, Index> _findIndexes;
27 
28         private string _sort = string.Empty;
29 
30         /// <summary>Allow a user implemented comparison of two DataRow</summary>
31         /// <remarks>User must use correct DataRowVersion in comparison or index corruption will happen</remarks>
32         private System.Comparison<DataRow> _comparison;
33 
34         /// <summary>
35         /// IFilter will allow LinqDataView to wrap <see cref='System.Predicate&lt;DataRow&gt;'/> instead of using a DataExpression
36         /// </summary>
37         private IFilter _rowFilter = null;
38 
39         private DataViewRowState _recordStates = DataViewRowState.CurrentRows;
40 
41         private bool _shouldOpen = true;
42         private bool _open = false;
43         private bool _allowNew = true;
44         private bool _allowEdit = true;
45         private bool _allowDelete = true;
46         private bool _applyDefaultSort = false;
47 
48         internal DataRow _addNewRow;
49         private ListChangedEventArgs _addNewMoved;
50 
51         private System.ComponentModel.ListChangedEventHandler _onListChanged;
52         internal static ListChangedEventArgs s_resetEventArgs = new ListChangedEventArgs(ListChangedType.Reset, -1);
53 
54         private DataTable _delayedTable = null;
55         private string _delayedRowFilter = null;
56         private string _delayedSort = null;
57         private DataViewRowState _delayedRecordStates = (DataViewRowState)(-1);
58         private bool _fInitInProgress = false;
59         private bool _fEndInitInProgress = false;
60 
61         /// <summary>
62         /// You can't delay create the DataRowView instances since multiple thread read access is valid
63         /// and each thread must obtain the same DataRowView instance and we want to avoid (inter)locking.
64         /// </summary>
65         /// <remarks>
66         /// In V1.1, the DataRowView[] was recreated after every change.  Each DataRowView was bound to a DataRow.
67         /// In V2.0 Whidbey, the DataRowView retained but bound to an index instead of DataRow, allowing the DataRow to vary.
68         /// In V2.0 Orcas, the DataRowView retained and bound to a DataRow, allowing the index to vary.
69         /// </remarks>
70         private Dictionary<DataRow, DataRowView> _rowViewCache = new Dictionary<DataRow, DataRowView>(DataRowReferenceComparer.s_default);
71 
72         /// <summary>
73         /// This collection allows expression maintenance to (add / remove) from the index when it really should be a (change / move).
74         /// </summary>
75         private readonly Dictionary<DataRow, DataRowView> _rowViewBuffer = new Dictionary<DataRow, DataRowView>(DataRowReferenceComparer.s_default);
76 
77         private sealed class DataRowReferenceComparer : IEqualityComparer<DataRow>
78         {
79             internal static readonly DataRowReferenceComparer s_default = new DataRowReferenceComparer();
80 
DataRowReferenceComparer()81             private DataRowReferenceComparer() { }
82 
Equals(DataRow x, DataRow y)83             public bool Equals(DataRow x, DataRow y) => x == (object)y;
84 
85             public int GetHashCode(DataRow obj) => obj._objectID;
86         }
87 
88         private DataViewListener _dvListener = null;
89 
90         private static int s_objectTypeCount; // Bid counter
91         private readonly int _objectID = System.Threading.Interlocked.Increment(ref s_objectTypeCount);
92 
DataView(DataTable table, bool locked)93         internal DataView(DataTable table, bool locked)
94         {
95             GC.SuppressFinalize(this);
96             DataCommonEventSource.Log.Trace("<ds.DataView.DataView|INFO> {0}, table={1}, locked={2}", ObjectID, (table != null) ? table.ObjectID : 0, locked);
97 
98             _dvListener = new DataViewListener(this);
99             _locked = locked;
100             _table = table;
101             _dvListener.RegisterMetaDataEvents(_table);
102         }
103 
104         /// <summary>
105         /// Initializes a new instance of the <see cref='System.Data.DataView'/> class.
106         /// </summary>
DataView()107         public DataView() : this(null)
108         {
109             SetIndex2("", DataViewRowState.CurrentRows, null, true);
110         }
111 
112         /// <summary>
113         /// Initializes a new instance of the <see cref='System.Data.DataView'/> class with the
114         ///    specified <see cref='System.Data.DataTable'/>.
115         /// </summary>
DataView(DataTable table)116         public DataView(DataTable table) : this(table, false)
117         {
118             SetIndex2("", DataViewRowState.CurrentRows, null, true);
119         }
120 
121         /// <summary>
122         /// Initializes a new instance of the <see cref='System.Data.DataView'/> class with the
123         ///    specified <see cref='System.Data.DataTable'/>.
124         /// </summary>
DataView(DataTable table, string RowFilter, string Sort, DataViewRowState RowState)125         public DataView(DataTable table, string RowFilter, string Sort, DataViewRowState RowState)
126         {
127             GC.SuppressFinalize(this);
128             DataCommonEventSource.Log.Trace("<ds.DataView.DataView|API> {0}, table={1}, RowFilter='{2}', Sort='{3}', RowState={4}",
129                 ObjectID, (table != null) ? table.ObjectID : 0, RowFilter, Sort, RowState);
130 
131             if (table == null)
132             {
133                 throw ExceptionBuilder.CanNotUse();
134             }
135 
136             _dvListener = new DataViewListener(this);
137             _locked = false;
138             _table = table;
139             _dvListener.RegisterMetaDataEvents(_table);
140 
141             if ((((int)RowState) & ((int)~(DataViewRowState.CurrentRows | DataViewRowState.OriginalRows))) != 0)
142             {
143                 throw ExceptionBuilder.RecordStateRange();
144             }
145             else if ((((int)RowState) & ((int)DataViewRowState.ModifiedOriginal)) != 0 &&
146                      (((int)RowState) & ((int)DataViewRowState.ModifiedCurrent)) != 0)
147             {
148                 throw ExceptionBuilder.SetRowStateFilter();
149             }
150 
151             if (Sort == null)
152             {
153                 Sort = string.Empty;
154             }
155 
156             if (RowFilter == null)
157             {
158                 RowFilter = string.Empty;
159             }
160 
161             DataExpression newFilter = new DataExpression(table, RowFilter);
162             SetIndex(Sort, RowState, newFilter);
163         }
164 
165         /// <summary>
166         /// Sets or gets a value indicating whether deletes are allowed.
167         /// </summary>
168         [DefaultValue(true)]
169         public bool AllowDelete
170         {
171             get { return _allowDelete; }
172             set
173             {
174                 if (_allowDelete != value)
175                 {
176                     _allowDelete = value;
177                     OnListChanged(s_resetEventArgs);
178                 }
179             }
180         }
181 
182         /// <summary>
183         /// Gets or sets a value indicating whether to use the default sort.
184         /// </summary>
185         [RefreshProperties(RefreshProperties.All)]
186         [DefaultValue(false)]
187         public bool ApplyDefaultSort
188         {
189             get { return _applyDefaultSort; }
190             set
191             {
192                 DataCommonEventSource.Log.Trace("<ds.DataView.set_ApplyDefaultSort|API> {0}, {1}", ObjectID, value);
193                 if (_applyDefaultSort != value)
194                 {
195                     _comparison = null; // clear the delegate to allow the Sort string to be effective
196                     _applyDefaultSort = value;
197                     UpdateIndex(true);
198                     OnListChanged(s_resetEventArgs);
199                 }
200             }
201         }
202 
203         /// <summary>
204         /// Gets or sets a value indicating whether edits are allowed.
205         /// </summary>
206         [DefaultValue(true)]
207         public bool AllowEdit
208         {
209             get { return _allowEdit; }
210             set
211             {
212                 if (_allowEdit != value)
213                 {
214                     _allowEdit = value;
215                     OnListChanged(s_resetEventArgs);
216                 }
217             }
218         }
219 
220         /// <summary>
221         /// Gets or sets a value indicating whether the new rows can
222         /// be added using the <see cref='System.Data.DataView.AddNew'/>
223         /// method.
224         /// </summary>
225         [DefaultValue(true)]
226         public bool AllowNew
227         {
228             get { return _allowNew; }
229             set
230             {
231                 if (_allowNew != value)
232                 {
233                     _allowNew = value;
234                     OnListChanged(s_resetEventArgs);
235                 }
236             }
237         }
238 
239         /// <summary>
240         /// Gets the number of records in the <see cref='System.Data.DataView'/>.
241         /// </summary>
242         [Browsable(false)]
243         public int Count
244         {
245             get
246             {
247                 Debug.Assert(_rowViewCache.Count == CountFromIndex, "DataView.Count mismatch");
248                 return _rowViewCache.Count;
249             }
250         }
251 
252         private int CountFromIndex => ((null != _index) ? _index.RecordCount : 0) + ((null != _addNewRow) ? 1 : 0);
253 
254         /// <summary>
255         /// Gets the <see cref='System.Data.DataViewManager'/> associated with this <see cref='System.Data.DataView'/> .
256         /// </summary>
257         [Browsable(false)]
258         public DataViewManager DataViewManager => _dataViewManager;
259 
260         [Browsable(false)]
261         public bool IsInitialized => !_fInitInProgress;
262 
263         /// <summary>
264         /// Gets a value indicating whether the data source is currently open and
265         /// projecting views of data on the <see cref='System.Data.DataTable'/>.
266         /// </summary>
267         [Browsable(false)]
268         protected bool IsOpen => _open;
269 
270         bool ICollection.IsSynchronized => false;
271 
272         /// <summary>
273         /// Gets or sets the expression used to filter which rows are viewed in the <see cref='System.Data.DataView'/>.
274         /// </summary>
275         [DefaultValue("")]
276         public virtual string RowFilter
277         {
278             get
279             {
280                 DataExpression expression = (_rowFilter as DataExpression);
281                 return (expression == null ? "" : expression.Expression); // CONSIDER: return optimized expression here
282             }
283             set
284             {
285                 if (value == null)
286                 {
287                     value = string.Empty;
288                 }
289                 DataCommonEventSource.Log.Trace("<ds.DataView.set_RowFilter|API> {0}, '{1}'", ObjectID, value);
290 
291                 if (_fInitInProgress)
292                 {
293                     _delayedRowFilter = value;
294                     return;
295                 }
296 
297                 CultureInfo locale = (_table != null ? _table.Locale : CultureInfo.CurrentCulture);
298                 if (null == _rowFilter || (string.Compare(RowFilter, value, false, locale) != 0))
299                 {
300                     DataExpression newFilter = new DataExpression(_table, value);
301                     SetIndex(_sort, _recordStates, newFilter);
302                 }
303             }
304         }
305 
306         #region RowPredicateFilter
307         /// <summary>
308         /// The predicate delegate that will determine if a DataRow should be contained within the view.
309         /// This RowPredicate property is mutually exclusive with the RowFilter property.
310         /// </summary>
311         internal Predicate<DataRow> RowPredicate
312         {
313             get
314             {
315                 RowPredicateFilter filter = (GetFilter() as RowPredicateFilter);
316                 return ((null != filter) ? filter._predicateFilter : null);
317             }
318             set
319             {
320                 if (!ReferenceEquals(RowPredicate, value))
321                 {
322                     SetIndex(Sort, RowStateFilter, ((null != value) ? new RowPredicateFilter(value) : null));
323                 }
324             }
325         }
326 
327         private sealed class RowPredicateFilter : System.Data.IFilter
328         {
329             internal readonly Predicate<DataRow> _predicateFilter;
330 
331             /// <summary></summary>
RowPredicateFilter(Predicate<DataRow> predicate)332             internal RowPredicateFilter(Predicate<DataRow> predicate)
333             {
334                 Debug.Assert(null != predicate, "null predicate");
335                 _predicateFilter = predicate;
336             }
337 
338             /// <summary></summary>
IFilter.Invoke(DataRow row, DataRowVersion version)339             bool IFilter.Invoke(DataRow row, DataRowVersion version)
340             {
341                 Debug.Assert(DataRowVersion.Default != version, "not expecting Default");
342                 Debug.Assert(DataRowVersion.Proposed != version, "not expecting Proposed");
343                 return _predicateFilter(row);
344             }
345         }
346         #endregion
347 
348         /// <summary>
349         /// Gets or sets the row state filter used in the <see cref='System.Data.DataView'/>.
350         /// </summary>
351         [DefaultValue(DataViewRowState.CurrentRows)]
352         public DataViewRowState RowStateFilter
353         {
354             get { return _recordStates; }
355             set
356             {
357                 DataCommonEventSource.Log.Trace("<ds.DataView.set_RowStateFilter|API> {0}, {1}", ObjectID, value);
358                 if (_fInitInProgress)
359                 {
360                     _delayedRecordStates = value;
361                     return;
362                 }
363 
364                 if ((((int)value) & ((int)~(DataViewRowState.CurrentRows | DataViewRowState.OriginalRows))) != 0)
365                 {
366                     throw ExceptionBuilder.RecordStateRange();
367                 }
368                 else if ((((int)value) & ((int)DataViewRowState.ModifiedOriginal)) != 0 &&
369                         (((int)value) & ((int)DataViewRowState.ModifiedCurrent)) != 0)
370                 {
371                     throw ExceptionBuilder.SetRowStateFilter();
372                 }
373 
374                 if (_recordStates != value)
375                 {
376                     SetIndex(_sort, value, _rowFilter);
377                 }
378             }
379         }
380 
381         /// <summary>
382         /// Gets or sets the sort column or columns, and sort order for the table.
383         /// </summary>
384         [DefaultValue("")]
385         public string Sort
386         {
387             get
388             {
389                 if (_sort.Length == 0 && _applyDefaultSort && _table != null && _table._primaryIndex.Length > 0)
390                 {
391                     return _table.FormatSortString(_table._primaryIndex);
392                 }
393                 else
394                 {
395                     return _sort;
396                 }
397             }
398             set
399             {
400                 if (value == null)
401                 {
402                     value = string.Empty;
403                 }
404                 DataCommonEventSource.Log.Trace("<ds.DataView.set_Sort|API> {0}, '{1}'", ObjectID, value);
405 
406                 if (_fInitInProgress)
407                 {
408                     _delayedSort = value;
409                     return;
410                 }
411 
412                 CultureInfo locale = (_table != null ? _table.Locale : CultureInfo.CurrentCulture);
413                 if (string.Compare(_sort, value, false, locale) != 0 || (null != _comparison))
414                 {
415                     CheckSort(value);
416                     _comparison = null; // clear the delegate to allow the Sort string to be effective
417                     SetIndex(value, _recordStates, _rowFilter);
418                 }
419             }
420         }
421 
422         /// <summary>Allow a user implemented comparison of two DataRow</summary>
423         /// <remarks>User must use correct DataRowVersion in comparison or index corruption will happen</remarks>
424         internal System.Comparison<DataRow> SortComparison
425         {
426             get { return _comparison; }
427             set
428             {
429                 DataCommonEventSource.Log.Trace("<ds.DataView.set_SortComparison|API> {0}", ObjectID);
430                 if (!ReferenceEquals(_comparison, value))
431                 {
432                     _comparison = value;
433                     SetIndex("", _recordStates, _rowFilter);
434                 }
435             }
436         }
437 
438         object ICollection.SyncRoot => this;
439 
440         /// <summary>
441         /// Gets or sets the source <see cref='System.Data.DataTable'/>.
442         /// </summary>
443         [TypeConverterAttribute(typeof(DataTableTypeConverter))]
444         [DefaultValue(null)]
445         [RefreshProperties(RefreshProperties.All)]
446         public DataTable Table
447         {
448             get { return _table; }
449             set
450             {
451                 DataCommonEventSource.Log.Trace("<ds.DataView.set_Table|API> {0}, {1}", ObjectID, (value != null) ? value.ObjectID : 0);
452                 if (_fInitInProgress && value != null)
453                 {
454                     _delayedTable = value;
455                     return;
456                 }
457 
458                 if (_locked)
459                 {
460                     throw ExceptionBuilder.SetTable();
461                 }
462                 if (_dataViewManager != null)
463                 {
464                     throw ExceptionBuilder.CanNotSetTable();
465                 }
466                 if (value != null && value.TableName.Length == 0)
467                 {
468                     throw ExceptionBuilder.CanNotBindTable();
469                 }
470 
471                 if (_table != value)
472                 {
473                     _dvListener.UnregisterMetaDataEvents();
474                     _table = value;
475                     if (_table != null)
476                     {
477                         _dvListener.RegisterMetaDataEvents(_table);
478                     }
479 
480                     SetIndex2("", DataViewRowState.CurrentRows, null, false);
481                     if (_table != null)
482                     {
483                         OnListChanged(new ListChangedEventArgs(ListChangedType.PropertyDescriptorChanged, new DataTablePropertyDescriptor(_table)));
484                     }
485                     // index was updated without firing the reset, fire it now
486                     OnListChanged(s_resetEventArgs);
487                 }
488             }
489         }
490 
491         object IList.this[int recordIndex]
492         {
493             get { return this[recordIndex]; }
494             set { throw ExceptionBuilder.SetIListObject(); }
495         }
496 
497         /// <summary>
498         /// Gets a row of data from a specified table.
499         /// </summary>
500         public DataRowView this[int recordIndex] => GetRowView(GetRow(recordIndex));
501 
502         /// <summary>
503         /// Adds a new row of data to view.
504         /// </summary>
505         /// <remarks>
506         /// Only one new row of data allowed at a time, so previous new row will be added to row collection.
507         /// Unsupported pattern: dataTable.Rows.Add(dataView.AddNew().Row)
508         /// </remarks>
AddNew()509         public virtual DataRowView AddNew()
510         {
511             long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataView.AddNew|API> {0}", ObjectID);
512             try
513             {
514                 CheckOpen();
515 
516                 if (!AllowNew)
517                 {
518                     throw ExceptionBuilder.AddNewNotAllowNull();
519                 }
520                 if (_addNewRow != null)
521                 {
522                     _rowViewCache[_addNewRow].EndEdit();
523                 }
524 
525                 Debug.Assert(null == _addNewRow, "AddNew addNewRow is not null");
526 
527                 _addNewRow = _table.NewRow();
528                 DataRowView drv = new DataRowView(this, _addNewRow);
529                 _rowViewCache.Add(_addNewRow, drv);
530                 OnListChanged(new ListChangedEventArgs(ListChangedType.ItemAdded, IndexOf(drv)));
531                 return drv;
532             }
533             finally
534             {
535                 DataCommonEventSource.Log.ExitScope(logScopeId);
536             }
537         }
538 
BeginInit()539         public void BeginInit()
540         {
541             _fInitInProgress = true;
542         }
543 
EndInit()544         public void EndInit()
545         {
546             if (_delayedTable != null && _delayedTable.fInitInProgress)
547             {
548                 _delayedTable._delayedViews.Add(this);
549                 return;
550             }
551 
552             _fInitInProgress = false;
553             _fEndInitInProgress = true;
554             if (_delayedTable != null)
555             {
556                 Table = _delayedTable;
557                 _delayedTable = null;
558             }
559             if (_delayedSort != null)
560             {
561                 Sort = _delayedSort;
562                 _delayedSort = null;
563             }
564             if (_delayedRowFilter != null)
565             {
566                 RowFilter = _delayedRowFilter;
567                 _delayedRowFilter = null;
568             }
569             if (_delayedRecordStates != (DataViewRowState)(-1))
570             {
571                 RowStateFilter = _delayedRecordStates;
572                 _delayedRecordStates = (DataViewRowState)(-1);
573             }
574             _fEndInitInProgress = false;
575 
576             SetIndex(Sort, RowStateFilter, _rowFilter);
577             OnInitialized();
578         }
579 
CheckOpen()580         private void CheckOpen()
581         {
582             if (!IsOpen) throw ExceptionBuilder.NotOpen();
583         }
584 
CheckSort(string sort)585         private void CheckSort(string sort)
586         {
587             if (_table == null)
588             {
589                 throw ExceptionBuilder.CanNotUse();
590             }
591             if (sort.Length == 0)
592             {
593                 return;
594             }
595             _table.ParseSortString(sort);
596         }
597 
598         /// <summary>
599         /// Closes the <see cref='System.Data.DataView'/>
600         /// </summary>
Close()601         protected void Close()
602         {
603             _shouldOpen = false;
604             UpdateIndex();
605             _dvListener.UnregisterMetaDataEvents();
606         }
607 
CopyTo(Array array, int index)608         public void CopyTo(Array array, int index)
609         {
610             if (null != _index)
611             {
612                 RBTree<int>.RBTreeEnumerator iterator = _index.GetEnumerator(0);
613                 while (iterator.MoveNext())
614                 {
615                     array.SetValue(GetRowView(iterator.Current), index);
616                     checked
617                     {
618                         index++;
619                     }
620                 }
621             }
622             if (null != _addNewRow)
623             {
624                 array.SetValue(_rowViewCache[_addNewRow], index);
625             }
626         }
627 
CopyTo(DataRowView[] array, int index)628         private void CopyTo(DataRowView[] array, int index)
629         {
630             if (null != _index)
631             {
632                 RBTree<int>.RBTreeEnumerator iterator = _index.GetEnumerator(0);
633                 while (iterator.MoveNext())
634                 {
635                     array[index] = GetRowView(iterator.Current);
636                     checked
637                     {
638                         index++;
639                     }
640                 }
641             }
642             if (null != _addNewRow)
643             {
644                 array[index] = _rowViewCache[_addNewRow];
645             }
646         }
647 
648         /// <summary>
649         /// Deletes a row at the specified index.
650         /// </summary>
Delete(int index)651         public void Delete(int index) => Delete(GetRow(index));
652 
Delete(DataRow row)653         internal void Delete(DataRow row)
654         {
655             if (null != row)
656             {
657                 long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataView.Delete|API> {0}, row={1}", ObjectID, row._objectID);
658                 try
659                 {
660                     CheckOpen();
661                     if (row == _addNewRow)
662                     {
663                         FinishAddNew(false);
664                         return;
665                     }
666                     if (!AllowDelete)
667                     {
668                         throw ExceptionBuilder.CanNotDelete();
669                     }
670                     row.Delete();
671                 }
672                 finally
673                 {
674                     DataCommonEventSource.Log.ExitScope(logScopeId);
675                 }
676             }
677         }
678 
Dispose(bool disposing)679         protected override void Dispose(bool disposing)
680         {
681             if (disposing)
682             {
683                 Close();
684             }
685             base.Dispose(disposing);
686         }
687 
688         /// <summary>
689         /// Finds a row in the <see cref='System.Data.DataView'/> by the specified primary key value.
690         /// </summary>
691         public int Find(object key) => FindByKey(key);
692 
693         /// <summary>Find index of a DataRowView instance that matches the specified primary key value.</summary>
FindByKey(object key)694         internal virtual int FindByKey(object key) => _index.FindRecordByKey(key);
695 
696         /// <summary>
697         /// Finds a row in the <see cref='System.Data.DataView'/> by the specified primary key values.
698         /// </summary>
699         public int Find(object[] key) => FindByKey(key);
700 
701         /// <summary>Find index of a DataRowView instance that matches the specified primary key values.</summary>
FindByKey(object[] key)702         internal virtual int FindByKey(object[] key) => _index.FindRecordByKey(key);
703 
704         /// <summary>
705         /// Finds a row in the <see cref='System.Data.DataView'/> by the specified primary key value.
706         /// </summary>
707         public DataRowView[] FindRows(object key) => FindRowsByKey(new object[] { key });
708 
709         /// <summary>
710         /// Finds a row in the <see cref='System.Data.DataView'/> by the specified primary key values.
711         /// </summary>
712         public DataRowView[] FindRows(object[] key) => FindRowsByKey(key);
713 
714         /// <summary>Find DataRowView instances that match the specified primary key values.</summary>
FindRowsByKey(object[] key)715         internal virtual DataRowView[] FindRowsByKey(object[] key)
716         {
717             long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataView.FindRows|API> {0}", ObjectID);
718             try
719             {
720                 Range range = _index.FindRecords(key);
721                 return GetDataRowViewFromRange(range);
722             }
723             finally
724             {
725                 DataCommonEventSource.Log.ExitScope(logScopeId);
726             }
727         }
728 
729         /// <summary>Convert a Range into a DataRowView[].</summary>
GetDataRowViewFromRange(Range range)730         internal DataRowView[] GetDataRowViewFromRange(Range range)
731         {
732             if (range.IsNull)
733             {
734                 return Array.Empty<DataRowView>();
735             }
736 
737             var rows = new DataRowView[range.Count];
738             for (int i = 0; i < rows.Length; i++)
739             {
740                 rows[i] = this[i + range.Min];
741             }
742             return rows;
743         }
744 
FinishAddNew(bool success)745         internal void FinishAddNew(bool success)
746         {
747             Debug.Assert(null != _addNewRow, "null addNewRow");
748             DataCommonEventSource.Log.Trace("<ds.DataView.FinishAddNew|INFO> {0}, success={1}", ObjectID, success);
749 
750             DataRow newRow = _addNewRow;
751             if (success)
752             {
753                 if (DataRowState.Detached == newRow.RowState)
754                 {
755                     // MaintainDataView will translate the ItemAdded from the RowCollection into
756                     // into either an ItemMoved or no event, since it didn't change position.
757                     // also possible it's added to the RowCollection but filtered out of the view.
758                     _table.Rows.Add(newRow);
759                 }
760                 else
761                 {
762                     // this means that the record was added to the table by different means and not part of view
763                     newRow.EndEdit();
764                 }
765             }
766 
767             if (newRow == _addNewRow)
768             {
769                 // this means that the record did not get to the view
770                 bool flag = _rowViewCache.Remove(_addNewRow);
771                 Debug.Assert(flag, "didn't remove addNewRow");
772                 _addNewRow = null;
773 
774                 if (!success)
775                 {
776                     newRow.CancelEdit();
777                 }
778                 OnListChanged(new ListChangedEventArgs(ListChangedType.ItemDeleted, Count));
779             }
780         }
781 
782         /// <summary>
783         /// xGets an enumerator for this <see cref='System.Data.DataView'/>.
784         /// </summary>
GetEnumerator()785         public IEnumerator GetEnumerator()
786         {
787             // V1.1 compatability: returning List<DataRowView>.GetEnumerator() from RowViewCache
788             // prevents users from changing data without invalidating the enumerator
789             // aka don't 'return this.RowViewCache.GetEnumerator()'
790             var temp = new DataRowView[Count];
791             CopyTo(temp, 0);
792             return temp.GetEnumerator();
793         }
794 
795         #region IList
796 
797         bool IList.IsReadOnly => false;
798 
799         bool IList.IsFixedSize => false;
800 
IList.Add(object value)801         int IList.Add(object value)
802         {
803             if (value == null)
804             {
805                 // null is default value, so we AddNew.
806                 AddNew();
807                 return Count - 1;
808             }
809             throw ExceptionBuilder.AddExternalObject();
810         }
811 
IList.Clear()812         void IList.Clear()
813         {
814             throw ExceptionBuilder.CanNotClear();
815         }
816 
817         bool IList.Contains(object value) => (0 <= IndexOf(value as DataRowView));
818 
819         int IList.IndexOf(object value) => IndexOf(value as DataRowView);
820 
821         /// <summary>Return positional index of a <see cref="DataRowView"/> in this DataView</summary>
822         /// <remarks>Behavioral change: will now return -1 once a DataRowView becomes detached.</remarks>
IndexOf(DataRowView rowview)823         internal int IndexOf(DataRowView rowview)
824         {
825             if (null != rowview)
826             {
827                 if (ReferenceEquals(_addNewRow, rowview.Row))
828                 {
829                     return Count - 1;
830                 }
831                 if ((null != _index) && (DataRowState.Detached != rowview.Row.RowState))
832                 {
833                     DataRowView cached; // verify the DataRowView is one we currently track - not something previously detached
834                     if (_rowViewCache.TryGetValue(rowview.Row, out cached) && cached == (object)rowview)
835                     {
836                         return IndexOfDataRowView(rowview);
837                     }
838                 }
839             }
840             return -1;
841         }
842 
IndexOfDataRowView(DataRowView rowview)843         private int IndexOfDataRowView(DataRowView rowview)
844         {
845             // rowview.GetRecord() may return the proposed record
846             // the index will only contain the original or current record, never proposed.
847             // return index.GetIndex(rowview.GetRecord());
848             return _index.GetIndex(rowview.Row.GetRecordFromVersion(rowview.Row.GetDefaultRowVersion(RowStateFilter) & ~DataRowVersion.Proposed));
849         }
850 
IList.Insert(int index, object value)851         void IList.Insert(int index, object value)
852         {
853             throw ExceptionBuilder.InsertExternalObject();
854         }
855 
IList.Remove(object value)856         void IList.Remove(object value)
857         {
858             int index = IndexOf(value as DataRowView);
859             if (0 <= index)
860             {
861                 // must delegate to IList.RemoveAt
862                 ((IList)this).RemoveAt(index);
863             }
864             else
865             {
866                 throw ExceptionBuilder.RemoveExternalObject();
867             }
868         }
869 
IList.RemoveAt(int index)870         void IList.RemoveAt(int index) => Delete(index);
871 
GetFindIndex(string column, bool keepIndex)872         internal Index GetFindIndex(string column, bool keepIndex)
873         {
874             if (_findIndexes == null)
875             {
876                 _findIndexes = new Dictionary<string, Index>();
877             }
878 
879             Index findIndex;
880             if (_findIndexes.TryGetValue(column, out findIndex))
881             {
882                 if (!keepIndex)
883                 {
884                     _findIndexes.Remove(column);
885                     findIndex.RemoveRef();
886                     if (findIndex.RefCount == 1)
887                     { // if we have created it and we are removing it, refCount is (1)
888                         findIndex.RemoveRef(); // if we are reusing the index created by others, refcount is (2)
889                     }
890                 }
891             }
892             else
893             {
894                 if (keepIndex)
895                 {
896                     findIndex = _table.GetIndex(column, _recordStates, GetFilter());
897                     _findIndexes[column] = findIndex;
898                     findIndex.AddRef();
899                 }
900             }
901             return findIndex;
902         }
903 
904         #endregion
905 
906         #region IBindingList implementation
907 
908         bool IBindingList.AllowNew => AllowNew;
IBindingList.AddNew()909         object IBindingList.AddNew() => AddNew();
910         bool IBindingList.AllowEdit => AllowEdit;
911         bool IBindingList.AllowRemove => AllowDelete;
912 
913         bool IBindingList.SupportsChangeNotification => true;
914         bool IBindingList.SupportsSearching => true;
915         bool IBindingList.SupportsSorting => true;
916         bool IBindingList.IsSorted => Sort.Length != 0;
917         PropertyDescriptor IBindingList.SortProperty => GetSortProperty();
918 
GetSortProperty()919         internal PropertyDescriptor GetSortProperty()
920         {
921             if (_table != null && _index != null && _index._indexFields.Length == 1)
922             {
923                 return new DataColumnPropertyDescriptor(_index._indexFields[0].Column);
924             }
925             return null;
926         }
927 
928         ListSortDirection IBindingList.SortDirection => (_index._indexFields.Length == 1 && _index._indexFields[0].IsDescending) ?
929             ListSortDirection.Descending :
930             ListSortDirection.Ascending;
931         #endregion
932 
933         #region ListChanged & Initialized events
934 
935         /// <summary>
936         /// Occurs when the list managed by the <see cref='System.Data.DataView'/> changes.
937         /// </summary>
938         public event ListChangedEventHandler ListChanged
939         {
940             add
941             {
942                 DataCommonEventSource.Log.Trace("<ds.DataView.add_ListChanged|API> {0}", ObjectID);
943                 _onListChanged += value;
944             }
945             remove
946             {
947                 DataCommonEventSource.Log.Trace("<ds.DataView.remove_ListChanged|API> {0}", ObjectID);
948                 _onListChanged -= value;
949             }
950         }
951 
952         public event EventHandler Initialized;
953 
954         #endregion
955 
956         #region IBindingList implementation
957 
958         void IBindingList.AddIndex(PropertyDescriptor property) => GetFindIndex(property.Name, keepIndex: true);
959 
IBindingList.ApplySort(PropertyDescriptor property, ListSortDirection direction)960         void IBindingList.ApplySort(PropertyDescriptor property, ListSortDirection direction)
961         {
962             Sort = CreateSortString(property, direction);
963         }
964 
IBindingList.Find(PropertyDescriptor property, object key)965         int IBindingList.Find(PropertyDescriptor property, object key)
966         {
967             // NOTE: this function had keepIndex previosely
968             if (property != null)
969             {
970                 bool created = false;
971                 Index findIndex = null;
972                 try
973                 {
974                     if ((null == _findIndexes) || !_findIndexes.TryGetValue(property.Name, out findIndex))
975                     {
976                         created = true;
977                         findIndex = _table.GetIndex(property.Name, _recordStates, GetFilter());
978                         findIndex.AddRef();
979                     }
980                     Range recordRange = findIndex.FindRecords(key);
981 
982                     if (!recordRange.IsNull)
983                     {
984                         // check to see if key is equal
985                         return _index.GetIndex(findIndex.GetRecord(recordRange.Min));
986                     }
987                 }
988                 finally
989                 {
990                     if (created && (null != findIndex))
991                     {
992                         findIndex.RemoveRef();
993                         if (findIndex.RefCount == 1)
994                         {
995                             // if we have created it and we are removing it, refCount is (1)
996                             findIndex.RemoveRef(); // if we are reusing the index created by others, refcount is (2)
997                         }
998                     }
999                 }
1000             }
1001             return -1;
1002         }
1003 
IBindingList.RemoveIndex(PropertyDescriptor property)1004         void IBindingList.RemoveIndex(PropertyDescriptor property)
1005         {
1006             // Ups: If we don't have index yet we will create it before destroing; Fix this later
1007             GetFindIndex(property.Name, /*keepIndex:*/false);
1008         }
1009 
IBindingList.RemoveSort()1010         void IBindingList.RemoveSort()
1011         {
1012             DataCommonEventSource.Log.Trace("<ds.DataView.RemoveSort|API> {0}", ObjectID);
1013             Sort = string.Empty;
1014         }
1015 
1016         #endregion
1017 
1018         #region Additional method and properties for new interface IBindingListView
1019 
IBindingListView.ApplySort(ListSortDescriptionCollection sorts)1020         void IBindingListView.ApplySort(ListSortDescriptionCollection sorts)
1021         {
1022             if (sorts == null)
1023             {
1024                 throw ExceptionBuilder.ArgumentNull(nameof(sorts));
1025             }
1026 
1027             var sortString = new StringBuilder();
1028             bool addCommaToString = false;
1029             foreach (ListSortDescription sort in sorts)
1030             {
1031                 if (sort == null)
1032                 {
1033                     throw ExceptionBuilder.ArgumentContainsNull(nameof(sorts));
1034                 }
1035                 PropertyDescriptor property = sort.PropertyDescriptor;
1036 
1037                 if (property == null)
1038                 {
1039                     throw ExceptionBuilder.ArgumentNull(nameof(PropertyDescriptor));
1040                 }
1041 
1042                 if (!_table.Columns.Contains(property.Name))
1043                 {
1044                     // just check if column does not exist, we will handle duplicate column in Sort
1045                     throw ExceptionBuilder.ColumnToSortIsOutOfRange(property.Name);
1046                 }
1047                 ListSortDirection direction = sort.SortDirection;
1048 
1049                 if (addCommaToString) // (sortStr.Length != 0)
1050                 {
1051                     sortString.Append(',');
1052                 }
1053                 sortString.Append(CreateSortString(property, direction));
1054 
1055                 if (!addCommaToString)
1056                 {
1057                     addCommaToString = true;
1058                 }
1059             }
1060             Sort = sortString.ToString(); // what if we dont have any valid sort criteira? we would reset the sort
1061         }
1062 
CreateSortString(PropertyDescriptor property, ListSortDirection direction)1063         private string CreateSortString(PropertyDescriptor property, ListSortDirection direction)
1064         {
1065             Debug.Assert(property != null, "property is null");
1066             StringBuilder resultString = new StringBuilder();
1067             resultString.Append('[');
1068             resultString.Append(property.Name);
1069             resultString.Append(']');
1070             if (ListSortDirection.Descending == direction)
1071             {
1072                 resultString.Append(" DESC");
1073             }
1074 
1075             return resultString.ToString();
1076         }
1077 
IBindingListView.RemoveFilter()1078         void IBindingListView.RemoveFilter()
1079         {
1080             DataCommonEventSource.Log.Trace("<ds.DataView.RemoveFilter|API> {0}", ObjectID);
1081             RowFilter = string.Empty;
1082         }
1083 
1084         string IBindingListView.Filter
1085         {
1086             get { return RowFilter; }
1087             set { RowFilter = value; }
1088         }
1089 
1090         ListSortDescriptionCollection IBindingListView.SortDescriptions => GetSortDescriptions();
1091 
GetSortDescriptions()1092         internal ListSortDescriptionCollection GetSortDescriptions()
1093         {
1094             ListSortDescription[] sortDescArray = Array.Empty<ListSortDescription>();
1095             if (_table != null && _index != null && _index._indexFields.Length > 0)
1096             {
1097                 sortDescArray = new ListSortDescription[_index._indexFields.Length];
1098                 for (int i = 0; i < _index._indexFields.Length; i++)
1099                 {
1100                     DataColumnPropertyDescriptor columnProperty = new DataColumnPropertyDescriptor(_index._indexFields[i].Column);
1101                     if (_index._indexFields[i].IsDescending)
1102                     {
1103                         sortDescArray[i] = new ListSortDescription(columnProperty, ListSortDirection.Descending);
1104                     }
1105                     else
1106                     {
1107                         sortDescArray[i] = new ListSortDescription(columnProperty, ListSortDirection.Ascending);
1108                     }
1109                 }
1110             }
1111             return new ListSortDescriptionCollection(sortDescArray);
1112         }
1113 
1114 
1115         bool IBindingListView.SupportsAdvancedSorting => true;
1116 
1117         bool IBindingListView.SupportsFiltering => true;
1118 
1119         #endregion
1120 
1121         #region ITypedList
1122 
System.ComponentModel.ITypedList.GetListName(PropertyDescriptor[] listAccessors)1123         string System.ComponentModel.ITypedList.GetListName(PropertyDescriptor[] listAccessors)
1124         {
1125             if (_table != null)
1126             {
1127                 if (listAccessors == null || listAccessors.Length == 0)
1128                 {
1129                     return _table.TableName;
1130                 }
1131                 else
1132                 {
1133                     DataSet dataSet = _table.DataSet;
1134                     if (dataSet != null)
1135                     {
1136                         DataTable foundTable = dataSet.FindTable(_table, listAccessors, 0);
1137                         if (foundTable != null)
1138                         {
1139                             return foundTable.TableName;
1140                         }
1141                     }
1142                 }
1143             }
1144             return string.Empty;
1145         }
1146 
System.ComponentModel.ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors)1147         PropertyDescriptorCollection System.ComponentModel.ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors)
1148         {
1149             if (_table != null)
1150             {
1151                 if (listAccessors == null || listAccessors.Length == 0)
1152                 {
1153                     return _table.GetPropertyDescriptorCollection(null);
1154                 }
1155                 else
1156                 {
1157                     DataSet dataSet = _table.DataSet;
1158                     if (dataSet == null)
1159                     {
1160                         return new PropertyDescriptorCollection(null);
1161                     }
1162 
1163                     DataTable foundTable = dataSet.FindTable(_table, listAccessors, 0);
1164                     if (foundTable != null)
1165                     {
1166                         return foundTable.GetPropertyDescriptorCollection(null);
1167                     }
1168                 }
1169             }
1170             return new PropertyDescriptorCollection(null);
1171         }
1172 
1173         #endregion
1174 
1175         /// <summary>
1176         /// Gets the filter for the <see cref='System.Data.DataView'/>.
1177         /// </summary>
GetFilter()1178         internal virtual IFilter GetFilter() => _rowFilter;
1179 
GetRecord(int recordIndex)1180         private int GetRecord(int recordIndex)
1181         {
1182             if (unchecked((uint)Count <= (uint)recordIndex))
1183             {
1184                 throw ExceptionBuilder.RowOutOfRange(recordIndex);
1185             }
1186 
1187             return recordIndex == _index.RecordCount ?
1188                 _addNewRow.GetDefaultRecord() :
1189                 _index.GetRecord(recordIndex);
1190         }
1191 
1192         /// <exception cref="IndexOutOfRangeException"></exception>
GetRow(int index)1193         internal DataRow GetRow(int index)
1194         {
1195             int count = Count;
1196             if (unchecked((uint)count <= (uint)index))
1197             {
1198                 throw ExceptionBuilder.GetElementIndex(index);
1199             }
1200             if ((index == (count - 1)) && (_addNewRow != null))
1201             {
1202                 // if we could rely on tempRecord being registered with recordManager
1203                 // then this special case code would go away
1204                 return _addNewRow;
1205             }
1206             return _table._recordManager[GetRecord(index)];
1207         }
1208 
GetRowView(int record)1209         private DataRowView GetRowView(int record) => GetRowView(_table._recordManager[record]);
1210 
1211         private DataRowView GetRowView(DataRow dr) => _rowViewCache[dr];
1212 
IndexListChanged(object sender, ListChangedEventArgs e)1213         protected virtual void IndexListChanged(object sender, ListChangedEventArgs e)
1214         {
1215             if (ListChangedType.Reset != e.ListChangedType)
1216             {
1217                 OnListChanged(e);
1218             }
1219 
1220             if (_addNewRow != null && _index.RecordCount == 0)
1221             {
1222                 FinishAddNew(false);
1223             }
1224 
1225             if (ListChangedType.Reset == e.ListChangedType)
1226             {
1227                 OnListChanged(e);
1228             }
1229         }
1230 
IndexListChangedInternal(ListChangedEventArgs e)1231         internal void IndexListChangedInternal(ListChangedEventArgs e)
1232         {
1233             _rowViewBuffer.Clear();
1234 
1235             if ((ListChangedType.ItemAdded == e.ListChangedType) && (null != _addNewMoved))
1236             {
1237                 if (_addNewMoved.NewIndex == _addNewMoved.OldIndex)
1238                 {
1239                     // ItemAdded for addNewRow which didn't change position
1240                     // RowStateChange only triggers RowChanged, not ListChanged
1241                 }
1242                 else
1243                 {
1244                     // translate the ItemAdded into ItemMoved for addNewRow adding into sorted collection
1245                     ListChangedEventArgs f = _addNewMoved;
1246                     _addNewMoved = null;
1247                     IndexListChanged(this, f);
1248                 }
1249             }
1250             // the ItemAdded has to fire twice for AddNewRow (public IBindingList API documentation)
1251             IndexListChanged(this, e);
1252         }
1253 
MaintainDataView(ListChangedType changedType, DataRow row, bool trackAddRemove)1254         internal void MaintainDataView(ListChangedType changedType, DataRow row, bool trackAddRemove)
1255         {
1256             DataRowView buffer = null;
1257             switch (changedType)
1258             {
1259                 case ListChangedType.ItemAdded:
1260                     Debug.Assert(null != row, "MaintainDataView.ItemAdded with null DataRow");
1261                     if (trackAddRemove)
1262                     {
1263                         if (_rowViewBuffer.TryGetValue(row, out buffer))
1264                         {
1265                             // help turn expression add/remove into a changed/move
1266                             bool flag = _rowViewBuffer.Remove(row);
1267                             Debug.Assert(flag, "row actually removed");
1268                         }
1269                     }
1270                     if (row == _addNewRow)
1271                     {
1272                         // DataView.AddNew().Row was added to DataRowCollection
1273                         int index = IndexOfDataRowView(_rowViewCache[_addNewRow]);
1274                         Debug.Assert(0 <= index, "ItemAdded was actually deleted");
1275 
1276                         _addNewRow = null;
1277                         _addNewMoved = new ListChangedEventArgs(ListChangedType.ItemMoved, index, Count - 1);
1278                     }
1279                     else if (!_rowViewCache.ContainsKey(row))
1280                     {
1281                         _rowViewCache.Add(row, buffer ?? new DataRowView(this, row));
1282                     }
1283                     else
1284                     {
1285                         Debug.Assert(false, "ItemAdded DataRow already in view");
1286                     }
1287                     break;
1288                 case ListChangedType.ItemDeleted:
1289                     Debug.Assert(null != row, "MaintainDataView.ItemDeleted with null DataRow");
1290                     Debug.Assert(row != _addNewRow, "addNewRow being deleted");
1291 
1292                     if (trackAddRemove)
1293                     {
1294                         // help turn expression add/remove into a changed/move
1295                         _rowViewCache.TryGetValue(row, out buffer);
1296                         if (null != buffer)
1297                         {
1298                             _rowViewBuffer.Add(row, buffer);
1299                         }
1300                         else
1301                         {
1302                             Debug.Assert(false, "ItemDeleted DataRow not in view tracking");
1303                         }
1304                     }
1305                     if (!_rowViewCache.Remove(row))
1306                     {
1307                         Debug.Assert(false, "ItemDeleted DataRow not in view");
1308                     }
1309                     break;
1310                 case ListChangedType.Reset:
1311                     Debug.Assert(null == row, "MaintainDataView.Reset with non-null DataRow");
1312                     ResetRowViewCache();
1313                     break;
1314                 case ListChangedType.ItemChanged:
1315                 case ListChangedType.ItemMoved:
1316                     break;
1317                 case ListChangedType.PropertyDescriptorAdded:
1318                 case ListChangedType.PropertyDescriptorChanged:
1319                 case ListChangedType.PropertyDescriptorDeleted:
1320                     Debug.Assert(false, "unexpected");
1321                     break;
1322             }
1323         }
1324 
1325         /// <summary>
1326         /// Raises the <see cref='E:System.Data.DataView.ListChanged'/> event.
1327         /// </summary>
OnListChanged(ListChangedEventArgs e)1328         protected virtual void OnListChanged(ListChangedEventArgs e)
1329         {
1330             DataCommonEventSource.Log.Trace("<ds.DataView.OnListChanged|INFO> {0}, ListChangedType={1}", ObjectID, e.ListChangedType);
1331             try
1332             {
1333                 DataColumn col = null;
1334                 string propertyName = null;
1335                 switch (e.ListChangedType)
1336                 {
1337                     case ListChangedType.ItemChanged:
1338                     // ItemChanged - a column value changed (0 <= e.OldIndex)
1339                     // ItemChanged - a DataRow.RowError changed (-1 == e.OldIndex)
1340                     // ItemChanged - RowState changed (e.NewIndex == e.OldIndex)
1341 
1342                     case ListChangedType.ItemMoved:
1343                         // ItemMoved - a column value affecting sort order changed
1344                         // ItemMoved - a state change in equivalent fields
1345                         Debug.Assert(((ListChangedType.ItemChanged == e.ListChangedType) && ((e.NewIndex == e.OldIndex) || (-1 == e.OldIndex))) ||
1346                                      (ListChangedType.ItemMoved == e.ListChangedType && (e.NewIndex != e.OldIndex) && (0 <= e.OldIndex)),
1347                                      "unexpected ItemChanged|ItemMoved");
1348 
1349                         Debug.Assert(0 <= e.NewIndex, "negative NewIndex");
1350                         if (0 <= e.NewIndex)
1351                         {
1352                             DataRow dr = GetRow(e.NewIndex);
1353                             if (dr.HasPropertyChanged)
1354                             {
1355                                 col = dr.LastChangedColumn;
1356                                 propertyName = (null != col) ? col.ColumnName : string.Empty;
1357                             }
1358                         }
1359 
1360                         break;
1361 
1362                     case ListChangedType.ItemAdded:
1363                     case ListChangedType.ItemDeleted:
1364                     case ListChangedType.PropertyDescriptorAdded:
1365                     case ListChangedType.PropertyDescriptorChanged:
1366                     case ListChangedType.PropertyDescriptorDeleted:
1367                     case ListChangedType.Reset:
1368                         break;
1369                 }
1370 
1371                 if (_onListChanged != null)
1372                 {
1373                     if ((col != null) && (e.NewIndex == e.OldIndex))
1374                     {
1375                         ListChangedEventArgs newEventArg = new ListChangedEventArgs(e.ListChangedType, e.NewIndex, new DataColumnPropertyDescriptor(col));
1376                         _onListChanged(this, newEventArg);
1377                     }
1378                     else
1379                     {
1380                         _onListChanged(this, e);
1381                     }
1382                 }
1383                 if (null != propertyName)
1384                 {
1385                     // empty string if more than 1 column changed
1386                     this[e.NewIndex].RaisePropertyChangedEvent(propertyName);
1387                 }
1388             }
1389             catch (Exception f) when (Common.ADP.IsCatchableExceptionType(f))
1390             {
1391                 ExceptionBuilder.TraceExceptionWithoutRethrow(f); // ignore the exception
1392             }
1393         }
1394 
OnInitialized()1395         private void OnInitialized()
1396         {
1397             Initialized?.Invoke(this, EventArgs.Empty);
1398         }
1399 
1400         /// <summary>
1401         /// Opens a <see cref='System.Data.DataView'/>.
1402         /// </summary>
Open()1403         protected void Open()
1404         {
1405             _shouldOpen = true;
1406             UpdateIndex();
1407             _dvListener.RegisterMetaDataEvents(_table);
1408         }
1409 
Reset()1410         protected void Reset()
1411         {
1412             if (IsOpen)
1413             {
1414                 _index.Reset();
1415             }
1416         }
1417 
ResetRowViewCache()1418         internal void ResetRowViewCache()
1419         {
1420             Dictionary<DataRow, DataRowView> rvc = new Dictionary<DataRow, DataRowView>(CountFromIndex, DataRowReferenceComparer.s_default);
1421             DataRowView drv;
1422 
1423             if (null != _index)
1424             {
1425                 // this improves performance by iterating of the index instead of computing record by index
1426                 RBTree<int>.RBTreeEnumerator iterator = _index.GetEnumerator(0);
1427                 while (iterator.MoveNext())
1428                 {
1429                     DataRow row = _table._recordManager[iterator.Current];
1430                     if (!_rowViewCache.TryGetValue(row, out drv))
1431                     {
1432                         drv = new DataRowView(this, row);
1433                     }
1434                     rvc.Add(row, drv);
1435                 }
1436             }
1437             if (null != _addNewRow)
1438             {
1439                 _rowViewCache.TryGetValue(_addNewRow, out drv);
1440                 Debug.Assert(null != drv, "didn't contain addNewRow");
1441                 rvc.Add(_addNewRow, drv);
1442             }
1443             Debug.Assert(rvc.Count == CountFromIndex, "didn't add expected count");
1444             _rowViewCache = rvc;
1445         }
1446 
SetDataViewManager(DataViewManager dataViewManager)1447         internal void SetDataViewManager(DataViewManager dataViewManager)
1448         {
1449             if (_table == null)
1450                 throw ExceptionBuilder.CanNotUse();
1451 
1452             if (_dataViewManager != dataViewManager)
1453             {
1454                 if (dataViewManager != null)
1455                 {
1456                     dataViewManager._nViews--;
1457                 }
1458 
1459                 _dataViewManager = dataViewManager;
1460                 if (dataViewManager != null)
1461                 {
1462                     dataViewManager._nViews++;
1463                     DataViewSetting dataViewSetting = dataViewManager.DataViewSettings[_table];
1464                     try
1465                     {
1466                         // sdub: check that we will not do unnesasary operation here if dataViewSetting.Sort == this.Sort ...
1467                         _applyDefaultSort = dataViewSetting.ApplyDefaultSort;
1468                         DataExpression newFilter = new DataExpression(_table, dataViewSetting.RowFilter);
1469                         SetIndex(dataViewSetting.Sort, dataViewSetting.RowStateFilter, newFilter);
1470                     }
1471                     catch (Exception e) when (Common.ADP.IsCatchableExceptionType(e))
1472                     {
1473                         ExceptionBuilder.TraceExceptionWithoutRethrow(e); // ignore the exception
1474                     }
1475                     _locked = true;
1476                 }
1477                 else
1478                 {
1479                     SetIndex("", DataViewRowState.CurrentRows, null);
1480                 }
1481             }
1482         }
1483 
SetIndex(string newSort, DataViewRowState newRowStates, IFilter newRowFilter)1484         internal virtual void SetIndex(string newSort, DataViewRowState newRowStates, IFilter newRowFilter)
1485         {
1486             SetIndex2(newSort, newRowStates, newRowFilter, true);
1487         }
1488 
SetIndex2(string newSort, DataViewRowState newRowStates, IFilter newRowFilter, bool fireEvent)1489         internal void SetIndex2(string newSort, DataViewRowState newRowStates, IFilter newRowFilter, bool fireEvent)
1490         {
1491             DataCommonEventSource.Log.Trace("<ds.DataView.SetIndex|INFO> {0}, newSort='{1}', newRowStates={2}", ObjectID, newSort, newRowStates);
1492             _sort = newSort;
1493             _recordStates = newRowStates;
1494             _rowFilter = newRowFilter;
1495 
1496             Debug.Assert((0 == (DataViewRowState.ModifiedCurrent & newRowStates)) ||
1497                          (0 == (DataViewRowState.ModifiedOriginal & newRowStates)),
1498                          "asking DataViewRowState for both Original & Current records");
1499 
1500             if (_fEndInitInProgress)
1501             {
1502                 return;
1503             }
1504 
1505             if (fireEvent)
1506             {
1507                 // old code path for virtual UpdateIndex
1508                 UpdateIndex(true);
1509             }
1510             else
1511             {
1512                 // new code path for RelatedView
1513                 Debug.Assert(null == _comparison, "RelatedView should not have a comparison function");
1514                 UpdateIndex(true, false);
1515             }
1516 
1517             if (null != _findIndexes)
1518             {
1519                 Dictionary<string, Index> indexes = _findIndexes;
1520                 _findIndexes = null;
1521 
1522                 foreach (KeyValuePair<string, Index> entry in indexes)
1523                 {
1524                     entry.Value.RemoveRef();
1525                 }
1526             }
1527         }
1528 
UpdateIndex()1529         protected void UpdateIndex() => UpdateIndex(false);
1530 
UpdateIndex(bool force)1531         protected virtual void UpdateIndex(bool force) => UpdateIndex(force, true);
1532 
UpdateIndex(bool force, bool fireEvent)1533         internal void UpdateIndex(bool force, bool fireEvent)
1534         {
1535             long logScopeId = DataCommonEventSource.Log.EnterScope("<ds.DataView.UpdateIndex|INFO> {0}, force={1}", ObjectID, force);
1536             try
1537             {
1538                 if (_open != _shouldOpen || force)
1539                 {
1540                     _open = _shouldOpen;
1541                     Index newIndex = null;
1542                     if (_open)
1543                     {
1544                         if (_table != null)
1545                         {
1546                             if (null != SortComparison)
1547                             {
1548                                 // because an Index with a Comparison<DataRow is not sharable, directly create the index here
1549                                 newIndex = new Index(_table, SortComparison, ((DataViewRowState)_recordStates), GetFilter());
1550 
1551                                 // bump the addref from 0 to 1 to added to table index collection
1552                                 // the bump from 1 to 2 will happen via DataViewListener.RegisterListChangedEvent
1553                                 newIndex.AddRef();
1554                             }
1555                             else
1556                             {
1557                                 newIndex = _table.GetIndex(Sort, ((DataViewRowState)_recordStates), GetFilter());
1558                             }
1559                         }
1560                     }
1561 
1562                     if (_index == newIndex)
1563                     {
1564                         return;
1565                     }
1566 
1567                     DataTable table = _index != null ? _index.Table : newIndex.Table;
1568 
1569                     if (_index != null)
1570                     {
1571                         _dvListener.UnregisterListChangedEvent();
1572                     }
1573 
1574                     _index = newIndex;
1575 
1576                     if (_index != null)
1577                     {
1578                         _dvListener.RegisterListChangedEvent(_index);
1579                     }
1580 
1581                     ResetRowViewCache();
1582 
1583                     if (fireEvent)
1584                     {
1585                         OnListChanged(s_resetEventArgs);
1586                     }
1587                 }
1588             }
1589             finally
1590             {
1591                 DataCommonEventSource.Log.ExitScope(logScopeId);
1592             }
1593         }
1594 
ChildRelationCollectionChanged(object sender, CollectionChangeEventArgs e)1595         internal void ChildRelationCollectionChanged(object sender, CollectionChangeEventArgs e)
1596         {
1597             DataRelationPropertyDescriptor NullProp = null;
1598             OnListChanged(
1599                 e.Action == CollectionChangeAction.Add ? new ListChangedEventArgs(ListChangedType.PropertyDescriptorAdded, new DataRelationPropertyDescriptor((System.Data.DataRelation)e.Element)) :
1600                 e.Action == CollectionChangeAction.Refresh ? new ListChangedEventArgs(ListChangedType.PropertyDescriptorChanged, NullProp) :
1601                 e.Action == CollectionChangeAction.Remove ? new ListChangedEventArgs(ListChangedType.PropertyDescriptorDeleted, new DataRelationPropertyDescriptor((System.Data.DataRelation)e.Element)) :
1602             /*default*/ null
1603             );
1604         }
1605 
ParentRelationCollectionChanged(object sender, CollectionChangeEventArgs e)1606         internal void ParentRelationCollectionChanged(object sender, CollectionChangeEventArgs e)
1607         {
1608             DataRelationPropertyDescriptor NullProp = null;
1609             OnListChanged(
1610                 e.Action == CollectionChangeAction.Add ? new ListChangedEventArgs(ListChangedType.PropertyDescriptorAdded, new DataRelationPropertyDescriptor((System.Data.DataRelation)e.Element)) :
1611                 e.Action == CollectionChangeAction.Refresh ? new ListChangedEventArgs(ListChangedType.PropertyDescriptorChanged, NullProp) :
1612                 e.Action == CollectionChangeAction.Remove ? new ListChangedEventArgs(ListChangedType.PropertyDescriptorDeleted, new DataRelationPropertyDescriptor((System.Data.DataRelation)e.Element)) :
1613             /*default*/ null
1614             );
1615         }
1616 
ColumnCollectionChanged(object sender, CollectionChangeEventArgs e)1617         protected virtual void ColumnCollectionChanged(object sender, CollectionChangeEventArgs e)
1618         {
1619             DataColumnPropertyDescriptor NullProp = null;
1620             OnListChanged(
1621                 e.Action == CollectionChangeAction.Add ? new ListChangedEventArgs(ListChangedType.PropertyDescriptorAdded, new DataColumnPropertyDescriptor((System.Data.DataColumn)e.Element)) :
1622                 e.Action == CollectionChangeAction.Refresh ? new ListChangedEventArgs(ListChangedType.PropertyDescriptorChanged, NullProp) :
1623                 e.Action == CollectionChangeAction.Remove ? new ListChangedEventArgs(ListChangedType.PropertyDescriptorDeleted, new DataColumnPropertyDescriptor((System.Data.DataColumn)e.Element)) :
1624                 /*default*/ null
1625             );
1626         }
1627 
ColumnCollectionChangedInternal(object sender, CollectionChangeEventArgs e)1628         internal void ColumnCollectionChangedInternal(object sender, CollectionChangeEventArgs e) =>
1629             ColumnCollectionChanged(sender, e);
1630 
ToTable()1631         public DataTable ToTable() =>
1632             ToTable(null, false, Array.Empty<string>());
1633 
ToTable(string tableName)1634         public DataTable ToTable(string tableName) =>
1635             ToTable(tableName, false, Array.Empty<string>());
1636 
ToTable(bool distinct, params string[] columnNames)1637         public DataTable ToTable(bool distinct, params string[] columnNames) =>
1638             ToTable(null, distinct, columnNames);
1639 
ToTable(string tableName, bool distinct, params string[] columnNames)1640         public DataTable ToTable(string tableName, bool distinct, params string[] columnNames)
1641         {
1642             DataCommonEventSource.Log.Trace("<ds.DataView.ToTable|API> {0}, TableName='{1}', distinct={2}", ObjectID, tableName, distinct);
1643 
1644             if (columnNames == null)
1645             {
1646                 throw ExceptionBuilder.ArgumentNull(nameof(columnNames));
1647             }
1648 
1649             DataTable dt = new DataTable();
1650             dt.Locale = _table.Locale;
1651             dt.CaseSensitive = _table.CaseSensitive;
1652             dt.TableName = ((null != tableName) ? tableName : _table.TableName);
1653             dt.Namespace = _table.Namespace;
1654             dt.Prefix = _table.Prefix;
1655 
1656             if (columnNames.Length == 0)
1657             {
1658                 columnNames = new string[Table.Columns.Count];
1659                 for (int i = 0; i < columnNames.Length; i++)
1660                 {
1661                     columnNames[i] = Table.Columns[i].ColumnName;
1662                 }
1663             }
1664 
1665             int[] columnIndexes = new int[columnNames.Length];
1666 
1667             List<object[]> rowlist = new List<object[]>();
1668 
1669             for (int i = 0; i < columnNames.Length; i++)
1670             {
1671                 DataColumn dc = Table.Columns[columnNames[i]];
1672                 if (dc == null)
1673                 {
1674                     throw ExceptionBuilder.ColumnNotInTheUnderlyingTable(columnNames[i], Table.TableName);
1675                 }
1676                 dt.Columns.Add(dc.Clone());
1677                 columnIndexes[i] = Table.Columns.IndexOf(dc);
1678             }
1679 
1680             foreach (DataRowView drview in this)
1681             {
1682                 object[] o = new object[columnNames.Length];
1683 
1684                 for (int j = 0; j < columnIndexes.Length; j++)
1685                 {
1686                     o[j] = drview[columnIndexes[j]];
1687                 }
1688                 if (!distinct || !RowExist(rowlist, o))
1689                 {
1690                     dt.Rows.Add(o);
1691                     rowlist.Add(o);
1692                 }
1693             }
1694 
1695             return dt;
1696         }
1697 
RowExist(List<object[]> arraylist, object[] objectArray)1698         private bool RowExist(List<object[]> arraylist, object[] objectArray)
1699         {
1700             for (int i = 0; i < arraylist.Count; i++)
1701             {
1702                 object[] rows = arraylist[i];
1703                 bool retval = true;
1704                 for (int j = 0; j < objectArray.Length; j++)
1705                 {
1706                     retval &= (rows[j].Equals(objectArray[j]));
1707                 }
1708                 if (retval)
1709                 {
1710                     return true;
1711                 }
1712             }
1713             return false;
1714         }
1715 
1716         /// <summary>
1717         /// If <paramref name="view"/> is equivalent to the current view with regards to all properties.
1718         /// <see cref="RowFilter"/> and <see cref="Sort"/> may differ by <see cref="StringComparison.OrdinalIgnoreCase"/>.
1719         /// </summary>
Equals(DataView view)1720         public virtual bool Equals(DataView view)
1721         {
1722             if ((null == view) ||
1723                Table != view.Table ||
1724                Count != view.Count ||
1725                !string.Equals(RowFilter, view.RowFilter, StringComparison.OrdinalIgnoreCase) ||  // case insensitive
1726                !string.Equals(Sort, view.Sort, StringComparison.OrdinalIgnoreCase) ||  // case insensitive
1727                !ReferenceEquals(SortComparison, view.SortComparison) ||
1728                !ReferenceEquals(RowPredicate, view.RowPredicate) ||
1729                RowStateFilter != view.RowStateFilter ||
1730                DataViewManager != view.DataViewManager ||
1731                AllowDelete != view.AllowDelete ||
1732                AllowNew != view.AllowNew ||
1733                AllowEdit != view.AllowEdit)
1734             {
1735                 return false;
1736             }
1737             return true;
1738         }
1739 
1740         internal int ObjectID => _objectID;
1741     }
1742 }
1743