From 0163042fb70051fd2ccb92016d9efb971f09919d Mon Sep 17 00:00:00 2001 From: jbe2277 Date: Sat, 30 Nov 2024 11:44:49 +0100 Subject: [PATCH] BookLib: Use async methods for database access; improve error handling --- .../Controllers/EntityControllerTest.cs | 8 ++-- .../Controllers/MockEntityController.cs | 14 ++----- .../Controllers/ModuleControllerTest.cs | 26 ++++++------ .../Controllers/BookController.cs | 18 +++++++- .../Controllers/EntityController.cs | 30 +++++++++---- .../Controllers/IEntityController.cs | 2 +- .../Controllers/ModuleController.cs | 3 +- .../Controllers/PersonController.cs | 14 ++++++- .../GlobalSuppressions.cs | 3 +- .../Properties/Resources.Designer.cs | 29 ++++++++++++- .../Properties/Resources.resx | 9 ++++ .../Services/EntityService.cs | 42 ++++++++++--------- .../Services/IEntityService.cs | 6 +++ 13 files changed, 143 insertions(+), 61 deletions(-) diff --git a/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications.Test/Controllers/EntityControllerTest.cs b/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications.Test/Controllers/EntityControllerTest.cs index aa8182d1..9a674221 100644 --- a/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications.Test/Controllers/EntityControllerTest.cs +++ b/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications.Test/Controllers/EntityControllerTest.cs @@ -1,10 +1,10 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Waf.Applications; using System.Waf.UnitTesting; using System.Waf.UnitTesting.Mocks; using Waf.BookLibrary.Library.Applications.Controllers; using Waf.BookLibrary.Library.Applications.Services; using Waf.BookLibrary.Library.Applications.ViewModels; -using Waf.BookLibrary.Library.Domain; namespace Test.BookLibrary.Library.Applications.Controllers; @@ -12,7 +12,7 @@ namespace Test.BookLibrary.Library.Applications.Controllers; public class EntityControllerTest : ApplicationsTest { [TestMethod] - public void ValidateBeforeSave() + public async Task ValidateBeforeSave() { var controller = Get(); controller.Initialize(); @@ -23,7 +23,7 @@ public void ValidateBeforeSave() Assert.IsTrue(controller.HasChanges()); Assert.IsTrue(controller.CanSave()); - controller.Save(); + Assert.IsTrue(await controller.SaveCore()); Assert.IsFalse(controller.HasChanges()); var messageService = Get(); @@ -33,7 +33,7 @@ public void ValidateBeforeSave() entityService.Persons[^1].Validate(); Assert.IsTrue(controller.HasChanges()); var shellViewModel = Get(); - shellViewModel.SaveCommand!.Execute(null); + await ((AsyncDelegateCommand)shellViewModel.SaveCommand!).ExecuteAsync(null); Assert.AreEqual(MessageType.Error, messageService.MessageType); Assert.IsTrue(controller.HasChanges()); diff --git a/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications.Test/Controllers/MockEntityController.cs b/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications.Test/Controllers/MockEntityController.cs index 94a388c9..ffa319fa 100644 --- a/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications.Test/Controllers/MockEntityController.cs +++ b/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications.Test/Controllers/MockEntityController.cs @@ -12,13 +12,11 @@ public class MockEntityController : IEntityController public bool HasChangesResult { get; set; } - public bool CanSaveResult { get; set; } + public bool CanSaveResult { get; set; } = true; - public bool SaveResult { get; set; } + public Func>? SaveCoreStub { get; set; } - public bool SaveCalled { get; set; } - - public MockEntityController() => CanSaveResult = true; + public bool SaveCoreCalled { get; set; } public void Initialize() => InitializeCalled = true; @@ -26,11 +24,7 @@ public class MockEntityController : IEntityController public bool CanSave() => CanSaveResult; - public bool Save() - { - SaveCalled = true; - return SaveResult; - } + public Task SaveCore() => SaveCoreStub?.Invoke() ?? Task.FromResult(true); public void Shutdown() => ShutdownCalled = true; } diff --git a/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications.Test/Controllers/ModuleControllerTest.cs b/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications.Test/Controllers/ModuleControllerTest.cs index bb51a557..0c0e0701 100644 --- a/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications.Test/Controllers/ModuleControllerTest.cs +++ b/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications.Test/Controllers/ModuleControllerTest.cs @@ -67,33 +67,35 @@ public void ModuleControllerHasChangesTest() return true; }; // Then we simulate that the EntityController wasn't able to save the changes. - entityController.SaveResult = false; + var saveCalled = false; + entityController.SaveCoreStub = () => + { + saveCalled = true; + return Task.FromResult(false); + }; shellViewModel.ExitCommand!.Execute(null); - // The Save method must be called. Because the save operation failed the expect the ShellView to be - // still visible. - Assert.IsTrue(entityController.SaveCalled); + // The Save method must be called. Because the save operation failed the expect the ShellView to be still visible. + Assert.IsTrue(saveCalled); Assert.IsTrue(shellView.IsVisible); // Exit the application although we have unsaved changes. entityController.HasChangesResult = true; - entityController.SaveCalled = false; + saveCalled = false; // When the question box asks us to save the changes we say "Cancel" => null. messageService.ShowQuestionStub = (_, _) => null; - // This time the Save method must not be called. Because we have chosen "Cancel" the ShellView must still - // be visible. + // This time the Save method must not be called. Because we have chosen "Cancel" the ShellView must still be visible. shellViewModel.ExitCommand.Execute(null); - Assert.IsFalse(entityController.SaveCalled); + Assert.IsFalse(saveCalled); Assert.IsTrue(shellView.IsVisible); // Exit the application although we have unsaved changes. entityController.HasChangesResult = true; - entityController.SaveCalled = false; + saveCalled = false; // When the question box asks us to save the changes we say "No" => false. messageService.ShowQuestionStub = (_, _) => false; - // This time the Save method must not be called. Because we have chosen "No" the ShellView must still - // be closed. + // This time the Save method must not be called. Because we have chosen "No" the ShellView must still be closed. shellViewModel.ExitCommand.Execute(null); - Assert.IsFalse(entityController.SaveCalled); + Assert.IsFalse(saveCalled); Assert.IsFalse(shellView.IsVisible); } diff --git a/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Controllers/BookController.cs b/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Controllers/BookController.cs index 00e1040a..5f55c0ee 100644 --- a/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Controllers/BookController.cs +++ b/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Controllers/BookController.cs @@ -1,6 +1,8 @@ using System.ComponentModel.Composition; using System.Waf.Applications; +using System.Waf.Applications.Services; using Waf.BookLibrary.Library.Applications.DataModels; +using Waf.BookLibrary.Library.Applications.Properties; using Waf.BookLibrary.Library.Applications.Services; using Waf.BookLibrary.Library.Applications.ViewModels; using Waf.BookLibrary.Library.Domain; @@ -11,6 +13,7 @@ namespace Waf.BookLibrary.Library.Applications.Controllers; [Export] internal class BookController { + private readonly IMessageService messageService; private readonly IShellService shellService; private readonly IEntityService entityService; private readonly BookListViewModel bookListViewModel; @@ -22,8 +25,10 @@ internal class BookController private SynchronizingList? bookDataModels; [ImportingConstructor] - public BookController(IShellService shellService, IEntityService entityService, BookListViewModel bookListViewModel, BookViewModel bookViewModel, ExportFactory lendToViewModelFactory) + public BookController(IMessageService messageService, IShellService shellService, IEntityService entityService, BookListViewModel bookListViewModel, + BookViewModel bookViewModel, ExportFactory lendToViewModelFactory) { + this.messageService = messageService; this.shellService = shellService; this.entityService = entityService; this.bookListViewModel = bookListViewModel; @@ -36,7 +41,7 @@ public BookController(IShellService shellService, IEntityService entityService, internal ObservableListViewCore? BooksView { get; private set; } - public void Initialize() + public async void Initialize() { bookViewModel.LendToCommand = lendToCommand; bookViewModel.PropertyChanged += BookViewModelPropertyChanged; @@ -51,6 +56,15 @@ public void Initialize() shellService.BookListView = bookListViewModel.View; shellService.BookView = bookViewModel.View; + try + { + await entityService.LoadBooks(); + } + catch (Exception ex) + { + Log.Default.Error(ex, "LoadBooks"); + messageService.ShowError(shellService.ShellView, Resources.LoadErrorBooks); + } bookListViewModel.SelectedBook = bookListViewModel.Books.FirstOrDefault(); } diff --git a/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Controllers/EntityController.cs b/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Controllers/EntityController.cs index 66cf00a8..086262cb 100644 --- a/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Controllers/EntityController.cs +++ b/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Controllers/EntityController.cs @@ -18,7 +18,7 @@ internal class EntityController : IEntityController private readonly IShellService shellService; private readonly IDBContextService dBContextService; private readonly Lazy shellViewModel; - private readonly DelegateCommand saveCommand; + private readonly AsyncDelegateCommand saveCommand; private DbContext? bookLibraryContext; [ImportingConstructor] @@ -29,7 +29,7 @@ public EntityController(EntityService entityService, IMessageService messageServ this.shellService = shellService; this.dBContextService = dBContextService; this.shellViewModel = shellViewModel; - saveCommand = new(() => Save(), CanSave); + saveCommand = new(Save, CanSave); } private ShellViewModel ShellViewModel => shellViewModel.Value; @@ -50,7 +50,22 @@ public void Initialize() public bool CanSave() => ShellViewModel.IsValid; - public bool Save() + public async Task SaveCore() + { + Log.Default.Info("Save changes in database."); + try + { + await entityService.SaveChanges().ConfigureAwait(false); + return true; + } + catch (Exception ex) + { + Log.Default.Error(ex, "SaveChangesAsync"); + } + return false; + } + + private async Task Save() { var entities = bookLibraryContext!.ChangeTracker.Entries().Where(x => x.State == EntityState.Added || x.State == EntityState.Modified).Select(x => x.Entity).ToArray(); var errors = entities.OfType().Where(x => x.HasErrors).ToArray(); @@ -59,12 +74,13 @@ public bool Save() var errorMessages = errors.Select(x => string.Format(CultureInfo.CurrentCulture, Resources.EntityInvalid, EntityToString(x), string.Join(Environment.NewLine, x.Errors))); Log.Default.Warn("Abort save changes because of errors: {0}", string.Join("; ", errorMessages)); messageService.ShowError(shellService.ShellView, Resources.SaveErrorInvalidEntities, string.Join(Environment.NewLine, errorMessages)); - return false; + return; } - Log.Default.Info("Save changes in database."); - bookLibraryContext.SaveChanges(); - return true; + if (!await SaveCore()) + { + messageService.ShowError(shellService.ShellView, Resources.SaveErrorDatabase); + } } private void ShellViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e) diff --git a/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Controllers/IEntityController.cs b/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Controllers/IEntityController.cs index de63d114..3a7da428 100644 --- a/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Controllers/IEntityController.cs +++ b/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Controllers/IEntityController.cs @@ -9,7 +9,7 @@ internal interface IEntityController bool CanSave(); - bool Save(); + Task SaveCore(); void Shutdown(); } diff --git a/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Controllers/ModuleController.cs b/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Controllers/ModuleController.cs index 9cb0bbcd..f115238f 100644 --- a/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Controllers/ModuleController.cs +++ b/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Controllers/ModuleController.cs @@ -56,8 +56,9 @@ private void ShellViewModelClosing(object? sender, CancelEventArgs e) bool? result = messageService.ShowQuestion(shellService.ShellView, Resources.SaveChangesQuestion); if (result == true) { - if (!entityController.Save()) + if (!entityController.SaveCore().GetAwaiter().GetResult()) { + messageService.ShowError(shellService.ShellView, Resources.SaveErrorDatabase); e.Cancel = true; } } diff --git a/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Controllers/PersonController.cs b/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Controllers/PersonController.cs index 5fb86bab..568b1e1b 100644 --- a/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Controllers/PersonController.cs +++ b/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Controllers/PersonController.cs @@ -39,18 +39,30 @@ public PersonController(IMessageService messageService, IShellService shellServi internal ObservableListViewCore? PersonsView { get; private set; } - public void Initialize() + public async void Initialize() { personViewModel.CreateNewEmailCommand = createNewEmailCommand; personViewModel.PropertyChanged += PersonViewModelPropertyChanged; + PersonsView = new(entityService.Persons, null, personListViewModel.Filter, null); personListViewModel.Persons = PersonsView; personListViewModel.AddNewCommand = addNewCommand; personListViewModel.RemoveCommand = removeCommand; personListViewModel.CreateNewEmailCommand = createNewEmailCommand; personListViewModel.PropertyChanged += PersonListViewModelPropertyChanged; + shellService.PersonListView = personListViewModel.View; shellService.PersonView = personViewModel.View; + + try + { + await entityService.LoadPersons(); + } + catch (Exception ex) + { + Log.Default.Error(ex, "LoadPersons"); + messageService.ShowError(shellService.ShellView, Resources.LoadErrorPersons); + } personListViewModel.SelectedPerson = personListViewModel.Persons.FirstOrDefault(); } diff --git a/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/GlobalSuppressions.cs b/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/GlobalSuppressions.cs index f9807b83..300fba2f 100644 --- a/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/GlobalSuppressions.cs +++ b/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/GlobalSuppressions.cs @@ -2,7 +2,6 @@ [assembly: SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Scope = "type", Target = "~T:Waf.BookLibrary.Library.Applications.Controllers.EntityController")] [assembly: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "BookList", Scope = "type", Target = "~T:Waf.BookLibrary.Library.Applications.ViewModels.BookListViewModel")] [assembly: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "BookList", Scope = "type", Target = "~T:Waf.BookLibrary.Library.Applications.Views.IBookListView")] -[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "CanSave", Scope = "member", Target = "~M:Waf.BookLibrary.Library.Applications.Controllers.EntityController.Save~System.Boolean")] [assembly: SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Scope = "member", Target = "~M:Waf.BookLibrary.Library.Applications.ViewModels.PersonListViewModel.Filter(Waf.BookLibrary.Library.Domain.Person)~System.Boolean")] [assembly: SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Scope = "member", Target = "~M:Waf.BookLibrary.Library.Applications.ViewModels.BookListViewModel.Filter(Waf.BookLibrary.Library.Applications.DataModels.BookDataModel)~System.Boolean")] [assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "~M:Waf.BookLibrary.Library.Applications.Controllers.ModuleController.Shutdown")] @@ -16,4 +15,4 @@ [assembly: SuppressMessage("Performance", "CA1826:Do not use Enumerable methods on indexable collections", Justification = "", Scope = "member", Target = "~M:Waf.BookLibrary.Library.Applications.Controllers.PersonController.Initialize")] [assembly: SuppressMessage("Performance", "CA1826:Do not use Enumerable methods on indexable collections", Justification = "", Scope = "member", Target = "~M:Waf.BookLibrary.Library.Applications.Controllers.PersonController.RemovePerson")] [assembly: SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "", Scope = "member", Target = "~M:Waf.BookLibrary.Library.Applications.ViewModels.ShellViewModel.#ctor(Waf.BookLibrary.Library.Applications.Views.IShellView,System.Waf.Applications.Services.IMessageService,Waf.BookLibrary.Library.Applications.Services.IShellService,System.Waf.Applications.Services.ISettingsService)")] -[assembly: SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "", Scope = "member", Target = "~M:Waf.BookLibrary.Library.Applications.Controllers.EntityController.Save~System.Boolean")] +[assembly: SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "", Scope = "member", Target = "~M:Waf.BookLibrary.Library.Applications.Controllers.EntityController.Save~System.Threading.Tasks.Task")] diff --git a/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Properties/Resources.Designer.cs b/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Properties/Resources.Designer.cs index 4aa7e557..7bac3e98 100644 --- a/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Properties/Resources.Designer.cs +++ b/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace Waf.BookLibrary.Library.Applications.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { @@ -95,6 +95,24 @@ internal static string EntityInvalid { } } + /// + /// Looks up a localized string similar to Could not load the Books from the database.. + /// + internal static string LoadErrorBooks { + get { + return ResourceManager.GetString("LoadErrorBooks", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not load the Persons from the database.. + /// + internal static string LoadErrorPersons { + get { + return ResourceManager.GetString("LoadErrorPersons", resourceCulture); + } + } + /// /// Looks up a localized string similar to Do you really want to close the application and lose the changes you made?. /// @@ -122,6 +140,15 @@ internal static string SaveChangesQuestion { } } + /// + /// Looks up a localized string similar to The save operation failed.. + /// + internal static string SaveErrorDatabase { + get { + return ResourceManager.GetString("SaveErrorDatabase", resourceCulture); + } + } + /// /// Looks up a localized string similar to The save operation was cancelled because of input errors. Please correct the error(s): /// diff --git a/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Properties/Resources.resx b/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Properties/Resources.resx index a348e326..6211aeb6 100644 --- a/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Properties/Resources.resx +++ b/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Properties/Resources.resx @@ -148,4 +148,13 @@ https://github.com/jbe2277/waf {0} + + The save operation failed. + + + Could not load the Books from the database. + + + Could not load the Persons from the database. + \ No newline at end of file diff --git a/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Services/EntityService.cs b/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Services/EntityService.cs index 6164c8df..7485e687 100644 --- a/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Services/EntityService.cs +++ b/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Services/EntityService.cs @@ -12,29 +12,31 @@ internal class EntityService : IEntityService public DbContext? BookLibraryContext { get; set; } - public ObservableCollection Books + public ObservableCollection Books => books ??= BookLibraryContext!.Set().Local.ToObservableCollection(); + + public ObservableCollection Persons => persons ??= BookLibraryContext!.Set().Local.ToObservableCollection(); + + public async Task LoadBooks(CancellationToken cancellation = default) + { + // Simulate a delay or an error + //await Task.Delay(2000, cancellation); + //throw new InvalidOperationException("Loading failed"); + await BookLibraryContext!.Set().Include(x => x.LendTo).LoadAsync(cancellation); + } + + public async Task LoadPersons(CancellationToken cancellation = default) { - get - { - if (books == null && BookLibraryContext != null) - { - BookLibraryContext.Set().Include(x => x.LendTo).Load(); - books = BookLibraryContext.Set().Local.ToObservableCollection(); - } - return books!; - } + // Simulate a delay or an error + //await Task.Delay(2000, cancellation); + //throw new InvalidOperationException("Loading failed"); + await BookLibraryContext!.Set().LoadAsync(cancellation); } - public ObservableCollection Persons + public async Task SaveChanges(CancellationToken cancellation = default) { - get - { - if (persons == null && BookLibraryContext != null) - { - BookLibraryContext.Set().Load(); - persons = BookLibraryContext.Set().Local.ToObservableCollection(); - } - return persons!; - } + // Simulate a delay or an error + //await Task.Delay(2000, cancellation).ConfigureAwait(false); + //throw new InvalidOperationException("Loading failed"); + await BookLibraryContext!.SaveChangesAsync(cancellation); } } diff --git a/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Services/IEntityService.cs b/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Services/IEntityService.cs index a676d36f..ddd45cb3 100644 --- a/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Services/IEntityService.cs +++ b/src/System.Waf/Samples/BookLibrary/BookLibrary.Library.Applications/Services/IEntityService.cs @@ -7,4 +7,10 @@ public interface IEntityService ObservableCollection Books { get; } ObservableCollection Persons { get; } + + Task LoadBooks(CancellationToken cancellation = default); + + Task LoadPersons(CancellationToken cancellation = default); + + Task SaveChanges(CancellationToken cancellation = default); }