// Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; using System.Collections.Generic; using System.Linq; using System.Threading; using Microsoft.EntityFrameworkCore; using osu.Framework.Platform; namespace osu.Game.Database { public abstract class DatabaseBackedStore { protected readonly Storage Storage; /// /// Create a new instance (separate from the shared context via for performing isolated operations. /// protected readonly Func CreateContext; private readonly ThreadLocal queryContext; /// /// Refresh an instance potentially from a different thread with a local context-tracked instance. /// /// The object to use as a reference when negotiating a local instance. /// An optional lookup source which will be used to query and populate a freshly retrieved replacement. If not provided, the refreshed object will still be returned but will not have any includes. /// A valid EF-stored type. protected virtual void Refresh(ref T obj, IEnumerable lookupSource = null) where T : class, IHasPrimaryKey { var context = GetContext(); if (context.Entry(obj).State != EntityState.Detached) return; var id = obj.ID; var foundObject = lookupSource?.SingleOrDefault(t => t.ID == id) ?? context.Find(id); if (foundObject != null) { obj = foundObject; context.Entry(obj).Reload(); } else context.Add(obj); } /// /// Retrieve a shared context for performing lookups (or write operations on the update thread, for now). /// protected OsuDbContext GetContext() => queryContext.Value; protected DatabaseBackedStore(Func createContext, Storage storage = null) { CreateContext = createContext; // todo: while this seems to work quite well, we need to consider that contexts could enter a state where they are never cleaned up. queryContext = new ThreadLocal(CreateContext); Storage = storage; } /// /// Perform any common clean-up tasks. Should be run when idle, or whenever necessary. /// public virtual void Cleanup() { } } }