From b2cf837de7b099d41bbb98d1881491f5bb959bea Mon Sep 17 00:00:00 2001 From: Robert McRackan Date: Thu, 15 Dec 2022 14:11:27 -0500 Subject: [PATCH] Hangover. WinForms. Restore deleted books --- Source/ApplicationServices/LibraryCommands.cs | 40 ++++++-- .../QueryObjects/LibraryBookQueries.cs | 13 +++ .../Views/MainWindow.axaml.cs | 7 +- Source/HangoverWinForms/Form1.CLI.cs | 2 +- Source/HangoverWinForms/Form1.Deleted.cs | 73 ++++++++++++++ Source/HangoverWinForms/Form1.Designer.cs | 98 +++++++++++++++++++ Source/HangoverWinForms/Form1.cs | 10 +- .../LibationWinForms/GridView/ProductsGrid.cs | 3 +- 8 files changed, 235 insertions(+), 11 deletions(-) create mode 100644 Source/HangoverWinForms/Form1.Deleted.cs diff --git a/Source/ApplicationServices/LibraryCommands.cs b/Source/ApplicationServices/LibraryCommands.cs index 2da39dab..463e5cf2 100644 --- a/Source/ApplicationServices/LibraryCommands.cs +++ b/Source/ApplicationServices/LibraryCommands.cs @@ -9,7 +9,6 @@ using Dinah.Core; using DtoImporterService; using LibationFileManager; using Serilog; -using static System.Reflection.Metadata.BlobBuilder; using static DtoImporterService.PerfLogger; namespace ApplicationServices @@ -336,7 +335,7 @@ namespace ApplicationServices } #endregion - #region remove books + #region remove/restore books public static Task RemoveBooksAsync(List idsToRemove) => Task.Run(() => removeBooks(idsToRemove)); public static int RemoveBook(string idToRemove) => removeBooks(new() { idToRemove }); private static int removeBooks(List idsToRemove) @@ -368,11 +367,40 @@ namespace ApplicationServices Log.Logger.Error(ex, "Error removing books"); throw; } - } - #endregion + } - // call this whenever books are added or removed from library - private static void finalizeLibrarySizeChange() => LibrarySizeChanged?.Invoke(null, null); + public static int RestoreBooks(this List libraryBooks) + { + try + { + if (libraryBooks is null || !libraryBooks.Any()) + return 0; + + using var context = DbContexts.GetContext(); + + // Attach() NoTracking entities before SaveChanges() + foreach (var lb in libraryBooks) + { + lb.IsDeleted = false; + context.Attach(lb).State = Microsoft.EntityFrameworkCore.EntityState.Modified; + } + + var qtyChanges = context.SaveChanges(); + if (qtyChanges > 0) + finalizeLibrarySizeChange(); + + return qtyChanges; + } + catch (Exception ex) + { + Log.Logger.Error(ex, "Error restoring books"); + throw; + } + } + #endregion + + // call this whenever books are added or removed from library + private static void finalizeLibrarySizeChange() => LibrarySizeChanged?.Invoke(null, null); /// Occurs when the size of the library changes. ie: books are added or removed public static event EventHandler LibrarySizeChanged; diff --git a/Source/DataLayer/QueryObjects/LibraryBookQueries.cs b/Source/DataLayer/QueryObjects/LibraryBookQueries.cs index c8665ca8..24063f0c 100644 --- a/Source/DataLayer/QueryObjects/LibraryBookQueries.cs +++ b/Source/DataLayer/QueryObjects/LibraryBookQueries.cs @@ -39,6 +39,19 @@ namespace DataLayer public static IQueryable GetLibrary(this IQueryable library) => library .Where(lb => !lb.IsDeleted) + .getLibrary(); + + public static List GetDeletedLibraryBooks(this LibationContext context) + => context + .LibraryBooks + .AsNoTrackingWithIdentityResolution() + .Where(lb => lb.IsDeleted) + .getLibrary() + .ToList(); + + /// This is still IQueryable. YOU MUST CALL ToList() YOURSELF + private static IQueryable getLibrary(this IQueryable library) + => library // owned items are always loaded. eg: book.UserDefinedItem, book.Supplements .Include(le => le.Book).ThenInclude(b => b.SeriesLink).ThenInclude(sb => sb.Series) .Include(le => le.Book).ThenInclude(b => b.ContributorsLink).ThenInclude(c => c.Contributor) diff --git a/Source/HangoverAvalonia/Views/MainWindow.axaml.cs b/Source/HangoverAvalonia/Views/MainWindow.axaml.cs index 60499471..ca6c94d3 100644 --- a/Source/HangoverAvalonia/Views/MainWindow.axaml.cs +++ b/Source/HangoverAvalonia/Views/MainWindow.axaml.cs @@ -1,3 +1,4 @@ +using AppScaffolding; using Avalonia.Controls; using HangoverAvalonia.ViewModels; @@ -9,7 +10,11 @@ namespace HangoverAvalonia.Views public MainWindow() { InitializeComponent(); - } + + var config = LibationScaffolding.RunPreConfigMigrations(); + LibationScaffolding.RunPostConfigMigrations(config); + LibationScaffolding.RunPostMigrationScaffolding(config); + } public void Execute_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) { diff --git a/Source/HangoverWinForms/Form1.CLI.cs b/Source/HangoverWinForms/Form1.CLI.cs index bb3cb12c..abc1022d 100644 --- a/Source/HangoverWinForms/Form1.CLI.cs +++ b/Source/HangoverWinForms/Form1.CLI.cs @@ -11,7 +11,7 @@ namespace HangoverWinForms private void cliTab_VisibleChanged(object sender, EventArgs e) { - if (!databaseTab.Visible) + if (!cliTab.Visible) return; } } diff --git a/Source/HangoverWinForms/Form1.Deleted.cs b/Source/HangoverWinForms/Form1.Deleted.cs new file mode 100644 index 00000000..0419d827 --- /dev/null +++ b/Source/HangoverWinForms/Form1.Deleted.cs @@ -0,0 +1,73 @@ +using ApplicationServices; +using DataLayer; + +namespace HangoverWinForms +{ + public partial class Form1 + { + private string deletedCheckedTemplate; + + private void Load_deletedTab() + { + deletedCheckedTemplate = deletedCheckedLbl.Text; + } + + private void deletedTab_VisibleChanged(object sender, EventArgs e) + { + if (!deletedTab.Visible) + return; + + if (deletedCbl.Items.Count == 0) + reload(); + } + + private void deletedCbl_ItemCheck(object sender, ItemCheckEventArgs e) + { + // CheckedItems.Count is not updated until after the event fires + setLabel(e.NewValue); + } + + private void checkAllBtn_Click(object sender, EventArgs e) + { + for (var i = 0; i < deletedCbl.Items.Count; i++) + deletedCbl.SetItemChecked(i, true); + } + + private void uncheckAllBtn_Click(object sender, EventArgs e) + { + for (var i = 0; i < deletedCbl.Items.Count; i++) + deletedCbl.SetItemChecked(i, false); + } + + private void saveBtn_Click(object sender, EventArgs e) + { + var libraryBooksToRestore = deletedCbl.CheckedItems.Cast().ToList(); + var qtyChanges = libraryBooksToRestore.RestoreBooks(); + if (qtyChanges > 0) + reload(); + } + + private void reload() + { + deletedCbl.Items.Clear(); + var deletedBooks = DbContexts.GetContext().GetDeletedLibraryBooks(); + foreach (var lb in deletedBooks) + deletedCbl.Items.Add(lb); + + setLabel(); + } + + private void setLabel(CheckState? checkedState = null) + { + var pre = deletedCbl.CheckedItems.Count; + int count = checkedState switch + { + CheckState.Checked => pre + 1, + CheckState.Unchecked => pre - 1, + _ => pre, + }; + + deletedCheckedLbl.Text = string.Format(deletedCheckedTemplate, count, deletedCbl.Items.Count); + } + } +} diff --git a/Source/HangoverWinForms/Form1.Designer.cs b/Source/HangoverWinForms/Form1.Designer.cs index 45a96525..cebd5fc2 100644 --- a/Source/HangoverWinForms/Form1.Designer.cs +++ b/Source/HangoverWinForms/Form1.Designer.cs @@ -36,14 +36,23 @@ this.sqlTb = new System.Windows.Forms.TextBox(); this.sqlLbl = new System.Windows.Forms.Label(); this.databaseFileLbl = new System.Windows.Forms.Label(); + this.deletedTab = new System.Windows.Forms.TabPage(); + this.deletedCheckedLbl = new System.Windows.Forms.Label(); + this.label1 = new System.Windows.Forms.Label(); + this.saveBtn = new System.Windows.Forms.Button(); + this.uncheckAllBtn = new System.Windows.Forms.Button(); + this.checkAllBtn = new System.Windows.Forms.Button(); + this.deletedCbl = new System.Windows.Forms.CheckedListBox(); this.cliTab = new System.Windows.Forms.TabPage(); this.tabControl1.SuspendLayout(); this.databaseTab.SuspendLayout(); + this.deletedTab.SuspendLayout(); this.SuspendLayout(); // // tabControl1 // this.tabControl1.Controls.Add(this.databaseTab); + this.tabControl1.Controls.Add(this.deletedTab); this.tabControl1.Controls.Add(this.cliTab); this.tabControl1.Dock = System.Windows.Forms.DockStyle.Fill; this.tabControl1.Location = new System.Drawing.Point(0, 0); @@ -119,6 +128,86 @@ this.databaseFileLbl.TabIndex = 0; this.databaseFileLbl.Text = "Database file: "; // + // deletedTab + // + this.deletedTab.Controls.Add(this.deletedCheckedLbl); + this.deletedTab.Controls.Add(this.label1); + this.deletedTab.Controls.Add(this.saveBtn); + this.deletedTab.Controls.Add(this.uncheckAllBtn); + this.deletedTab.Controls.Add(this.checkAllBtn); + this.deletedTab.Controls.Add(this.deletedCbl); + this.deletedTab.Location = new System.Drawing.Point(4, 24); + this.deletedTab.Name = "deletedTab"; + this.deletedTab.Padding = new System.Windows.Forms.Padding(3); + this.deletedTab.Size = new System.Drawing.Size(792, 422); + this.deletedTab.TabIndex = 2; + this.deletedTab.Text = "Deleted Books"; + this.deletedTab.UseVisualStyleBackColor = true; + // + // deletedCheckedLbl + // + this.deletedCheckedLbl.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.deletedCheckedLbl.AutoSize = true; + this.deletedCheckedLbl.Location = new System.Drawing.Point(233, 395); + this.deletedCheckedLbl.Name = "deletedCheckedLbl"; + this.deletedCheckedLbl.Size = new System.Drawing.Size(104, 15); + this.deletedCheckedLbl.TabIndex = 6; + this.deletedCheckedLbl.Text = "Checked: {0} of {1}"; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(8, 3); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(239, 15); + this.label1.TabIndex = 0; + this.label1.Text = "To restore deleted book, check box and save"; + // + // saveBtn + // + this.saveBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.saveBtn.Location = new System.Drawing.Point(709, 391); + this.saveBtn.Name = "saveBtn"; + this.saveBtn.Size = new System.Drawing.Size(75, 23); + this.saveBtn.TabIndex = 5; + this.saveBtn.Text = "Save"; + this.saveBtn.UseVisualStyleBackColor = true; + this.saveBtn.Click += new System.EventHandler(this.saveBtn_Click); + // + // uncheckAllBtn + // + this.uncheckAllBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.uncheckAllBtn.Location = new System.Drawing.Point(129, 391); + this.uncheckAllBtn.Name = "uncheckAllBtn"; + this.uncheckAllBtn.Size = new System.Drawing.Size(98, 23); + this.uncheckAllBtn.TabIndex = 4; + this.uncheckAllBtn.Text = "Uncheck All"; + this.uncheckAllBtn.UseVisualStyleBackColor = true; + this.uncheckAllBtn.Click += new System.EventHandler(this.uncheckAllBtn_Click); + // + // checkAllBtn + // + this.checkAllBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.checkAllBtn.Location = new System.Drawing.Point(8, 391); + this.checkAllBtn.Name = "checkAllBtn"; + this.checkAllBtn.Size = new System.Drawing.Size(98, 23); + this.checkAllBtn.TabIndex = 3; + this.checkAllBtn.Text = "Check All"; + this.checkAllBtn.UseVisualStyleBackColor = true; + this.checkAllBtn.Click += new System.EventHandler(this.checkAllBtn_Click); + // + // deletedCbl + // + this.deletedCbl.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.deletedCbl.FormattingEnabled = true; + this.deletedCbl.Location = new System.Drawing.Point(8, 21); + this.deletedCbl.Name = "deletedCbl"; + this.deletedCbl.Size = new System.Drawing.Size(776, 364); + this.deletedCbl.TabIndex = 2; + this.deletedCbl.ItemCheck += new System.Windows.Forms.ItemCheckEventHandler(this.deletedCbl_ItemCheck); + // // cliTab // this.cliTab.Location = new System.Drawing.Point(4, 24); @@ -140,6 +229,8 @@ this.tabControl1.ResumeLayout(false); this.databaseTab.ResumeLayout(false); this.databaseTab.PerformLayout(); + this.deletedTab.ResumeLayout(false); + this.deletedTab.PerformLayout(); this.ResumeLayout(false); } @@ -154,5 +245,12 @@ private Label sqlLbl; private Button sqlExecuteBtn; private TabPage cliTab; + private TabPage deletedTab; + private CheckedListBox deletedCbl; + private Label label1; + private Button saveBtn; + private Button uncheckAllBtn; + private Button checkAllBtn; + private Label deletedCheckedLbl; } } \ No newline at end of file diff --git a/Source/HangoverWinForms/Form1.cs b/Source/HangoverWinForms/Form1.cs index 99f746ca..23392376 100644 --- a/Source/HangoverWinForms/Form1.cs +++ b/Source/HangoverWinForms/Form1.cs @@ -1,4 +1,6 @@ -namespace HangoverWinForms +using AppScaffolding; + +namespace HangoverWinForms { public partial class Form1 : Form { @@ -6,11 +8,17 @@ { InitializeComponent(); + var config = LibationScaffolding.RunPreConfigMigrations(); + LibationScaffolding.RunPostConfigMigrations(config); + LibationScaffolding.RunPostMigrationScaffolding(config); + databaseTab.VisibleChanged += databaseTab_VisibleChanged; cliTab.VisibleChanged += cliTab_VisibleChanged; + deletedTab.VisibleChanged += deletedTab_VisibleChanged; Load_databaseTab(); Load_cliTab(); + Load_deletedTab(); } } } diff --git a/Source/LibationWinForms/GridView/ProductsGrid.cs b/Source/LibationWinForms/GridView/ProductsGrid.cs index e81b8376..0ac93377 100644 --- a/Source/LibationWinForms/GridView/ProductsGrid.cs +++ b/Source/LibationWinForms/GridView/ProductsGrid.cs @@ -383,8 +383,7 @@ namespace LibationWinForms.GridView if (visibleCount != bindingList.Count) VisibleCountChanged?.Invoke(this, bindingList.BookEntries().Count()); - - } + } #endregion