diff --git a/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs b/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs index 545fddc3..f36fb4b7 100644 --- a/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs +++ b/TIAMSharedUI/Shared/Components/Grids/TiamGrid.cs @@ -1,46 +1,19 @@ using System.ComponentModel; -using System.Data.Common; using AyCode.Core; using AyCode.Core.Enums; using AyCode.Core.Extensions; using AyCode.Core.Helpers; using AyCode.Core.Interfaces; -using AyCode.Interfaces.Entities; using AyCode.Services.SignalRs; using AyCode.Utils.Extensions; using DevExpress.Blazor; -using DevExpress.Blazor.Internal; using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Web; using TIAM.Services; using TIAMWebApp.Shared.Application.Services; using TIAMWebApp.Shared.Application.Utility; namespace TIAMSharedUI.Shared.Components.Grids { - public class GridDataItemChangingEventArgs : GridDataItemChangedEventArgs where TDataItem : class, IId - { - internal GridDataItemChangingEventArgs(TiamGrid grid, TDataItem dataItem, TrackingState trackingState) : base(grid, dataItem, trackingState) - { - } - - public bool IsCanceled { get; set; } - } - - public class GridDataItemChangedEventArgs where TDataItem : class, IId - { - internal GridDataItemChangedEventArgs(TiamGrid grid, TDataItem dataItem, TrackingState trackingState) - { - Grid = grid; - DataItem = dataItem; - TrackingState = trackingState; - } - - public TiamGrid Grid { get; } - public TDataItem DataItem { get; } - public TrackingState TrackingState { get; } - } - public class TiamGrid : DxGrid where TDataItem : class, IId { protected bool IsFirstInitializeParameters; @@ -71,10 +44,12 @@ namespace TIAMSharedUI.Shared.Components.Grids [Parameter] public EventCallback> OnDataSourceChanged { get; set; } [Parameter] public EventCallback> OnGridItemChanging { get; set; } + /// - /// After server response! + /// After the server has responded! /// - [Parameter] public EventCallback> OnGridItemChanged { get; set; } + [Parameter] + public EventCallback> OnGridItemChanged { get; set; } [Parameter] [DefaultValue(null)] @@ -91,27 +66,7 @@ namespace TIAMSharedUI.Shared.Components.Grids return _dataSource!; } - set - { - if (value == null) return; - _dataSourceParam = value; - - //bool equals; - - //if ((equals = Equals(_dataSource, value)) == false) - //{ - // if (value is SignalRDataSource dataSource) - // _dataSource = dataSource; - // else - // { - // var crudTags = new SignalRCrudTags(GetAllMessageTag, SignalRTags.None, AddMessageTag, UpdateMessageTag, RemoveMessageTag); - // _dataSource = new SignalRDataSource(value, SignalRClient, crudTags, ContextId); - // } - //} - - //Data = _dataSource; - //if (!equals) OnDataSourceChanged.InvokeAsync(_dataSource); - } + set => _dataSourceParam = value; } protected override async Task OnInitializedAsync() @@ -127,7 +82,7 @@ namespace TIAMSharedUI.Shared.Components.Grids var crudTags = new SignalRCrudTags(GetAllMessageTag, SignalRTags.None, AddMessageTag, UpdateMessageTag, RemoveMessageTag); _dataSource = new SignalRDataSource(SignalRClient, crudTags, ContextId); - + Data = _dataSource; _dataSource.OnDataSourceLoaded += OnDataSourceLoaded; @@ -136,20 +91,22 @@ namespace TIAMSharedUI.Shared.Components.Grids await base.OnInitializedAsync(); } - private Task OnDataSourceItemChanged(ItemChangedEventArgs args) + private async Task OnDataSourceItemChanged(ItemChangedEventArgs args) { - if (args.TrackingState is TrackingState.GetAll or TrackingState.None) return Task.CompletedTask; + if (args.TrackingState is TrackingState.GetAll or TrackingState.None) return; - Logger.Info($"{_gridLogName} OnItemLoaded; trackingState: {args.TrackingState}"); + Logger.Debug($"{_gridLogName} OnDataSourceItemChanged; trackingState: {args.TrackingState}"); var changedEventArgs = new GridDataItemChangedEventArgs(this, args.Item, args.TrackingState); - return OnGridItemChanged.InvokeAsync(changedEventArgs); + await OnGridItemChanged.InvokeAsync(changedEventArgs); + + await InvokeAsync(StateHasChanged); } private Task OnDataSourceLoaded() { - Logger.Info($"{_gridLogName} OnDataSourceLoaded"); - + Logger.Debug($"{_gridLogName} OnDataSourceLoaded"); + Reload(); return OnDataSourceChanged.InvokeAsync(_dataSource); } @@ -165,28 +122,18 @@ namespace TIAMSharedUI.Shared.Components.Grids } } - public Task AddDataItem(TDataItem dataItem) => AddDataItem(dataItem, AddMessageTag); - public Task AddDataItem(TDataItem dataItem, int messageTag) => PostDataToServerAsync(dataItem, messageTag, TrackingState.Add); - - public Task UpdateDataItem(TDataItem dataItem) => UpdateDataItem(dataItem, UpdateMessageTag); - public Task UpdateDataItem(TDataItem dataItem, int messageTag) => PostDataToServerAsync(dataItem, messageTag, TrackingState.Update); - - public Task RemoveDataItem(TDataItem dataItem) => RemoveDataItem(dataItem, RemoveMessageTag); - public Task RemoveDataItem(TDataItem dataItem, int messageTag) => PostDataToServerAsync(dataItem, messageTag, TrackingState.Remove); - - public Task RemoveDataItem(Guid id) => RemoveDataItem(id, RemoveMessageTag); - - public async Task RemoveDataItem(Guid id, int messageTag) + public Task AddDataItem(TDataItem dataItem) { - var dataItem = _dataSource.FirstOrDefault(x => x.Id == id); - if (dataItem != null) - { - _dataSource.Remove(dataItem); - await _dataSource.SaveChanges(); - //await RemoveDataItem(dataItem); - } + if (dataItem.Id.IsNullOrEmpty()) dataItem.Id = Guid.NewGuid(); + return _dataSource.Add(dataItem, true); + } - await InvokeAsync(StateHasChanged); + public Task AddDataItemAsync(TDataItem dataItem) + { + if (dataItem.Id.IsNullOrEmpty()) dataItem.Id = Guid.NewGuid(); + _dataSource.Add(dataItem); + + return SaveChangesToServerAsync(); } private async Task OnItemSaving(GridEditModelSavingEventArgs e) @@ -196,7 +143,7 @@ namespace TIAMSharedUI.Shared.Components.Grids if (e.IsNew && dataItem.Id.IsNullOrEmpty()) dataItem.Id = Guid.NewGuid(); var logText = e.IsNew ? "add" : "update"; - Logger.Info($"{_gridLogName} OnItemSaving {logText}; Id: {dataItem.Id}"); + Logger.Debug($"{_gridLogName} OnItemSaving {logText}; Id: {dataItem.Id}"); await OnGridEditModelSaving.InvokeAsync(e); @@ -206,29 +153,46 @@ namespace TIAMSharedUI.Shared.Components.Grids return; } - if (!e.IsNew) _dataSource.SetTrackingStateToUpdate(dataItem); - else + if (e.IsNew) await AddDataItemAsync(dataItem); + else await UpdateDataItemAsync(dataItem); + } + + private Task SaveChangesToServerAsync() + { + try { - if (dataItem.Id.IsNullOrEmpty()) dataItem.Id = Guid.NewGuid(); - _dataSource.Add(dataItem); + return _dataSource.SaveChangesAsync(); } + catch (Exception ex) + { + Logger.Error($"{_gridLogName} SaveChangesToServerAsync->SaveChangesAsync error!", ex); + } + + return Task.CompletedTask; + } + + private async Task SaveChangesToServer() + { + var result = false; try { var unsavedItems = await _dataSource.SaveChanges(); - if (unsavedItems.Count > 0) - Logger.Error($"OnItemSaving->TrySaveChanges error! unsavedCount: {unsavedItems.Count}"); + if ((result = unsavedItems.Count == 0) == false) + Logger.Error($"{_gridLogName} SaveChangesToServer->SaveChanges error! unsavedCount: {unsavedItems.Count}"); } catch (Exception ex) { Logger.Error($"{_gridLogName} OnItemSaving", ex); } + + return result; } private async Task OnItemDeleting(GridDataItemDeletingEventArgs e) { - Logger.Info($"{_gridLogName} OnItemDeleting"); + Logger.Debug($"{_gridLogName} OnItemDeleting"); await OnGridItemDeleting.InvokeAsync(e); @@ -239,54 +203,9 @@ namespace TIAMSharedUI.Shared.Components.Grids } var dataItem = (e.DataItem as TDataItem)!; - _dataSource.Remove(dataItem); - - var unsavedItems = await _dataSource.SaveChanges(); - if (unsavedItems.Count > 0) - Logger.Error($"OnItemDeleting->TrySaveChanges error! unsavedCount: {unsavedItems.Count}"); + await RemoveDataItem(dataItem); } - protected virtual async Task PostDataToServerAsync(TDataItem dataItem, int messageTag, TrackingState trackingState) - { - return; - - var changingEventArgs = new GridDataItemChangingEventArgs(this, dataItem, trackingState); - await OnGridItemChanging.InvokeAsync(changingEventArgs); - - if (changingEventArgs.IsCanceled) - { - Logger.Debug($"{_gridLogName} OnDataItemChanging canceled"); - return; - } - - if (messageTag == 0) return; - - Logger.Info($"{_gridLogName} PostDataToServerAsync called; transferId " + dataItem.Id); - - if (dataItem.Id.IsNullOrEmpty()) dataItem.Id = Guid.NewGuid(); - - _dataSource.UpdateCollection(dataItem, trackingState == TrackingState.Remove); //egyből látszódik a változás a grid-ben, nem csak a callback lefutásakor! felhasználóbarátabb... - J. - - //SignalRClient.PostDataAsync(messageTag, dataItem, async response => - //{ - // if (response.Status != SignalResponseStatus.Success || response.ResponseData == null) - // { - // RefreshDataSourceAsync().Forget(); - // return; - // } - - // _dataSource.UpdateCollection(response.ResponseData, trackingState == TrackingState.Remove); - - // var changedEventArgs = new GridDataItemChangedEventArgs(this, response.ResponseData, trackingState); - // await OnDataItemChanged.InvokeAsync(changedEventArgs); - - // InvokeAsync(StateHasChanged).Forget(); - //}).Forget(); - - //transfer = await devAdminSignalClient.PostDataAsync(SignalRTags.UpdateTransferAsync, transfer); - } - - private void OnCustomizeElement(GridCustomizeElementEventArgs e) { if (e.ElementType == GridElementType.DetailCell) @@ -341,5 +260,54 @@ namespace TIAMSharedUI.Shared.Components.Grids _gridLogName = $"[{GridName}]"; } + + //public Task AddDataItem(TDataItem dataItem, int messageTag) => PostDataToServerAsync(dataItem, messageTag, TrackingState.Add); + + public Task UpdateDataItem(TDataItem dataItem) => _dataSource.Update(dataItem, true); + + public Task UpdateDataItemAsync(TDataItem dataItem) + { + _dataSource.Update(dataItem, false); + return SaveChangesToServerAsync(); + } + //public Task UpdateDataItem(TDataItem dataItem, int messageTag) => PostDataToServerAsync(dataItem, messageTag, TrackingState.Update); + + public Task AddOrUpdateDataItem(TDataItem dataItem) => _dataSource.AddOrUpdate(dataItem, true); + + public Task RemoveDataItem(TDataItem dataItem) => _dataSource.Remove(dataItem, true); + //public Task RemoveDataItem(TDataItem dataItem, int messageTag) => PostDataToServerAsync(dataItem, messageTag, TrackingState.Remove); + + public Task RemoveDataItem(Guid id) => RemoveDataItem(id, RemoveMessageTag); + + public Task RemoveDataItem(Guid id, int messageTag) + { + var dataItem = _dataSource.FirstOrDefault(x => x.Id == id); + if (dataItem == null) return Task.CompletedTask; + + return _dataSource.Remove(dataItem, true); + } + } + + public class GridDataItemChangingEventArgs : GridDataItemChangedEventArgs where TDataItem : class, IId + { + internal GridDataItemChangingEventArgs(TiamGrid grid, TDataItem dataItem, TrackingState trackingState) : base(grid, dataItem, trackingState) + { + } + + public bool IsCanceled { get; set; } + } + + public class GridDataItemChangedEventArgs where TDataItem : class, IId + { + internal GridDataItemChangedEventArgs(TiamGrid grid, TDataItem dataItem, TrackingState trackingState) + { + Grid = grid; + DataItem = dataItem; + TrackingState = trackingState; + } + + public TiamGrid Grid { get; } + public TDataItem DataItem { get; } + public TrackingState TrackingState { get; } } } diff --git a/TIAMWebApp/Shared/Utility/SignalRDataSource.cs b/TIAMWebApp/Shared/Utility/SignalRDataSource.cs index 1a6ce0d3..fc9a14f3 100644 --- a/TIAMWebApp/Shared/Utility/SignalRDataSource.cs +++ b/TIAMWebApp/Shared/Utility/SignalRDataSource.cs @@ -131,7 +131,7 @@ namespace TIAMWebApp.Shared.Application.Utility var resultList = (await SignalRClient.GetAllAsync>(SignalRCrudTags.GetAllMessageTag, ContextId)) ?? throw new NullReferenceException(); - LoadDataSource(resultList); + await LoadDataSource(resultList); } public Task LoadDataSourceAsync(bool clearChangeTracking = true) @@ -141,9 +141,9 @@ namespace TIAMWebApp.Shared.Application.Utility return SignalRClient.GetAllAsync>(SignalRCrudTags.GetAllMessageTag, ContextId, result=> { if (result.Status != SignalResponseStatus.Success || result.ResponseData == null) - throw new NullReferenceException($"result.Status != SignalResponseStatus.Success || result.ResponseData == null; Status: {SignalResponseStatus.Success}"); + throw new NullReferenceException($"LoadDataSourceAsync; result.Status != SignalResponseStatus.Success || result.ResponseData == null; Status: {SignalResponseStatus.Success}"); - LoadDataSource(result.ResponseData); + LoadDataSource(result.ResponseData).Forget(); }); } @@ -225,7 +225,7 @@ namespace TIAMWebApp.Shared.Application.Utility Monitor.Enter(_syncRoot); try { - Update(index, value); + UpdateUnsafe(index, value); } finally { @@ -235,11 +235,6 @@ namespace TIAMWebApp.Shared.Application.Utility } } - /// - /// AddMessageTag - /// - /// - /// public void Add(T newValue) { if (newValue.Id.IsNullOrEmpty()) throw new ArgumentNullException(nameof(newValue), @"Add->newValue.Id.IsNullOrEmpty()"); @@ -257,15 +252,37 @@ namespace TIAMWebApp.Shared.Application.Utility { Monitor.Exit(_syncRoot); } + } + /// + /// AddMessageTag + /// + /// + /// + /// + public async Task Add(T newValue, bool autoSave) + { + Monitor.Enter(_syncRoot); + + try + { + Add(newValue); + + return autoSave ? await SaveItem(newValue, TrackingState.Add) : newValue; + } + finally + { + Monitor.Exit(_syncRoot); + } } /// /// AddMessageTag or UpdateMessageTag /// /// + /// /// - public T AddOrUpdate(T newValue) + public async Task AddOrUpdate(T newValue, bool autoSave) { if (newValue.Id.IsNullOrEmpty()) throw new ArgumentNullException(nameof(newValue), @"AddOrUpdate->newValue.Id.IsNullOrEmpty()"); @@ -275,7 +292,7 @@ namespace TIAMWebApp.Shared.Application.Utility { var index = IndexOf(newValue); - return index > -1 ? Update(index, newValue) : UnsafeAdd(newValue); + return index > -1 ? await Update(index, newValue, autoSave) : await Add(newValue, autoSave); } finally { @@ -292,12 +309,10 @@ namespace TIAMWebApp.Shared.Application.Utility // } //} - protected T UnsafeAdd(T newValue) + protected void UnsafeAdd(T newValue) { TrackingItems.AddTrackingItem(TrackingState.Add, newValue); InnerList.Add(newValue); - - return newValue; } /// @@ -305,6 +320,7 @@ namespace TIAMWebApp.Shared.Application.Utility /// /// /// + /// /// /// public void Insert(int index, T newValue) @@ -316,7 +332,7 @@ namespace TIAMWebApp.Shared.Application.Utility try { if (Contains(newValue)) - throw new ArgumentException($@"It already contains this Id! Id: {newValue.Id}", nameof(newValue)); + throw new ArgumentException($@"Insert; It already contains this Id! Id: {newValue.Id}", nameof(newValue)); TrackingItems.AddTrackingItem(TrackingState.Add, newValue); InnerList.Insert(index, newValue); @@ -325,28 +341,60 @@ namespace TIAMWebApp.Shared.Application.Utility { Monitor.Exit(_syncRoot); } - } + public async Task Insert(int index, T newValue, bool autoSave) + { + Monitor.Enter(_syncRoot); + + try + { + Insert(index, newValue); + + return autoSave ? await SaveItem(newValue, TrackingState.Add) : newValue; + } + finally + { + Monitor.Exit(_syncRoot); + } + } /// /// UpdateMessageTag /// /// - public T Update(T newItem) => Update(IndexOf(newItem), newItem); + /// + public Task Update(T newItem, bool autoSave) => Update(IndexOf(newItem), newItem, autoSave); /// /// UpdateMessageTag /// /// /// + /// /// /// /// /// /// /// - public T Update(int index, T newValue) + public async Task Update(int index, T newValue, bool autoSave) + { + Monitor.Enter(_syncRoot); + + try + { + UpdateUnsafe(index, newValue); + + return autoSave ? await SaveItem(newValue, TrackingState.Update) : newValue; + } + finally + { + Monitor.Exit(_syncRoot); + } + } + + private void UpdateUnsafe(int index, T newValue) { if (default(T) != null && newValue == null) throw new NullReferenceException(nameof(newValue)); - if (newValue.Id.IsNullOrEmpty()) throw new ArgumentNullException(nameof(newValue), @"Update->newValue.Id.IsNullOrEmpty()"); + if (newValue.Id.IsNullOrEmpty()) throw new ArgumentNullException(nameof(newValue), @"UpdateUnsafe->newValue.Id.IsNullOrEmpty()"); if ((uint)index >= (uint)Count) throw new ArgumentOutOfRangeException(nameof(index)); Monitor.Enter(_syncRoot); @@ -356,20 +404,18 @@ namespace TIAMWebApp.Shared.Application.Utility var currentItem = InnerList[index]; if (currentItem.Id != newValue.Id) - throw new ArgumentException($@"currentItem.Id != item.Id! Id: {newValue.Id}", nameof(newValue)); + throw new ArgumentException($@"UpdateUnsafe; currentItem.Id != item.Id! Id: {newValue.Id}", nameof(newValue)); TrackingItems.AddTrackingItem(TrackingState.Update, newValue, currentItem); InnerList[index] = newValue; - - return newValue; } finally { Monitor.Exit(_syncRoot); } - } + /// /// RemoveMessageTag /// @@ -392,9 +438,26 @@ namespace TIAMWebApp.Shared.Application.Utility { Monitor.Exit(_syncRoot); } - } + public async Task Remove(T item, bool autoSave) + { + Monitor.Enter(_syncRoot); + + try + { + var result = Remove(item); + + if (!autoSave || !result) return result; + + await SaveItem(item, TrackingState.Remove); + return true; + } + finally + { + Monitor.Exit(_syncRoot); + } + } /// /// /// @@ -439,9 +502,27 @@ namespace TIAMWebApp.Shared.Application.Utility { Monitor.Exit(_syncRoot); } - } + public async Task RemoveAt(int index, bool autoSave) + { + Monitor.Enter(_syncRoot); + + try + { + var currentItem = InnerList[index]; + RemoveAt(index); + + if (autoSave) + { + await SaveItem(currentItem, TrackingState.Remove); + } + } + finally + { + Monitor.Exit(_syncRoot); + } + } /// /// /// @@ -518,7 +599,6 @@ namespace TIAMWebApp.Shared.Application.Utility { try { - //throw new Exception(); await SaveTrackingItemUnsafe(trackingItem); } catch(Exception ex) @@ -533,26 +613,49 @@ namespace TIAMWebApp.Shared.Application.Utility { Monitor.Exit(_syncRoot); } + } + public async Task SaveChangesAsync() + { + Monitor.Enter(_syncRoot); + + try + { + foreach (var trackingItem in TrackingItems.ToList()) + { + try + { + await SaveTrackingItemUnsafeAsync(trackingItem); + } + catch(Exception ex) + { + TryRollbackItem(trackingItem.CurrentValue.Id, out _); + } + } + } + finally + { + Monitor.Exit(_syncRoot); + } } /// /// /// /// - /// /// - public async Task SaveItem(Guid id) + public async Task SaveItem(Guid id) { Monitor.Enter(_syncRoot); try { - T? resultItem = null; + T resultItem = null!; if (TryGetTrackingItem(id, out var trackingItem)) resultItem = await SaveTrackingItemUnsafe(trackingItem); + if (resultItem == null) throw new NullReferenceException($"SaveItem; resultItem == null"); return resultItem; } finally @@ -561,17 +664,18 @@ namespace TIAMWebApp.Shared.Application.Utility } } - public async Task SaveItem(Guid id, TrackingState trackingState) + public async Task SaveItem(Guid id, TrackingState trackingState) { Monitor.Enter(_syncRoot); try { - T? resultItem = null; + T resultItem = null!; if (TryGetValue(id, out var item)) resultItem = await SaveItem(item, trackingState); + if (resultItem == null) throw new NullReferenceException($"SaveItem; resultItem == null"); return resultItem; } finally @@ -580,31 +684,68 @@ namespace TIAMWebApp.Shared.Application.Utility } } - public Task SaveItem(T item, TrackingState trackingState) => SaveItemUnsafe(item, trackingState); + public Task SaveItem(T item, TrackingState trackingState) => SaveItemUnsafe(item, trackingState); - protected Task SaveTrackingItemUnsafe(TrackingItem trackingItem) + protected Task SaveTrackingItemUnsafe(TrackingItem trackingItem) => SaveItemUnsafe(trackingItem.CurrentValue, trackingItem.TrackingState); - protected async Task SaveItemUnsafe(T item, TrackingState trackingState) + protected Task SaveTrackingItemUnsafeAsync(TrackingItem trackingItem) + => SaveItemUnsafeAsync(trackingItem.CurrentValue, trackingItem.TrackingState); + + protected async Task SaveItemUnsafe(T item, TrackingState trackingState) { var messageTag = SignalRCrudTags.GetMessageTagByTrackingState(trackingState); - if (messageTag == SignalRTags.None) return null; //throw new ArgumentException($"messageTag == SignalRTags.None"); + if (messageTag == SignalRTags.None) throw new ArgumentException($"SaveItemUnsafe; messageTag == SignalRTags.None"); var result = await SignalRClient.PostDataAsync(messageTag, item); - if (result == null) return null; //throw new NullReferenceException($"result == null"); + if (result == null) throw new NullReferenceException($"SaveItemUnsafe; result == null"); - if (TryGetTrackingItem(item.Id, out var trackingItem)) - TrackingItems.Remove(trackingItem); - - if (TryGetIndex(result.Id, out var index)) - InnerList[index] = result; - - var eventArgs = new ItemChangedEventArgs(result, trackingState); - if (OnDataSourceItemChanged != null) await OnDataSourceItemChanged.Invoke(eventArgs); + await ProcessSavedResponseItem(result, trackingState); return result; } + protected Task SaveItemUnsafeAsync(T item, TrackingState trackingState) + { + var messageTag = SignalRCrudTags.GetMessageTagByTrackingState(trackingState); + if (messageTag == SignalRTags.None) return Task.CompletedTask; + + return SignalRClient.PostDataAsync(messageTag, item, response => + { + Monitor.Enter(_syncRoot); + + try + { + if (response.Status != SignalResponseStatus.Success || response.ResponseData == null) + { + if (TryRollbackItem(item.Id, out _)) return; + + throw new NullReferenceException($"SaveItemUnsafeAsync; result.Status != SignalResponseStatus.Success || result.ResponseData == null; Status: {SignalResponseStatus.Success}"); + } + + ProcessSavedResponseItem(response.ResponseData, trackingState).Forget(); + } + finally + { + Monitor.Exit(_syncRoot); + } + }); + } + + private async Task ProcessSavedResponseItem(T? resultItem, TrackingState trackingState) + { + if (resultItem == null) return; + + if (TryGetTrackingItem(resultItem.Id, out var trackingItem)) + TrackingItems.Remove(trackingItem); + + if (TryGetIndex(resultItem.Id, out var index)) + InnerList[index] = resultItem; + + var eventArgs = new ItemChangedEventArgs(resultItem, trackingState); + if (OnDataSourceItemChanged != null) await OnDataSourceItemChanged.Invoke(eventArgs); + } + protected void RollbackItemUnsafe(TrackingItem trackingItem) { if (TryGetIndex(trackingItem.CurrentValue.Id, out var index))