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<DataRow>'/> 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