1 //------------------------------------------------------------------------------
2 // <copyright file="Selection.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="true" primary="false">Microsoft</owner>
7 //------------------------------------------------------------------------------
9 namespace System.Data {
11 using System.Diagnostics;
12 using System.ComponentModel;
13 using System.Collections.Generic;
14 using System.Threading;
16 internal struct IndexField {
17 public readonly DataColumn Column;
18 public readonly bool IsDescending; // false = Asc; true = Desc what is default value for this?
20 internal IndexField(DataColumn column, bool isDescending) {
21 Debug.Assert(column != null, "null column");
24 IsDescending = isDescending;
27 public static bool operator == (IndexField if1, IndexField if2) {
28 return if1.Column == if2.Column && if1.IsDescending == if2.IsDescending;
31 public static bool operator !=(IndexField if1, IndexField if2) {
35 // must override Equals if == operator is defined
36 public override bool Equals(object obj) {
37 if (obj is IndexField)
38 return this == (IndexField)obj;
43 // must override GetHashCode if Equals is redefined
44 public override int GetHashCode() {
45 return Column.GetHashCode() ^ IsDescending.GetHashCode();
49 internal sealed class Index {
51 private sealed class IndexTree : RBTree<int> {
52 private readonly Index _index;
54 internal IndexTree(Index index) : base(TreeAccessMethod.KEY_SEARCH_AND_INDEX) {
58 protected override int CompareNode (int record1, int record2) {
59 return _index.CompareRecords(record1, record2);
61 protected override int CompareSateliteTreeNode (int record1, int record2) {
62 return _index.CompareDuplicateRecords(record1, record2);
66 // these constants are used to update a DataRow when the record and Row are known, but don't match
67 private const int DoNotReplaceCompareRecord = 0;
68 private const int ReplaceNewRecordForCompare = 1;
69 private const int ReplaceOldRecordForCompare = 2;
71 private readonly DataTable table;
72 internal readonly IndexField[] IndexFields;
74 /// <summary>Allow a user implemented comparision of two DataRow</summary>
75 /// <remarks>User must use correct DataRowVersion in comparison or index corruption will happen</remarks>
76 private readonly System.Comparison<DataRow> _comparison;
78 private readonly DataViewRowState recordStates;
79 private WeakReference rowFilter;
80 private IndexTree records;
81 private int recordCount;
84 private Listeners<DataViewListener> _listeners;
86 private bool suspendEvents;
88 private readonly static object[] zeroObjects = new object[0];
89 private readonly bool isSharable;
90 private readonly bool _hasRemoteAggregate;
92 internal const Int32 MaskBits = unchecked((int)0x7FFFFFFF);
94 private static int _objectTypeCount; // Bid counter
95 private readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
97 public Index(DataTable table, IndexField[] indexFields, DataViewRowState recordStates, IFilter rowFilter)
98 : this(table, indexFields, null, recordStates, rowFilter) { }
100 public Index(DataTable table, System.Comparison<DataRow> comparison, DataViewRowState recordStates, IFilter rowFilter)
101 : this(table, GetAllFields(table.Columns), comparison, recordStates, rowFilter) { }
103 // for the delegate methods, we don't know what the dependent columns are - so all columns are dependent
104 private static IndexField[] GetAllFields(DataColumnCollection columns) {
105 IndexField[] fields = new IndexField[columns.Count];
106 for(int i = 0; i < fields.Length; ++i) {
107 fields[i] = new IndexField(columns[i], false);
112 private Index(DataTable table, IndexField[] indexFields, System.Comparison<DataRow> comparison, DataViewRowState recordStates, IFilter rowFilter) {
113 Bid.Trace("<ds.Index.Index|API> %d#, table=%d, recordStates=%d{ds.DataViewRowState}\n",
114 ObjectID, (table != null) ? table.ObjectID : 0, (int)recordStates);
115 Debug.Assert(indexFields != null);
116 Debug.Assert(null != table, "null table");
118 (~(DataViewRowState.CurrentRows | DataViewRowState.OriginalRows))) != 0) {
119 throw ExceptionBuilder.RecordStateRange();
122 _listeners = new Listeners<DataViewListener>(ObjectID,
123 delegate(DataViewListener listener)
125 return (null != listener);
128 IndexFields = indexFields;
129 this.recordStates = recordStates;
130 _comparison = comparison;
132 DataColumnCollection columns = table.Columns;
133 isSharable = (rowFilter == null) && (comparison == null); // a filter or comparison make an index unsharable
134 if (null != rowFilter) {
135 this.rowFilter = new WeakReference(rowFilter);
136 DataExpression expr = (rowFilter as DataExpression);
138 _hasRemoteAggregate = expr.HasRemoteAggregate();
141 InitRecords(rowFilter);
143 // do not AddRef in ctor, every caller should be responsible to AddRef it
144 // if caller does not AddRef, it is expected to be a one-time read operation because the index won't be maintained on writes
147 public bool Equal(IndexField[] indexDesc, DataViewRowState recordStates, IFilter rowFilter) {
150 IndexFields.Length != indexDesc.Length ||
151 this.recordStates != recordStates ||
157 for (int loop = 0; loop < IndexFields.Length; loop++) {
158 if (IndexFields[loop].Column!= indexDesc[loop].Column ||
159 IndexFields[loop].IsDescending != indexDesc[loop].IsDescending) {
167 internal bool HasRemoteAggregate {
169 return _hasRemoteAggregate;
173 internal int ObjectID {
179 public DataViewRowState RecordStates {
180 get { return recordStates; }
183 public IFilter RowFilter {
184 get { return (IFilter)((null != rowFilter) ? rowFilter.Target : null); }
187 public int GetRecord(int recordIndex) {
188 Debug.Assert (recordIndex >= 0 && recordIndex < recordCount, "recordIndex out of range");
189 return records[recordIndex];
192 public bool HasDuplicates {
194 return records.HasDuplicates;
198 public int RecordCount {
204 public bool IsSharable {
211 private bool AcceptRecord(int record) {
212 return AcceptRecord(record, RowFilter);
215 private bool AcceptRecord(int record, IFilter filter) {
216 Bid.Trace("<ds.Index.AcceptRecord|API> %d#, record=%d\n", ObjectID, record);
220 DataRow row = table.recordManager[record];
227 DataRowVersion version = DataRowVersion.Default;
228 if (row.oldRecord == record) {
229 version = DataRowVersion.Original;
231 else if (row.newRecord == record) {
232 version = DataRowVersion.Current;
234 else if (row.tempRecord == record) {
235 version = DataRowVersion.Proposed;
238 return filter.Invoke(row, version);
241 /// <remarks>Only call from inside a lock(this)</remarks>
242 internal void ListChangedAdd(DataViewListener listener) {
243 _listeners.Add(listener);
246 /// <remarks>Only call from inside a lock(this)</remarks>
247 internal void ListChangedRemove(DataViewListener listener) {
248 _listeners.Remove(listener);
251 public int RefCount {
257 public void AddRef() {
258 Bid.Trace("<ds.Index.AddRef|API> %d#\n", ObjectID);
259 LockCookie lc = table.indexesLock.UpgradeToWriterLock(-1);
261 Debug.Assert(0 <= refCount, "AddRef on disposed index");
262 Debug.Assert(null != records, "null records");
264 table.ShadowIndexCopy();
265 table.indexes.Add(this);
270 table.indexesLock.DowngradeFromWriterLock(ref lc);
274 public int RemoveRef() {
275 Bid.Trace("<ds.Index.RemoveRef|API> %d#\n", ObjectID);
277 LockCookie lc = table.indexesLock.UpgradeToWriterLock(-1);
281 table.ShadowIndexCopy();
282 table.indexes.Remove(this);
286 table.indexesLock.DowngradeFromWriterLock(ref lc);
291 private void ApplyChangeAction(int record, int action, int changeRecord) {
294 if (AcceptRecord(record)) {
295 InsertRecord(record, true);
298 else if ((null != _comparison) && (-1 != record)) {
299 // when removing a record, the DataRow has already been updated to the newer record
300 // depending on changeRecord, either the new or old record needs be backdated to record
301 // for Comparison<DataRow> to operate correctly
302 DeleteRecord(GetIndex(record, changeRecord));
305 // unnecessary codepath other than keeping original code path for redbits
306 DeleteRecord(GetIndex(record));
311 public bool CheckUnique() {
313 Debug.Assert(records.CheckUnique(records.root) != HasDuplicates, "CheckUnique difference");
315 return !HasDuplicates;
318 // only used for main tree compare, not satalite tree
319 private int CompareRecords(int record1, int record2) {
320 if (null != _comparison) {
321 return CompareDataRows(record1, record2);
323 if (0 < IndexFields.Length) {
324 for (int i = 0; i < IndexFields.Length; i++) {
325 int c = IndexFields[i].Column.Compare(record1, record2);
327 return (IndexFields[i].IsDescending ? -c : c);
333 Debug.Assert(null != table.recordManager[record1], "record1 no datarow");
334 Debug.Assert(null != table.recordManager[record2], "record2 no datarow");
336 // DataRow needs to always updated appropriately via GetIndex(int,int)
337 //table.recordManager.VerifyRecord(record1, table.recordManager[record1]);
338 //table.recordManager.VerifyRecord(record2, table.recordManager[record2]);
340 // Need to use compare because subtraction will wrap
341 // to positive for very large neg numbers, etc.
342 return table.Rows.IndexOf(table.recordManager[record1]).CompareTo(table.Rows.IndexOf(table.recordManager[record2]));
346 private int CompareDataRows(int record1, int record2)
348 table.recordManager.VerifyRecord(record1, table.recordManager[record1]);
349 table.recordManager.VerifyRecord(record2, table.recordManager[record2]);
350 return _comparison(table.recordManager[record1], table.recordManager[record2]);
354 // PS: same as previous CompareRecords, except it compares row state if needed
355 // only used for satalite tree compare
356 private int CompareDuplicateRecords(int record1, int record2) {
358 if (null != _comparison) {
359 Debug.Assert(0 == CompareDataRows(record1, record2), "duplicate record not a duplicate by user function");
361 else if (record1 != record2) {
362 for (int i = 0; i < IndexFields.Length; i++) {
363 int c = IndexFields[i].Column.Compare(record1, record2);
364 Debug.Assert(0 == c, "duplicate record not a duplicate");
368 Debug.Assert(null != table.recordManager[record1], "record1 no datarow");
369 Debug.Assert(null != table.recordManager[record2], "record2 no datarow");
371 // DataRow needs to always updated appropriately via GetIndex(int,int)
372 //table.recordManager.VerifyRecord(record1, table.recordManager[record1]);
373 //table.recordManager.VerifyRecord(record2, table.recordManager[record2]);
375 if (null == table.recordManager[record1]) {
376 return ((null == table.recordManager[record2]) ? 0 : -1);
378 else if (null == table.recordManager[record2]) {
382 // Need to use compare because subtraction will wrap
383 // to positive for very large neg numbers, etc.
384 int diff = table.recordManager[record1].rowID.CompareTo(table.recordManager[record2].rowID);
386 // if they're two records in the same row, we need to be able to distinguish them.
387 if ((diff == 0) && (record1 != record2)) {
388 diff = ((int)table.recordManager[record1].GetRecordState(record1)).CompareTo((int)table.recordManager[record2].GetRecordState(record2));
393 private int CompareRecordToKey(int record1, object[] vals) {
394 for (int i = 0; i < IndexFields.Length; i++) {
395 int c = IndexFields[i].Column.CompareValueTo(record1, vals[i]);
397 return (IndexFields[i].IsDescending ? -c : c);
403 // DeleteRecordFromIndex deletes the given record from index and does not fire any Event. IT SHOULD NOT FIRE EVENT
404 // I added this since I can not use existing DeleteRecord which is not silent operation
405 public void DeleteRecordFromIndex(int recordIndex) { // this is for expression use, to maintain expression columns's sort , filter etc. do not fire event
406 DeleteRecord(recordIndex, false);
408 // old and existing DeleteRecord behavior, we can not use this for silently deleting
409 private void DeleteRecord(int recordIndex) {
410 DeleteRecord(recordIndex, true);
412 private void DeleteRecord(int recordIndex, bool fireEvent) {
413 Bid.Trace("<ds.Index.DeleteRecord|INFO> %d#, recordIndex=%d, fireEvent=%d{bool}\n", ObjectID, recordIndex, fireEvent);
415 if (recordIndex >= 0) {
417 int record = records.DeleteByIndex(recordIndex);
419 MaintainDataView(ListChangedType.ItemDeleted, record, !fireEvent);
422 // 1) Webdata 104939 do not fix this, it would be breaking change
423 // 2) newRecord = -1, oldrecord = recordIndex;
424 OnListChanged(ListChangedType.ItemDeleted, recordIndex);
429 // SQLBU 428961: Serious performance issue when creating DataView
430 // this improves performance by allowing DataView to iterating instead of computing for records over index
431 // this will also allow Linq over DataSet to enumerate over the index
432 // avoid boxing by returning RBTreeEnumerator (a struct) instead of IEnumerator<int>
433 public RBTree<int>.RBTreeEnumerator GetEnumerator(int startIndex) {
434 return new IndexTree.RBTreeEnumerator(records, startIndex);
437 // What it actually does is find the index in the records[] that
438 // this record inhabits, and if it doesn't, suggests what index it would
439 // inhabit while setting the high bit.
442 public int GetIndex(int record) {
443 int index = records.GetIndexByKey(record);
448 /// When searching by value for a specific record, the DataRow may require backdating to reflect the appropriate state
449 /// otherwise on Delete of a DataRow in the Added state, would result in the <see cref="System.Comparison<DataRow>"/> where the row
450 /// reflection record would be in the Detatched instead of Added state.
452 private int GetIndex(int record, int changeRecord) {
453 Debug.Assert(null != _comparison, "missing comparison");
456 DataRow row = table.recordManager[record];
458 int a = row.newRecord;
459 int b = row.oldRecord;
461 switch(changeRecord) {
462 case ReplaceNewRecordForCompare:
463 row.newRecord = record;
465 case ReplaceOldRecordForCompare:
466 row.oldRecord = record;
469 table.recordManager.VerifyRecord(record, row);
471 index = records.GetIndexByKey(record);
474 switch(changeRecord) {
475 case ReplaceNewRecordForCompare:
476 Debug.Assert(record == row.newRecord, "newRecord has change during GetIndex");
479 case ReplaceOldRecordForCompare:
480 Debug.Assert(record == row.oldRecord, "oldRecord has change during GetIndex");
486 table.recordManager.VerifyRecord(a, row);
493 public object[] GetUniqueKeyValues() {
494 if (IndexFields == null || IndexFields.Length == 0) {
497 List<object[]> list = new List<object[]>();
498 GetUniqueKeyValues(list, records.root);
499 return list.ToArray();
503 /// Find index of maintree node that matches key in record
505 public int FindRecord(int record) {
506 int nodeId = records.Search(record);
507 if (nodeId!=IndexTree.NIL)
508 return records.GetIndexByNode(nodeId); //always returns the First record index
513 public int FindRecordByKey(object key) {
514 int nodeId = FindNodeByKey(key);
515 if (IndexTree.NIL != nodeId) {
516 return records.GetIndexByNode(nodeId);
518 return -1; // return -1 to user indicating record not found
521 public int FindRecordByKey(object[] key) {
522 int nodeId = FindNodeByKeys(key);
523 if (IndexTree.NIL != nodeId) {
524 return records.GetIndexByNode(nodeId);
526 return -1; // return -1 to user indicating record not found
529 private int FindNodeByKey(object originalKey) {
531 if (IndexFields.Length != 1) {
532 throw ExceptionBuilder.IndexKeyLength(IndexFields.Length, 1);
536 if (IndexTree.NIL != x) { // otherwise storage may not exist
537 DataColumn column = IndexFields[0].Column;
538 object key = column.ConvertValue(originalKey);
541 if (IndexFields[0].IsDescending) {
542 while (IndexTree.NIL != x) {
543 c = column.CompareValueTo(records.Key(x), key);
544 if (c == 0) { break; }
545 if (c < 0) { x = records.Left(x); } // < for decsending
546 else { x = records.Right(x); }
550 while (IndexTree.NIL != x) {
551 c = column.CompareValueTo(records.Key(x), key);
552 if (c == 0) { break; }
553 if (c > 0) { x = records.Left(x); } // > for ascending
554 else { x = records.Right(x); }
561 private int FindNodeByKeys(object[] originalKey) {
563 c = ((null != originalKey) ? originalKey.Length : 0);
564 if ((0 == c) || (IndexFields.Length != c)) {
565 throw ExceptionBuilder.IndexKeyLength(IndexFields.Length, c);
569 if (IndexTree.NIL != x) { // otherwise storage may not exist
570 // copy array to avoid changing original
571 object[] key = new object[originalKey.Length];
572 for(int i = 0; i < originalKey.Length; ++i) {
573 key[i] = IndexFields[i].Column.ConvertValue(originalKey[i]);
577 while (IndexTree.NIL != x) {
578 c = CompareRecordToKey(records.Key(x), key);
579 if (c == 0) { break; }
580 if (c > 0) { x = records.Left(x); }
581 else { x = records.Right(x); }
587 private int FindNodeByKeyRecord(int record) {
590 if (IndexTree.NIL != x) { // otherwise storage may not exist
592 while (IndexTree.NIL != x) {
593 c = CompareRecords(records.Key(x), record);
594 if (c == 0) { break; }
595 if (c > 0) { x = records.Left(x); }
596 else { x = records.Right(x); }
603 internal delegate int ComparisonBySelector<TKey,TRow>(TKey key, TRow row) where TRow:DataRow;
605 /// <summary>This method exists for LinqDataView to keep a level of abstraction away from the RBTree</summary>
606 internal Range FindRecords<TKey,TRow>(ComparisonBySelector<TKey,TRow> comparison, TKey key) where TRow:DataRow
608 int x = records.root;
609 while (IndexTree.NIL != x)
611 int c = comparison(key, (TRow)table.recordManager[records.Key(x)]);
612 if (c == 0) { break; }
613 if (c < 0) { x = records.Left(x); }
614 else { x = records.Right(x); }
616 return GetRangeFromNode(x);
619 private Range GetRangeFromNode(int nodeId)
621 // fill range with the min and max indexes of matching record (i.e min and max of satelite tree)
622 // min index is the index of the node in main tree, and max is the min + size of satelite tree-1
624 if (IndexTree.NIL == nodeId) {
627 int recordIndex = records.GetIndexByNode(nodeId);
629 if (records.Next (nodeId) == IndexTree.NIL)
630 return new Range (recordIndex, recordIndex);
632 int span = records.SubTreeSize(records.Next(nodeId));
633 return new Range (recordIndex, recordIndex + span - 1);
636 public Range FindRecords(object key) {
637 int nodeId = FindNodeByKey (key); // main tree node associated with key
638 return GetRangeFromNode(nodeId);
641 public Range FindRecords(object[] key) {
642 int nodeId = FindNodeByKeys (key); // main tree node associated with key
643 return GetRangeFromNode(nodeId);
646 internal void FireResetEvent() {
647 Bid.Trace("<ds.Index.FireResetEvent|API> %d#\n", ObjectID);
649 OnListChanged(DataView.ResetEventArgs);
653 private int GetChangeAction(DataViewRowState oldState, DataViewRowState newState) {
654 int oldIncluded = ((int)recordStates & (int)oldState) == 0? 0: 1;
655 int newIncluded = ((int)recordStates & (int)newState) == 0? 0: 1;
656 return newIncluded - oldIncluded;
659 /// <summary>Determine if the record that needs backdating is the newRecord or oldRecord or neither</summary>
660 private static int GetReplaceAction(DataViewRowState oldState)
662 return ((0 != (DataViewRowState.CurrentRows & oldState)) ? ReplaceNewRecordForCompare : // Added/ModifiedCurrent/Unchanged
663 ((0 != (DataViewRowState.OriginalRows & oldState)) ? ReplaceOldRecordForCompare : // Deleted/ModififedOriginal
664 DoNotReplaceCompareRecord)); // None
667 public DataRow GetRow(int i) {
668 return table.recordManager[GetRecord(i)];
671 public DataRow[] GetRows(Object[] values) {
672 return GetRows(FindRecords(values));
675 public DataRow[] GetRows(Range range) {
676 DataRow[] newRows = table.NewRowArray(range.Count);
677 if (0 < newRows.Length) {
678 RBTree<int>.RBTreeEnumerator iterator = GetEnumerator(range.Min);
679 for (int i = 0; i < newRows.Length && iterator.MoveNext(); i++) {
680 newRows[i] = table.recordManager[iterator.Current];
686 private void InitRecords(IFilter filter) {
687 DataViewRowState states = recordStates;
689 // SQLBU 428961: Serious performance issue when creating DataView
690 // this improves performance when the is no filter, like with the default view (creating after rows added)
691 // we know the records are in the correct order, just append to end, duplicates not possible
692 bool append = (0 == IndexFields.Length);
694 records = new IndexTree(this);
698 // SQLBU 428961: Serious performance issue when creating DataView
699 // this improves performance by iterating of the index instead of computing record by index
700 foreach(DataRow b in table.Rows)
703 if (b.oldRecord == b.newRecord) {
704 if ((int)(states & DataViewRowState.Unchanged) != 0) {
705 record = b.oldRecord;
708 else if (b.oldRecord == -1) {
709 if ((int)(states & DataViewRowState.Added) != 0) {
710 record = b.newRecord;
713 else if (b.newRecord == -1) {
714 if ((int)(states & DataViewRowState.Deleted) != 0) {
715 record = b.oldRecord;
719 if ((int)(states & DataViewRowState.ModifiedCurrent) != 0) {
720 record = b.newRecord;
722 else if ((int)(states & DataViewRowState.ModifiedOriginal) != 0) {
723 record = b.oldRecord;
726 if (record != -1 && AcceptRecord(record, filter))
728 records.InsertAt(-1, record, append);
735 // InsertRecordToIndex inserts the given record to index and does not fire any Event. IT SHOULD NOT FIRE EVENT
736 // I added this since I can not use existing InsertRecord which is not silent operation
737 // it returns the position that record is inserted
738 public int InsertRecordToIndex(int record) {
740 if (AcceptRecord(record)) {
741 pos = InsertRecord(record, false);
746 // existing functionality, it calls the overlaod with fireEvent== true, so it still fires the event
747 private int InsertRecord(int record, bool fireEvent) {
748 Bid.Trace("<ds.Index.InsertRecord|INFO> %d#, record=%d, fireEvent=%d{bool}\n", ObjectID, record, fireEvent);
750 // SQLBU 428961: Serious performance issue when creating DataView
751 // this improves performance when the is no filter, like with the default view (creating before rows added)
752 // we know can append when the new record is the last row in table, normal insertion pattern
754 if ((0 == IndexFields.Length) && (null != table))
756 DataRow row = table.recordManager[record];
757 append = (table.Rows.IndexOf(row)+1 == table.Rows.Count);
759 int nodeId = records.InsertAt(-1, record, append);
763 MaintainDataView(ListChangedType.ItemAdded, record, !fireEvent);
767 OnListChanged(ListChangedType.ItemAdded, records.GetIndexByNode(nodeId));
772 return records.GetIndexByNode(nodeId);
777 // Search for specified key
778 public bool IsKeyInIndex(object key) {
779 int x_id = FindNodeByKey(key);
780 return (IndexTree.NIL != x_id);
783 public bool IsKeyInIndex(object[] key) {
784 int x_id = FindNodeByKeys(key);
785 return (IndexTree.NIL != x_id);
788 public bool IsKeyRecordInIndex(int record) {
789 int x_id = FindNodeByKeyRecord(record);
790 return (IndexTree.NIL != x_id);
793 private bool DoListChanged {
794 get { return (!suspendEvents && _listeners.HasListeners && !table.AreIndexEventsSuspended); }
797 private void OnListChanged(ListChangedType changedType, int newIndex, int oldIndex) {
799 OnListChanged(new ListChangedEventArgs(changedType, newIndex, oldIndex));
803 private void OnListChanged(ListChangedType changedType, int index) {
805 OnListChanged(new ListChangedEventArgs(changedType, index));
809 private void OnListChanged(ListChangedEventArgs e) {
810 Bid.Trace("<ds.Index.OnListChanged|INFO> %d#\n", ObjectID);
811 Debug.Assert(DoListChanged, "supposed to check DoListChanged before calling to delay create ListChangedEventArgs");
813 _listeners.Notify(e, false, false,
814 delegate(DataViewListener listener, ListChangedEventArgs args, bool arg2, bool arg3)
816 listener.IndexListChanged(args);
820 private void MaintainDataView(ListChangedType changedType, int record, bool trackAddRemove) {
821 Debug.Assert(-1 <= record, "bad record#");
823 _listeners.Notify(changedType, ((0 <= record) ? table.recordManager[record] : null), trackAddRemove,
824 delegate(DataViewListener listener, ListChangedType type, DataRow row, bool track)
826 listener.MaintainDataView(changedType, row, track);
830 public void Reset() {
831 Bid.Trace("<ds.Index.Reset|API> %d#\n", ObjectID);
832 InitRecords(RowFilter);
833 MaintainDataView(ListChangedType.Reset, -1, false); // SQLBU 360388
837 public void RecordChanged(int record) {
838 Bid.Trace("<ds.Index.RecordChanged|API> %d#, record=%d\n", ObjectID, record);
840 int index = GetIndex(record);
842 OnListChanged(ListChangedType.ItemChanged, index);
846 // new RecordChanged which takes oldIndex and newIndex and fires onListChanged
847 public void RecordChanged(int oldIndex, int newIndex) {
848 Bid.Trace("<ds.Index.RecordChanged|API> %d#, oldIndex=%d, newIndex=%d\n", ObjectID, oldIndex, newIndex);
850 if (oldIndex > -1 || newIndex > -1) { // no need to fire if it was not and will not be in index: this check means at least one version should be in index
851 if (oldIndex == newIndex ) {
852 OnListChanged(ListChangedType.ItemChanged, newIndex, oldIndex);
854 else if (oldIndex == -1) { // it is added
855 OnListChanged(ListChangedType.ItemAdded, newIndex, oldIndex);
857 else if (newIndex == -1) { // its deleted
858 // Do not fix this. see bug Bug 271076 for explanation.
859 OnListChanged(ListChangedType.ItemDeleted, oldIndex);
862 OnListChanged(ListChangedType.ItemMoved, newIndex, oldIndex);
867 public void RecordStateChanged(int record, DataViewRowState oldState, DataViewRowState newState) {
868 Bid.Trace("<ds.Index.RecordStateChanged|API> %d#, record=%d, oldState=%d{ds.DataViewRowState}, newState=%d{ds.DataViewRowState}\n", ObjectID, record, (int)oldState, (int)newState);
870 int action = GetChangeAction(oldState, newState);
871 ApplyChangeAction(record, action, GetReplaceAction(oldState));
874 public void RecordStateChanged(int oldRecord, DataViewRowState oldOldState, DataViewRowState oldNewState,
875 int newRecord, DataViewRowState newOldState, DataViewRowState newNewState) {
877 Bid.Trace("<ds.Index.RecordStateChanged|API> %d#, oldRecord=%d, oldOldState=%d{ds.DataViewRowState}, oldNewState=%d{ds.DataViewRowState}, newRecord=%d, newOldState=%d{ds.DataViewRowState}, newNewState=%d{ds.DataViewRowState}\n", ObjectID,oldRecord, (int)oldOldState, (int)oldNewState, newRecord, (int)newOldState, (int)newNewState);
879 Debug.Assert((-1 == oldRecord) || (-1 == newRecord) ||
880 table.recordManager[oldRecord] == table.recordManager[newRecord],
881 "not the same DataRow when updating oldRecord and newRecord");
883 int oldAction = GetChangeAction(oldOldState, oldNewState);
884 int newAction = GetChangeAction(newOldState, newNewState);
885 if (oldAction == -1 && newAction == 1 && AcceptRecord(newRecord)) {
888 if ((null != _comparison) && oldAction < 0)
889 { // when oldRecord is being removed, allow GetIndexByKey updating the DataRow for Comparison<DataRow>
890 oldRecordIndex = GetIndex (oldRecord, GetReplaceAction(oldOldState));
893 oldRecordIndex = GetIndex (oldRecord);
896 if ((null == _comparison) && oldRecordIndex != -1 && CompareRecords(oldRecord, newRecord)==0) {
897 records.UpdateNodeKey(oldRecord, newRecord); //change in place, as Both records have same key value
899 int commonIndexLocation = GetIndex(newRecord);
900 OnListChanged(ListChangedType.ItemChanged, commonIndexLocation, commonIndexLocation);
903 suspendEvents = true;
904 if (oldRecordIndex != -1) {
905 records.DeleteByIndex(oldRecordIndex); // DeleteByIndex doesn't require searching by key
909 records.Insert(newRecord);
912 suspendEvents = false;
914 int newRecordIndex = GetIndex (newRecord);
915 if(oldRecordIndex == newRecordIndex) { // if the position is the same
916 OnListChanged (ListChangedType.ItemChanged, newRecordIndex, oldRecordIndex); // be carefull remove oldrecord index if needed
919 if (oldRecordIndex == -1) {
920 MaintainDataView(ListChangedType.ItemAdded, newRecord, false);
921 OnListChanged(ListChangedType.ItemAdded, GetIndex(newRecord)); // oldLocation would be -1
924 OnListChanged (ListChangedType.ItemMoved, newRecordIndex, oldRecordIndex);
930 ApplyChangeAction(oldRecord, oldAction, GetReplaceAction(oldOldState));
931 ApplyChangeAction(newRecord, newAction, GetReplaceAction(newOldState));
935 internal DataTable Table {
941 private void GetUniqueKeyValues(List<object[]> list, int curNodeId) {
942 if (curNodeId != IndexTree.NIL) {
943 GetUniqueKeyValues(list, records.Left(curNodeId));
945 int record = records.Key(curNodeId);
946 object[] element = new object[IndexFields.Length]; // number of columns in PK
947 for (int j = 0; j < element.Length; ++j) {
948 element[j] = IndexFields[j].Column[record];
952 GetUniqueKeyValues(list, records.Right(curNodeId));
956 internal static int IndexOfReference<T>(List<T> list, T item) where T : class {
958 for (int i = 0; i < list.Count; ++i) {
959 if (Object.ReferenceEquals(list[i], item)) {
966 internal static bool ContainsReference<T>(List<T> list, T item) where T : class {
967 return (0 <= IndexOfReference(list, item));
971 internal sealed class Listeners<TElem> where TElem : class
973 private readonly List<TElem> listeners;
974 private readonly Func<TElem, bool> filter;
975 private readonly int ObjectID;
976 private int _listenerReaderCount;
978 /// <summary>Wish this was defined in mscorlib.dll instead of System.Core.dll</summary>
979 internal delegate void Action<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
981 /// <summary>Wish this was defined in mscorlib.dll instead of System.Core.dll</summary>
982 internal delegate TResult Func<T1, TResult>(T1 arg1);
984 internal Listeners(int ObjectID, Func<TElem, bool> notifyFilter) {
985 listeners = new List<TElem>();
986 filter = notifyFilter;
987 this.ObjectID = ObjectID;
988 _listenerReaderCount = 0;
991 internal bool HasListeners {
992 get { return (0 < listeners.Count); }
996 /// <remarks>Only call from inside a lock</remarks>
997 internal void Add(TElem listener) {
998 Debug.Assert(null != listener, "null listener");
999 Debug.Assert(!Index.ContainsReference(listeners, listener), "already contains reference");
1000 listeners.Add(listener);
1003 internal int IndexOfReference(TElem listener) {
1004 return Index.IndexOfReference(listeners, listener);
1007 /// <remarks>Only call from inside a lock</remarks>
1008 internal void Remove(TElem listener) {
1009 Debug.Assert(null != listener, "null listener");
1011 int index = IndexOfReference(listener);
1012 Debug.Assert(0 <= index, "listeners don't contain listener");
1013 listeners[index] = null;
1015 if (0 == _listenerReaderCount) {
1016 listeners.RemoveAt(index);
1017 listeners.TrimExcess();
1022 /// Write operation which means user must control multi-thread and we can assume single thread
1024 internal void Notify<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3, Action<TElem, T1, T2, T3> action) {
1025 Debug.Assert(null != action, "no action");
1026 Debug.Assert(0 <= _listenerReaderCount, "negative _listEventCount");
1028 int count = listeners.Count;
1032 // protect against listeners shrinking via Remove
1033 _listenerReaderCount++;
1035 // protect against listeners growing via Add since new listeners will already have the Notify in progress
1036 for (int i = 0; i < count; ++i) {
1037 // protect against listener being set to null (instead of being removed)
1038 TElem listener = listeners[i];
1039 if (filter(listener)) {
1040 // perform the action on each listener
1041 // some actions may throw an exception blocking remaning listeners from being notified (just like events)
1042 action(listener, arg1, arg2, arg3);
1045 listeners[i] = null;
1051 _listenerReaderCount--;
1053 if (0 == _listenerReaderCount) {
1054 RemoveNullListeners(nullIndex);
1059 private void RemoveNullListeners(int nullIndex) {
1060 Debug.Assert((-1 == nullIndex) || (null == listeners[nullIndex]), "non-null listener");
1061 Debug.Assert(0 == _listenerReaderCount, "0 < _listenerReaderCount");
1062 for (int i = nullIndex; 0 <= i; --i) {
1063 if (null == listeners[i]) {
1064 listeners.RemoveAt(i);