diff --git a/Source/ApplicationServices/DbContexts.cs b/Source/ApplicationServices/DbContexts.cs
index d8f4b487..d1dc5832 100644
--- a/Source/ApplicationServices/DbContexts.cs
+++ b/Source/ApplicationServices/DbContexts.cs
@@ -9,7 +9,7 @@ namespace ApplicationServices
{
/// Use for fully functional context, incl. SaveChanges(). For query-only, use the other method
public static LibationContext GetContext()
- => LibationContext.Create(SqliteStorage.ConnectionString);
+ => InstanceQueue.WaitToCreateInstance(() => LibationContext.Create(SqliteStorage.ConnectionString));
/// Use for full library querying. No lazy loading
public static List GetLibrary_Flat_NoTracking(bool includeParents = false)
diff --git a/Source/DataLayer/InstanceQueue.cs b/Source/DataLayer/InstanceQueue.cs
new file mode 100644
index 00000000..4bfda40a
--- /dev/null
+++ b/Source/DataLayer/InstanceQueue.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Diagnostics;
+using System.Threading;
+
+#nullable enable
+namespace DataLayer;
+
+/// Notifies clients that the object is being disposed.
+public interface INotifyDisposed : IDisposable
+{
+ /// Event raised when the object is disposed.
+ event EventHandler? ObjectDisposed;
+}
+
+/// Creates a single instance of at a time, blocking subsequent creations until the previous creations are disposed.
+public static class InstanceQueue where TDisposable : INotifyDisposed
+{
+ /// Synchronization object for access to "/>
+ private static Lock Locker { get; } = new();
+ /// Ticket holder for the last instance creator in line.
+ private static Ticket? LastInLine { get; set; }
+
+ /// Waits for all previously created instances of to be disposed, then creates and returns a new instance of using the provided factory.
+ public static TDisposable WaitToCreateInstance(Func creator)
+ {
+ Ticket ticket;
+ lock (Locker)
+ {
+ ticket = LastInLine = new Ticket(creator, LastInLine);
+ }
+
+ return ticket.Fulfill();
+ }
+
+ /// A queue ticket for an instance creator.
+ /// Factory to create a new instance of
+ /// The ticket immediately in preceding this new ticket. This new ticket must wait for to signal its instance has been disposed before creating a new instance of
+ private class Ticket(Func creator, Ticket? inFront) : IDisposable
+ {
+ /// Factory to create a new instance of
+ private Func Creator { get; } = creator;
+ /// Ticket immediately in front of this one. This instance must wait for to signal its instance has been disposed before creating a new instance of
+ private Ticket? InFront { get; } = inFront;
+ /// Wait handle to signal when this ticket's created instance is disposed
+ private EventWaitHandle WaitHandle { get; } = new(false, EventResetMode.ManualReset);
+ /// This ticket's created instance of
+ private TDisposable? CreatedInstance { get; set; }
+
+ /// Disposes of this ticket and every ticket queued in front of it.
+ public void Dispose()
+ {
+ WaitHandle.Dispose();
+ InFront?.Dispose();
+ }
+
+ ///
+ /// Waits for the ticket's instance to be disposed, then creates and returns a new instance of using the factory.
+ ///
+ public TDisposable Fulfill()
+ {
+#if DEBUG
+ var sw = Stopwatch.StartNew();
+#endif
+ //Wait for the previous ticket's instance to be disposed, then dispose of the previous ticket.
+ InFront?.WaitHandle.WaitOne(Timeout.Infinite);
+ InFront?.Dispose();
+#if DEBUG
+ sw.Stop();
+ Debug.WriteLine($"Waited {sw.ElapsedMilliseconds}ms to create instance of {typeof(TDisposable).Name}");
+#endif
+ CreatedInstance = Creator();
+ CreatedInstance.ObjectDisposed += CreatedInstance_ObjectDisposed;
+ return CreatedInstance;
+ }
+
+ private void CreatedInstance_ObjectDisposed(object? sender, EventArgs e)
+ {
+ Debug.WriteLine($"{typeof(TDisposable).Name} Disposed");
+ if (CreatedInstance is not null)
+ {
+ CreatedInstance.ObjectDisposed -= CreatedInstance_ObjectDisposed;
+ CreatedInstance = default;
+ }
+
+ lock (Locker)
+ {
+ if (this == LastInLine)
+ {
+ //There are no ticket holders waiting after this one.
+ //This ticket is fulfilled and will never be waited on.
+ LastInLine = null;
+ Dispose();
+ }
+ else
+ {
+ //Signal the that this ticket has been fulfilled so that
+ //the next ticket in line may proceed.
+ WaitHandle.Set();
+ }
+ }
+ }
+ }
+}
diff --git a/Source/DataLayer/LibationContext.cs b/Source/DataLayer/LibationContext.cs
index f4e773c3..0ed780b9 100644
--- a/Source/DataLayer/LibationContext.cs
+++ b/Source/DataLayer/LibationContext.cs
@@ -1,9 +1,11 @@
using DataLayer.Configurations;
using Microsoft.EntityFrameworkCore;
+using System;
+using System.Threading.Tasks;
namespace DataLayer
{
- public class LibationContext : DbContext
+ public class LibationContext : DbContext, INotifyDisposed
{
// IMPORTANT: USING DbSet<>
// ========================
@@ -25,6 +27,18 @@ namespace DataLayer
public DbSet Categories { get; private set; }
public DbSet CategoryLadders { get; private set; }
+ public event EventHandler ObjectDisposed;
+ public override void Dispose()
+ {
+ base.Dispose();
+ ObjectDisposed?.Invoke(this, EventArgs.Empty);
+ }
+ public override async ValueTask DisposeAsync()
+ {
+ await base.DisposeAsync();
+ ObjectDisposed?.Invoke(this, EventArgs.Empty);
+ }
+
public static LibationContext Create(string connectionString)
{
var factory = new LibationContextFactory();