diff --git a/AyCode.Core/Helpers/AcFastObservableCollection.cs b/AyCode.Core/Helpers/AcFastObservableCollection.cs new file mode 100644 index 0000000..0d5be2c --- /dev/null +++ b/AyCode.Core/Helpers/AcFastObservableCollection.cs @@ -0,0 +1,152 @@ +using System.Collections; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; + +namespace AyCode.Core.Helpers +{ + public interface IAcFastObservableCollection + { + public void AddRange(IEnumerable other); + public void Replace(IEnumerable other); + public void RemoveRange(IEnumerable other); + public void Synchronize(NotifyCollectionChangedEventArgs args); + } + + public interface IAcFastObservableCollection : IAcFastObservableCollection + { + public void Replace(IEnumerable other); + public void Sort(IComparer comparer); + public void SortAndReplace(IEnumerable other, IComparer comparer); + } + + public class AcFastObservableCollection : ObservableCollection, IAcFastObservableCollection + { + private bool _suppressChangedEvent; + + public void Replace(IEnumerable other) + { + _suppressChangedEvent = true; + + Clear(); + AddRange(other); + } + + public void Replace(IEnumerable other) + { + _suppressChangedEvent = true; + + Clear(); + foreach (T item in other) Add(item); + + _suppressChangedEvent = false; + + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + OnPropertyChanged(new PropertyChangedEventArgs(nameof(Count))); + } + + public void AddRange(IEnumerable other) + { + _suppressChangedEvent = true; + + foreach (var item in other) + { + if (item is T tItem) Add(tItem); + } + + _suppressChangedEvent = false; + + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + OnPropertyChanged(new PropertyChangedEventArgs(nameof(Count))); + } + + public void RemoveRange(IEnumerable other) + { + _suppressChangedEvent = true; + + foreach (var item in other) + { + if (item is T tItem) Remove(tItem); + } + + _suppressChangedEvent = false; + + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + OnPropertyChanged(new PropertyChangedEventArgs(nameof(Count))); + } + + public void SortAndReplace(IEnumerable other, IComparer comparer) + { + List values = new(other); + + values.Sort(comparer); + Replace(values); + } + + public void Sort(IComparer comparer) + { + List values = new(this); + + values.Sort(comparer); + Replace(values); + } + + public void Synchronize(NotifyCollectionChangedEventArgs args) + { + switch (args.Action) + { + case NotifyCollectionChangedAction.Add when args.NewItems != null: + AddRange(args.NewItems); + break; + case NotifyCollectionChangedAction.Remove when args.OldItems != null: + RemoveRange(args.OldItems); + break; + case NotifyCollectionChangedAction.Reset: + Clear(); + break; + case NotifyCollectionChangedAction.Replace: + case NotifyCollectionChangedAction.Move: + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + protected override void OnPropertyChanged(PropertyChangedEventArgs e) + { + if (_suppressChangedEvent) + return; + + base.OnPropertyChanged(e); + } + + protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + { + if (_suppressChangedEvent) + return; + + base.OnCollectionChanged(e); + } + + //protected override void ClearItems() + //{ + // base.ClearItems(); + //} + + //protected override void InsertItem(int index, T item) + //{ + // base.InsertItem(index, item); + //} + + //protected override void MoveItem(int oldIndex, int newIndex) + //{ + // base.MoveItem(oldIndex, newIndex); + //} + + //public override event NotifyCollectionChangedEventHandler? CollectionChanged + //{ + // add => base.CollectionChanged += value; + // remove => base.CollectionChanged -= value; + //} + } +} \ No newline at end of file diff --git a/AyCode.Services.Server/SignalRs/AcSignalRDataSource.cs b/AyCode.Services.Server/SignalRs/AcSignalRDataSource.cs index da56bf3..255cc9e 100644 --- a/AyCode.Services.Server/SignalRs/AcSignalRDataSource.cs +++ b/AyCode.Services.Server/SignalRs/AcSignalRDataSource.cs @@ -61,18 +61,18 @@ namespace AyCode.Services.Server.SignalRs public class ChangeTracking /*: IEnumerable>*/where TDataItem : class, IId where TId : struct { - private readonly EqualityComparer _equalityComparer = EqualityComparer.Default; + private readonly EqualityComparer _equalityComparerId = EqualityComparer.Default; private readonly List> _trackingItems = []; //TODO: Dictionary... - J. //protected abstract bool HasIdValue(TDataItem dataItem); //protected abstract int FindIndex(TDataItem newValue); //public abstract bool TryGetTrackingItem(TId id, [NotNullWhen(true)] out TrackingItem? trackingItem); - private bool HasIdValue(TDataItem dataItem) => !_equalityComparer.Equals(dataItem.Id, default);//dataItem.Id.IsNullOrEmpty(); - public int FindIndex(TDataItem newValue) => _trackingItems.FindIndex(x => _equalityComparer.Equals(x.CurrentValue.Id, newValue.Id)); + private bool HasIdValue(TDataItem dataItem) => !_equalityComparerId.Equals(dataItem.Id, default);//dataItem.Id.IsNullOrEmpty(); + public int FindIndex(TDataItem newValue) => _trackingItems.FindIndex(x => _equalityComparerId.Equals(x.CurrentValue.Id, newValue.Id)); public bool TryGetTrackingItem(TId id, [NotNullWhen(true)] out TrackingItem? trackingItem) { - trackingItem = _trackingItems.FirstOrDefault(x => _equalityComparer.Equals(x.CurrentValue.Id, id)); + trackingItem = _trackingItems.FirstOrDefault(x => _equalityComparerId.Equals(x.CurrentValue.Id, id)); return trackingItem != null; } @@ -165,7 +165,7 @@ namespace AyCode.Services.Server.SignalRs where TIList : class, IList { private readonly object _syncRoot = new(); - private readonly EqualityComparer _equalityComparer = EqualityComparer.Default; + private readonly EqualityComparer _equalityComparerId = EqualityComparer.Default; protected TIList InnerList = Activator.CreateInstance();// []; //TODO: Dictionary??? - J. protected readonly ChangeTracking TrackingItems = new(); @@ -184,8 +184,8 @@ namespace AyCode.Services.Server.SignalRs //protected abstract int FindIndexInnerList(TId id); //protected abstract TDataItem? FirstOrDefaultInnerList(TId id); - protected bool HasIdValue(TDataItem dataItem) => !_equalityComparer.Equals(dataItem.Id, default);//dataItem.Id.IsNullOrEmpty(); - protected bool IdEquals(TId id1, TId id2) => _equalityComparer.Equals(id1, id2); + protected bool HasIdValue(TDataItem dataItem) => !_equalityComparerId.Equals(dataItem.Id, default);//dataItem.Id.IsNullOrEmpty(); + protected bool IdEquals(TId id1, TId id2) => _equalityComparerId.Equals(id1, id2); protected int FindIndexInnerList(TId id) => InnerList.FindIndex(x => IdEquals(x.Id, id)); protected TDataItem? FirstOrDefaultInnerList(TId id) => InnerList.FirstOrDefault(x => IdEquals(x.Id, id)); @@ -265,11 +265,20 @@ namespace AyCode.Services.Server.SignalRs protected void AddRange(IEnumerable source, TIList destination) { - if (destination is List dest) dest.AddRange(source); - else + switch (destination) { - foreach (var dataItem in source) - destination.Add(dataItem); + case IAcFastObservableCollection dest: + dest.AddRange(source); + break; + case List dest: + dest.AddRange(source); + break; + default: + { + foreach (var dataItem in source) + destination.Add(dataItem); + break; + } } } @@ -371,7 +380,7 @@ namespace AyCode.Services.Server.SignalRs public void Add(TDataItem newValue) { - if (!HasIdValue(newValue)) throw new ArgumentNullException(nameof(newValue), @"Add->newValue.Id.IsNullOrEmpty()"); + if (!HasIdValue(newValue)) throw new ArgumentNullException(nameof(newValue), @"Add->HasIdValue(newValue) == false"); Monitor.Enter(_syncRoot); @@ -854,7 +863,7 @@ namespace AyCode.Services.Server.SignalRs throw new NullReferenceException($"SaveItemUnsafe; result == null"); } - ProcessSavedResponseItem(x.Result, trackingState); + ProcessSavedResponseItem(x.Result, trackingState, item.Id); return x.Result; }); } @@ -877,7 +886,7 @@ namespace AyCode.Services.Server.SignalRs throw new NullReferenceException($"SaveItemUnsafeAsync; result.Status != SignalResponseStatus.Success || result.ResponseData == null; Status: {SignalResponseStatus.Success}"); } - return ProcessSavedResponseItem(response.ResponseData, trackingState); + return ProcessSavedResponseItem(response.ResponseData, trackingState, item.Id); } finally { @@ -886,14 +895,14 @@ namespace AyCode.Services.Server.SignalRs }); } - private Task ProcessSavedResponseItem(TDataItem? resultItem, TrackingState trackingState) + private Task ProcessSavedResponseItem(TDataItem? resultItem, TrackingState trackingState, TId originalId) { if (resultItem == null) return Task.CompletedTask; - if (TryGetTrackingItem(resultItem.Id, out var trackingItem)) + if (TryGetTrackingItem(originalId, out var trackingItem)) TrackingItems.Remove(trackingItem); - if (TryGetIndex(resultItem.Id, out var index)) + if (TryGetIndex(originalId, out var index)) InnerList[index] = resultItem; var eventArgs = new ItemChangedEventArgs(resultItem, trackingState); diff --git a/AyCode.Services.Server/SignalRs/AcWebSignalRHubBase.cs b/AyCode.Services.Server/SignalRs/AcWebSignalRHubBase.cs index 9162d35..ec04426 100644 --- a/AyCode.Services.Server/SignalRs/AcWebSignalRHubBase.cs +++ b/AyCode.Services.Server/SignalRs/AcWebSignalRHubBase.cs @@ -207,7 +207,7 @@ public abstract class AcWebSignalRHubBase(IConfiguration if (Context.User != null) { userName = Context.User.Identity?.Name; - Guid.TryParse((string?)Context.User.FindFirstValue(ClaimTypes.NameIdentifier), out userId); + Guid.TryParse(Context.User.FindFirstValue(ClaimTypes.NameIdentifier), out userId); } if (AcDomain.IsDeveloperVersion) Logger.WarningConditional($"SignalR.Context; userName: {userName}; userId: {userId}");