// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using osu.Framework.Platform; namespace osu.Game.Database { /// /// A typed store which supports basic addition, deletion and updating for soft-deletable models. /// /// The databased model. public abstract class MutableDatabaseBackedStore : DatabaseBackedStore where T : class, IHasPrimaryKey, ISoftDelete { /// /// Fired when an item was added or updated. /// public event Action ItemUpdated; /// /// Fired when an item was removed. /// public event Action ItemRemoved; protected MutableDatabaseBackedStore(IDatabaseContextFactory contextFactory, Storage storage = null) : base(contextFactory, storage) { } /// /// Access items pre-populated with includes for consumption. /// public IQueryable ConsumableItems => AddIncludesForConsumption(ContextFactory.Get().Set()); /// /// Add a to the database. /// /// The item to add. public void Add(T item) { using (var usage = ContextFactory.GetForWrite()) { var context = usage.Context; context.Attach(item); } ItemUpdated?.Invoke(item); } /// /// Update a in the database. /// /// The item to update. public void Update(T item) { using (var usage = ContextFactory.GetForWrite()) usage.Context.Update(item); ItemUpdated?.Invoke(item); } /// /// Delete a from the database. /// /// The item to delete. public bool Delete(T item) { using (ContextFactory.GetForWrite()) { Refresh(ref item); if (item.DeletePending) return false; item.DeletePending = true; } ItemRemoved?.Invoke(item); return true; } /// /// Restore a from a deleted state. /// /// The item to undelete. public bool Undelete(T item) { using (ContextFactory.GetForWrite()) { Refresh(ref item, ConsumableItems); if (!item.DeletePending) return false; item.DeletePending = false; } ItemUpdated?.Invoke(item); return true; } /// /// Allow implementations to add database-side includes or constraints when querying for consumption of items. /// /// The input query. /// A potentially modified output query. protected virtual IQueryable AddIncludesForConsumption(IQueryable query) => query; /// /// Allow implementations to add database-side includes or constraints when deleting items. /// Included properties could then be subsequently deleted by overriding . /// /// The input query. /// A potentially modified output query. protected virtual IQueryable AddIncludesForDeletion(IQueryable query) => query; /// /// Called when removing an item completely from the database. /// /// The items to be purged. /// The write context which can be used to perform subsequent deletions. protected virtual void Purge(List items, OsuDbContext context) => context.RemoveRange(items); public override void Cleanup() { base.Cleanup(); PurgeDeletable(); } /// /// Purge items in a pending delete state. /// /// An optional query limiting the scope of the purge. public void PurgeDeletable(Expression> query = null) { using (var usage = ContextFactory.GetForWrite()) { var context = usage.Context; var lookup = context.Set().Where(s => s.DeletePending); if (query != null) lookup = lookup.Where(query); lookup = AddIncludesForDeletion(lookup); var purgeable = lookup.ToList(); if (!purgeable.Any()) return; Purge(purgeable, context); } } } }