CoreRepository.cs 25 KB

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