CoreRepository.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834
  1. using System.Collections;
  2. using System.Collections.ObjectModel;
  3. using System.Collections.Specialized;
  4. using System.ComponentModel;
  5. using System.Diagnostics.CodeAnalysis;
  6. using System.Linq.Expressions;
  7. using System.Runtime.CompilerServices;
  8. using Avalonia.Controls.Selection;
  9. using Avalonia.Threading;
  10. using CommunityToolkit.Mvvm.ComponentModel;
  11. using InABox.Clients;
  12. using InABox.Configuration;
  13. using InABox.Core;
  14. using JetBrains.Annotations;
  15. using NotNullAttribute = JetBrains.Annotations.NotNullAttribute;
  16. namespace InABox.Avalonia
  17. {
  18. public class CoreRepositoryItemCreatedArgs<TShell> : EventArgs
  19. {
  20. public TShell Item { get; private set; }
  21. public CoreRepositoryItemCreatedArgs(TShell item)
  22. {
  23. Item = item;
  24. }
  25. }
  26. public delegate void CoreRepositoryItemCreatedEvent<TShell>(object sender, CoreRepositoryItemCreatedArgs<TShell> args);
  27. public abstract class CoreRepository
  28. {
  29. public static bool IsCached(string? filename) =>
  30. !String.IsNullOrWhiteSpace(filename)
  31. && File.Exists(CacheFileName(filename));
  32. public static string CacheFileName(string filename) =>
  33. Path.Combine(CacheFolder(), filename);
  34. public static string CacheFolder()
  35. {
  36. var result = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
  37. if (OperatingSystem.IsWindows())
  38. {
  39. var assembly = Path.GetFileNameWithoutExtension(System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName);
  40. result = Path.Combine(result, assembly);
  41. }
  42. if (CacheID != Guid.Empty)
  43. result = Path.Combine(result,CacheID.ToString());
  44. if (!Directory.Exists(result))
  45. Directory.CreateDirectory(result);
  46. return result;
  47. }
  48. public static Guid CacheID { get; set; }
  49. }
  50. public partial class CoreRepositoryFilter : ObservableObject
  51. {
  52. [ObservableProperty]
  53. private string? _name;
  54. [ObservableProperty]
  55. private string? _filter;
  56. [ObservableProperty]
  57. private bool _selected;
  58. public override string ToString() => Name ?? "";
  59. }
  60. public abstract class CoreRepository<TParent, TItem, TEntity> : CoreRepository, ICoreRepository, IEnumerable<TItem>
  61. where TParent : CoreRepository<TParent, TItem, TEntity>
  62. where TEntity : Entity, IRemotable, IPersistent, new()
  63. where TItem : Shell<TParent, TEntity>, new()
  64. {
  65. readonly MultiQuery _query = new();
  66. public Func<Filter<TEntity>> Filter { get; set; }
  67. protected virtual Filter<TEntity>? BaseFilter() => null;
  68. public IModelHost Host { get; set; }
  69. private DateTime _lastUpdated = DateTime.MinValue;
  70. public DateTime LastUpdated
  71. {
  72. get => _lastUpdated;
  73. protected set
  74. {
  75. _lastUpdated = value;
  76. OnPropertyChanged();
  77. }
  78. }
  79. public Func<string>? FileName { get; }
  80. private string DataFileName() => FileName != null
  81. ? $"{FileName.Invoke()}.data"
  82. : string.Empty;
  83. private string FilterFileName() => !string.IsNullOrWhiteSpace(FilterTag) && FileName != null
  84. ? $"{FileName.Invoke()}.filter"
  85. : string.Empty;
  86. protected CoreRepository(IModelHost host, Func<Filter<TEntity>> filter, Func<string>? filename = null)
  87. {
  88. AllItems = new CoreObservableCollection<TItem>();
  89. AllItems.CollectionChanged += (sender, args) =>
  90. {
  91. _items = null;
  92. ItemsChanged(AllItems);
  93. };
  94. // EnableSynchronization(AllItems);
  95. //Items = new CoreObservableCollection<TItem>();
  96. // EnableSynchronization(Items);
  97. Reset();
  98. Host = host;
  99. Filter = filter;
  100. FileName = filename;
  101. }
  102. protected virtual void ItemsChanged(IEnumerable<TItem> items)
  103. {
  104. }
  105. // private void EnableSynchronization(IEnumerable items)
  106. // {
  107. // // BindingBase.EnableCollectionSynchronization(items, null,
  108. // // (collection, context, method, access) =>
  109. // // {
  110. // // lock (collection)
  111. // // {
  112. // // method?.Invoke();
  113. // // }
  114. // // }
  115. // // );
  116. // }
  117. #region INotifyPropertyChanged
  118. public event PropertyChangedEventHandler? PropertyChanged;
  119. protected void DoPropertyChanged(object sender, PropertyChangedEventArgs args)
  120. {
  121. PropertyChanged?.Invoke(sender, args);
  122. }
  123. protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
  124. {
  125. if (EqualityComparer<T>.Default.Equals(field, value))
  126. return false;
  127. field = value;
  128. OnPropertyChanged(propertyName);
  129. return true;
  130. }
  131. protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
  132. => DoPropertyChanged(this, new PropertyChangedEventArgs(propertyName));
  133. #endregion
  134. #region Image Lookups
  135. public Dictionary<Guid, byte[]> Images { get; private set; } = new Dictionary<Guid, byte[]>();
  136. public byte[]? GetImageSource(Guid id)
  137. {
  138. return Images.GetValueOrDefault(id);
  139. }
  140. public byte[]? GetImage(Guid id) => Images.GetValueOrDefault(id);
  141. public bool HasImages() => Images.Any();
  142. #endregion
  143. protected virtual string FilterTag => typeof(TEntity).EntityName().Split('.').Last();
  144. public CoreObservableCollection<CoreRepositoryFilter> AvailableFilters { get; } = new();
  145. IEnumerable ICoreRepository.AvailableFilters => AvailableFilters;
  146. protected Filter<TEntity>? SelectedFilter =>
  147. Serialization.Deserialize<Filter<TEntity>>(AvailableFilters.FirstOrDefault(x => x.Selected)?.Filter);
  148. public bool FiltersVisible => AvailableFilters.Any();
  149. private bool FilterChanged = false;
  150. public string? SelectedFilterName => AvailableFilters.FirstOrDefault(x => x.Selected)?.Name;
  151. public void SelectFilter(String? name)
  152. {
  153. var currentSelected = AvailableFilters.FirstOrDefault(x => x.Selected);
  154. var definition = AvailableFilters.FirstOrDefault(x => String.Equals(x.Name, name));
  155. if (definition is null && name is null)
  156. {
  157. definition = AvailableFilters.FirstOrDefault(x => x.Name == "All");
  158. }
  159. FilterChanged = FilterChanged || currentSelected != definition;
  160. foreach (var availableFilter in AvailableFilters)
  161. availableFilter.Selected = definition == availableFilter;
  162. OnPropertyChanged(nameof(AvailableFilters));
  163. }
  164. protected Filter<TEntity>? EffectiveFilter()
  165. {
  166. var filters = new Filters<TEntity>();
  167. filters.Add(BaseFilter());
  168. filters.Add(Filter?.Invoke());
  169. filters.Add(SelectedFilter);
  170. var result = filters.Combine();
  171. return result;
  172. }
  173. protected Columns<TOtherEntity> GetColumns<TOtherItem, TOtherEntity>()
  174. where TOtherItem : Shell<TParent, TOtherEntity>, new()
  175. where TOtherEntity : Entity, IRemotable, IPersistent, new()
  176. {
  177. return new TOtherItem().Columns.Columns;
  178. }
  179. protected virtual void Initialize()
  180. {
  181. Loaded = false;
  182. AllItems.Clear();
  183. //Items.Clear();
  184. Images.Clear();
  185. }
  186. public bool Loaded { get; protected set; }
  187. private void DoRefresh(bool force)
  188. {
  189. force = force || FilterChanged;
  190. // force = force || Host.Status == ConnectionStatus.Connected;
  191. var selectedIDs = SelectedItems.Select(x => x.ID).ToHashSet();
  192. //Items.Clear();
  193. var dataFileName = DataFileName();
  194. if ((!force || Host.Status != ConnectionStatus.Connected) && !Loaded && CoreRepository.IsCached(dataFileName))
  195. {
  196. DoBeforeLoad();
  197. if (LoadFromStorage())
  198. {
  199. DoAfterLoad();
  200. foreach(var item in Items)
  201. {
  202. item.IsSelected = selectedIDs.Contains(item.ID);
  203. }
  204. return;
  205. }
  206. }
  207. if ((force || !Loaded) && (Host.Status == ConnectionStatus.Connected))
  208. {
  209. DoLoad();
  210. SaveToStorage();
  211. foreach(var item in Items)
  212. {
  213. item.IsSelected = selectedIDs.Contains(item.ID);
  214. }
  215. return;
  216. }
  217. foreach(var item in Items)
  218. {
  219. item.IsSelected = selectedIDs.Contains(item.ID);
  220. }
  221. }
  222. private void AfterRefresh()
  223. {
  224. Loaded = true;
  225. Dispatcher.UIThread.Invoke(Search);
  226. NotifyChanged();
  227. }
  228. public virtual ICoreRepository Refresh(bool force)
  229. {
  230. DoRefresh(force);
  231. AfterRefresh();
  232. return this;
  233. }
  234. public void Refresh(bool force, Action loaded)
  235. {
  236. Task.Run(
  237. () =>
  238. {
  239. DoRefresh(force);
  240. Dispatcher.UIThread.Post(
  241. () =>
  242. {
  243. AfterRefresh();
  244. loaded?.Invoke();
  245. }
  246. );
  247. }
  248. );
  249. }
  250. public Task<ICoreRepository> RefreshAsync(bool force)
  251. {
  252. return Task.Run(() => Refresh(force));
  253. }
  254. public void Reset()
  255. {
  256. Initialize();
  257. }
  258. public event CoreRepositoryChangedEvent? Changed;
  259. protected void NotifyChanged() => Changed?.Invoke(this, new CoreRepositoryChangedEventArgs());
  260. public virtual SortOrder<TEntity>? Sort => LookupFactory.DefineSort<TEntity>();
  261. protected CoreObservableCollection<TItem> AllItems { get; private set; }
  262. private CoreTable _table = new CoreTable();
  263. private IEnumerable<TItem>? _items;
  264. public IEnumerable<TItem> Items
  265. {
  266. get
  267. {
  268. _items ??= SearchAndSort();
  269. return _items;
  270. }
  271. }
  272. public int ItemCount => Items.Count();
  273. IEnumerable ICoreRepository.Items => Items;
  274. #region Item Selection
  275. IEnumerable ICoreRepository.SelectedItems => SelectedItems;
  276. IEnumerable<TItem> SelectedItems => Items.Where(x => x.IsSelected);
  277. public bool IsSelected(TItem? item) => item is not null && item.IsSelected;
  278. public void SetSelectedItems(IEnumerable<TItem> items)
  279. {
  280. var selectedItems = items.Select(x => x.ID).ToHashSet();
  281. foreach(var item in Items)
  282. {
  283. item.IsSelected = selectedItems.Contains(item.ID);
  284. }
  285. Search();
  286. }
  287. public void SelectItem([CanBeNull] TItem? item)
  288. {
  289. if ((item != null) && !item.IsSelected)
  290. {
  291. item.IsSelected = true;
  292. Search();
  293. }
  294. }
  295. public void UnselectItem([CanBeNull] TItem? item)
  296. {
  297. if ((item != null) && item.IsSelected)
  298. {
  299. item.IsSelected = false;
  300. Search();
  301. }
  302. }
  303. public void ToggleSelection(TItem? item)
  304. {
  305. if (IsSelected(item))
  306. UnselectItem(item);
  307. else
  308. SelectItem(item);
  309. }
  310. public void SelectNone()
  311. {
  312. foreach(var item in AllItems)
  313. {
  314. item.IsSelected = false;
  315. }
  316. Search();
  317. }
  318. public void SelectAll()
  319. {
  320. foreach(var item in AllItems)
  321. {
  322. item.IsSelected = true;
  323. }
  324. Search();
  325. }
  326. void ICoreRepository.SelectItem(object item) => SelectItem(item as TItem);
  327. void ICoreRepository.UnselectItem(object item) => UnselectItem(item as TItem);
  328. void ICoreRepository.ToggleSelection(object item) => ToggleSelection(item as TItem);
  329. bool ICoreRepository.IsSelected(object item) => IsSelected(item as TItem);
  330. void ICoreRepository.SetSelectedItems(IEnumerable<object> items) => SetSelectedItems(items.OfType<TItem>());
  331. #endregion
  332. #region Searching
  333. private Func<TItem, bool>? _searchPredicate;
  334. public Func<TItem, bool>? SearchPredicate
  335. {
  336. get => _searchPredicate;
  337. set
  338. {
  339. _searchPredicate = value;
  340. _items = null;
  341. }
  342. }
  343. private Func<List<TItem>, List<TItem>>? _sortPredicate;
  344. public Func<List<TItem>,List<TItem>>? SortPredicate
  345. {
  346. get => _sortPredicate;
  347. set
  348. {
  349. _sortPredicate = value;
  350. _items = null;
  351. }
  352. }
  353. public ICoreRepository Search(Func<TItem, bool> searchpredicate, Func<List<TItem>,List<TItem>> sortpredicate)
  354. {
  355. SortPredicate = sortpredicate;
  356. SearchPredicate = searchpredicate;
  357. Search();
  358. return this;
  359. }
  360. public ICoreRepository Search(Func<TItem, bool>? searchpredicate)
  361. {
  362. SearchPredicate = searchpredicate;
  363. Search();
  364. return this;
  365. }
  366. private TItem[] SearchAndSort()
  367. {
  368. List<TItem> _result;
  369. if (AllItems != null)
  370. {
  371. if (SearchPredicate != null)
  372. {
  373. var search = AllItems.Where(SearchPredicate);
  374. _result = new List<TItem>(search);
  375. }
  376. else
  377. _result = new List<TItem>(AllItems);
  378. }
  379. else
  380. _result = new List<TItem>();
  381. if (SortPredicate != null)
  382. _result = SortPredicate(_result);
  383. return _result.ToArray();
  384. }
  385. public ICoreRepository Search()
  386. {
  387. _items = null;
  388. OnPropertyChanged(nameof(Items));
  389. OnPropertyChanged(nameof(ItemCount));
  390. return this;
  391. }
  392. ICoreRepository ICoreRepository.Search(Func<object,bool> method)
  393. => Search((o) => method(o as TItem));
  394. #endregion
  395. protected virtual Expression<Func<TEntity, object?>>? ImageColumn => null;
  396. #region Loading
  397. private void DoBeforeLoad()
  398. {
  399. _query.Clear();
  400. _query.Add(
  401. EffectiveFilter(),
  402. GetColumns<TItem,TEntity>(),
  403. Sort
  404. );
  405. if (ImageColumn is not null)
  406. {
  407. _query.Add(
  408. new Filter<Document>(x => x.ID).InQuery(EffectiveFilter(), ImageColumn),
  409. Columns.None<Document>().Add(x => x.ID)
  410. .Add(x => x.Data)
  411. );
  412. }
  413. }
  414. protected virtual void BeforeLoad(MultiQuery query)
  415. {
  416. }
  417. protected virtual void AfterLoad(MultiQuery query)
  418. {
  419. }
  420. protected void DoLoad()
  421. {
  422. try
  423. {
  424. var selected = AvailableFilters.FirstOrDefault(x => x.Selected)?.Name;
  425. if (!string.IsNullOrWhiteSpace(FilterTag))
  426. {
  427. var filters = new GlobalConfiguration<CoreFilterDefinitions>(FilterTag).Load()
  428. .Where(x => x.Visibility == CoreFilterDefinitionVisibility.DesktopAndMobile)
  429. .Select(x => new CoreRepositoryFilter() { Name = x.Name, Filter = x.Filter, Selected = string.Equals(x.Name,selected) })
  430. .ToList();
  431. if (filters.Any())
  432. filters.Insert(0, new CoreRepositoryFilter() { Name="All", Filter = "", Selected = !filters.Any(x => string.Equals(x.Name, selected)) });
  433. AvailableFilters.ReplaceRange(filters);
  434. Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(FiltersVisible)));
  435. }
  436. DoBeforeLoad();
  437. BeforeLoad(_query);
  438. Task.Run(() =>
  439. {
  440. _query.Query();
  441. DoAfterLoad();
  442. }).Wait();
  443. AfterLoad(_query);
  444. Search();
  445. LastUpdated = DateTime.Now;
  446. FilterChanged = false;
  447. }
  448. catch (Exception e)
  449. {
  450. MobileLogging.Log(e,"CoreRepository");
  451. }
  452. }
  453. protected void DoAfterLoad()
  454. {
  455. _table = _query.Get<TEntity>();
  456. AllItems.ReplaceRange(_query.Get<TEntity>().Rows.Select(CreateItem<TItem>));
  457. if (ImageColumn != null)
  458. {
  459. Images.Clear();
  460. _query.Get<Document>().IntoDictionary<Document, Guid, byte[]>(Images, x => x.ID,
  461. r => r.Get<Document, byte[]>(x => x.Data));
  462. }
  463. }
  464. #endregion
  465. #region Persistent Storage
  466. protected void InitializeTables()
  467. {
  468. var defs = _query.Definitions();
  469. foreach (var def in defs)
  470. {
  471. var table = InitializeTable(def.Value);
  472. _query.Set(def.Key, table);
  473. }
  474. }
  475. protected CoreTable InitializeTable(IQueryDef def)
  476. {
  477. var table = new CoreTable();
  478. if (def.Columns != null)
  479. table.LoadColumns(def.Columns);
  480. else
  481. table.LoadColumns(def.Type);
  482. return table;
  483. }
  484. protected class QueryStorage : ISerializeBinary
  485. {
  486. private readonly Dictionary<String, CoreTable> _data = new Dictionary<string, CoreTable>();
  487. public CoreTable Get([NotNull] String key) => _data[key];
  488. public void Set([NotNull] String key, CoreTable table) => _data[key] = table;
  489. public bool Contains([NotNull] String key) => _data.ContainsKey(key);
  490. public bool TryGet(string key, [NotNullWhen(true)] out CoreTable? table)
  491. {
  492. return _data.TryGetValue(key, out table);
  493. }
  494. public void SerializeBinary(CoreBinaryWriter writer)
  495. {
  496. writer.Write(_data.Count);
  497. foreach (var key in _data.Keys)
  498. {
  499. writer.Write(key);
  500. _data[key].SerializeBinary(writer);
  501. }
  502. }
  503. public void DeserializeBinary(CoreBinaryReader reader)
  504. {
  505. int count = reader.ReadInt32();
  506. for (int i = 0; i < count; i++)
  507. {
  508. String key = reader.ReadString();
  509. CoreTable table = new CoreTable();
  510. table.DeserializeBinary(reader);
  511. _data[key] = table;
  512. }
  513. }
  514. }
  515. protected bool LoadFromStorage()
  516. {
  517. var filterFileName = FilterFileName();
  518. if (!filterFileName.IsNullOrWhiteSpace())
  519. {
  520. if(CacheManager.TryLoadJSON<ObservableCollection<CoreRepositoryFilter>>(filterFileName, out var filters, out var _))
  521. {
  522. AvailableFilters.ReplaceRange(filters);
  523. Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(FiltersVisible)));
  524. }
  525. }
  526. var dataFileName = DataFileName();
  527. if (dataFileName.IsNullOrWhiteSpace())
  528. {
  529. InitializeTables();
  530. return true;
  531. }
  532. if(CacheManager.TryLoadBinary<QueryStorage>(dataFileName, out var storage, out var lastUpdated))
  533. {
  534. LastUpdated = lastUpdated;
  535. var defs = _query.Definitions();
  536. foreach (var key in defs.Keys)
  537. {
  538. var keyStr = key.ToString() ?? "";
  539. if(!storage.TryGet(keyStr, out var table))
  540. {
  541. table = InitializeTable(defs[key]);
  542. }
  543. var queryDef = _query.Definitions()[key];
  544. if (CheckColumns(table, queryDef.Type, queryDef.Columns))
  545. _query.Set(key, table);
  546. else
  547. return false;
  548. }
  549. }
  550. else
  551. {
  552. InitializeTables();
  553. }
  554. return true;
  555. }
  556. private bool CheckColumns(CoreTable table, Type T, IColumns? required)
  557. {
  558. required = CoreUtils.GetColumns(T, required);
  559. foreach (var column in required.ColumnNames())
  560. {
  561. if (!table.Columns.Any(x => String.Equals(x.ColumnName, column)))
  562. return false;
  563. }
  564. return true;
  565. }
  566. protected void SaveToStorage()
  567. {
  568. var filterFileName = FilterFileName();
  569. if (!string.IsNullOrWhiteSpace(filterFileName))
  570. {
  571. CacheManager.SaveJSON(filterFileName, AvailableFilters, DateTime.MaxValue);
  572. }
  573. var dataFileName = DataFileName();
  574. if (dataFileName.IsNullOrWhiteSpace())
  575. return;
  576. QueryStorage storage = new QueryStorage();
  577. var results = _query.Results();
  578. foreach (var key in results.Keys)
  579. storage.Set(key.ToString() ?? "", results[key]);
  580. CacheManager.SaveBinary(dataFileName, storage, DateTime.MaxValue);
  581. }
  582. #endregion
  583. #region CRUD Operations
  584. public event CoreRepositoryItemCreatedEvent<TItem>? ItemAdded;
  585. private T CreateItem<T>(CoreRow row)
  586. where T : Shell<TParent,TEntity>, new()
  587. {
  588. var result = new T() { Row = row, Parent = (TParent)this };
  589. result.PropertyChanged += (_, args) => DoPropertyChanged(result, args);
  590. return result;
  591. }
  592. public virtual TItem CreateItem()
  593. {
  594. CoreRow row = _table.NewRow();
  595. var entity = new TEntity();
  596. _table.FillRow(row,entity);
  597. var result = CreateItem<TItem>(row);
  598. ItemAdded?.Invoke(this, new CoreRepositoryItemCreatedArgs<TItem>(result));
  599. return result;
  600. }
  601. public bool HasItem(TItem item)
  602. {
  603. return _table.Rows.Contains(item.Row);
  604. }
  605. public virtual void CommitItem(TItem item)
  606. {
  607. _table.Rows.Add(item.Row);
  608. AllItems.Add(item);
  609. Search();
  610. NotifyChanged();
  611. }
  612. public virtual TItem AddItem()
  613. {
  614. var result = CreateItem();
  615. CommitItem(result);
  616. return result;
  617. }
  618. public virtual void DeleteItem(TItem item)
  619. {
  620. _table.Rows.Remove(item.Row);
  621. AllItems.Remove(item);
  622. Search();
  623. NotifyChanged();
  624. }
  625. public virtual void Save(string auditMessage)
  626. {
  627. var _changes = AllItems.Where(x => x.IsChanged()).ToArray();
  628. if (_changes.Any())
  629. {
  630. new Client<TEntity>().Save(_changes.Select(x=>x.Entity), auditMessage);
  631. foreach (var _change in _changes)
  632. _change.SyncRow();
  633. SaveToStorage();
  634. }
  635. }
  636. public virtual Task SaveAsync(string auditMessage)
  637. {
  638. return Task.Run(() => Save(auditMessage));
  639. }
  640. object ICoreRepository.CreateItem() => this.CreateItem();
  641. bool ICoreRepository.HasItem(object item) => item is TItem tItem ? HasItem(tItem) : false;
  642. void ICoreRepository.CommitItem(object item)
  643. {
  644. if (item is TItem titem)
  645. CommitItem(titem);
  646. }
  647. object ICoreRepository.AddItem() => this.AddItem();
  648. void ICoreRepository.DeleteItem(object item)
  649. {
  650. if (item is TItem titem)
  651. DeleteItem(titem);
  652. }
  653. #endregion
  654. #region IEnumerable Interface
  655. IEnumerator<TItem> IEnumerable<TItem>.GetEnumerator()
  656. {
  657. return Items.GetEnumerator();
  658. }
  659. public IEnumerator GetEnumerator()
  660. {
  661. return Items.GetEnumerator();
  662. }
  663. #endregion
  664. }
  665. }