1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-15 10:02:59 +08:00

DatabaseWriteUsage

This commit is contained in:
Dean Herbert 2018-02-12 17:55:11 +09:00
parent cc948d688f
commit edc3638175
14 changed files with 385 additions and 354 deletions

View File

@ -63,12 +63,10 @@ namespace osu.Game.Tests.Visual
var storage = new TestStorage(@"TestCasePlaySongSelect");
// this is by no means clean. should be replacing inside of OsuGameBase somehow.
var context = new OsuDbContext();
DatabaseContextFactory factory = new SingletonContextFactory(new OsuDbContext());
OsuDbContext contextFactory() => context;
dependencies.Cache(rulesets = new RulesetStore(contextFactory));
dependencies.Cache(manager = new BeatmapManager(storage, contextFactory, rulesets, null)
dependencies.Cache(rulesets = new RulesetStore(factory));
dependencies.Cache(manager = new BeatmapManager(storage, factory, rulesets, null)
{
DefaultBeatmap = defaultBeatmap = game.Beatmap.Default
});

View File

@ -60,7 +60,7 @@ namespace osu.Game.Beatmaps
/// </summary>
public WorkingBeatmap DefaultBeatmap { private get; set; }
private readonly Func<OsuDbContext> createContext;
private readonly DatabaseContextFactory contextFactory;
private readonly FileStore files;
@ -85,29 +85,18 @@ namespace osu.Game.Beatmaps
/// </summary>
public Func<Storage> GetStableStorage { private get; set; }
private void refreshImportContext()
public BeatmapManager(Storage storage, DatabaseContextFactory contextFactory, RulesetStore rulesets, APIAccess api, IIpcHost importHost = null)
{
lock (importContextLock)
{
importContext?.Value?.Dispose();
this.contextFactory = contextFactory;
importContext = new Lazy<OsuDbContext>(() =>
{
var c = createContext();
c.Database.AutoTransactionsEnabled = false;
return c;
});
}
}
beatmaps = new BeatmapStore(contextFactory);
public BeatmapManager(Storage storage, Func<OsuDbContext> context, RulesetStore rulesets, APIAccess api, IIpcHost importHost = null)
{
createContext = context;
beatmaps.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s);
beatmaps.BeatmapSetRemoved += s => BeatmapSetRemoved?.Invoke(s);
beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b);
beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b);
refreshImportContext();
beatmaps = getBeatmapStoreWithContext(context);
files = new FileStore(context, storage);
files = new FileStore(contextFactory, storage);
this.rulesets = rulesets;
this.api = api;
@ -170,7 +159,6 @@ namespace osu.Game.Beatmaps
{
e = e.InnerException ?? e;
Logger.Error(e, $@"Could not import beatmap set ({Path.GetFileName(path)})");
refreshImportContext();
}
}
@ -178,21 +166,13 @@ namespace osu.Game.Beatmaps
return imported;
}
private readonly object importContextLock = new object();
private Lazy<OsuDbContext> importContext;
/// <summary>
/// Import a beatmap from an <see cref="ArchiveReader"/>.
/// </summary>
/// <param name="archive">The beatmap to be imported.</param>
public BeatmapSetInfo Import(ArchiveReader archive)
{
// let's only allow one concurrent import at a time for now
lock (importContextLock)
{
var context = importContext.Value;
using (var transaction = context.BeginTransaction())
using ( contextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes.
{
// create a new set info (don't yet add to database)
var beatmapSet = createBeatmapSetInfo(archive);
@ -201,7 +181,7 @@ namespace osu.Game.Beatmaps
var existingHashMatch = beatmaps.BeatmapSets.FirstOrDefault(b => b.Hash == beatmapSet.Hash);
if (existingHashMatch != null)
{
undelete(beatmaps, files, existingHashMatch);
undelete(existingHashMatch);
return existingHashMatch;
}
@ -218,7 +198,7 @@ namespace osu.Game.Beatmaps
}
}
beatmapSet.Files = createFileInfos(archive, getFileStoreWithContext(context));
beatmapSet.Files = createFileInfos(archive, files);
beatmapSet.Beatmaps = createBeatmapDifficulties(archive);
// remove metadata from difficulties where it matches the set
@ -227,31 +207,16 @@ namespace osu.Game.Beatmaps
b.Metadata = null;
// import to beatmap store
import(beatmapSet, context);
context.SaveChanges(transaction);
Import(beatmapSet);
return beatmapSet;
}
}
}
/// <summary>
/// Import a beatmap from a <see cref="BeatmapSetInfo"/>.
/// </summary>
/// <param name="beatmapSetInfo">The beatmap to be imported.</param>
public void Import(BeatmapSetInfo beatmapSetInfo)
{
lock (importContextLock)
{
var context = importContext.Value;
using (var transaction = context.BeginTransaction())
{
import(beatmapSetInfo, context);
context.SaveChanges(transaction);
}
}
}
/// <param name="beatmapSet">The beatmap to be imported.</param>
public void Import(BeatmapSetInfo beatmapSet) => beatmaps.Add(beatmapSet);
/// <summary>
/// Downloads a beatmap.
@ -350,26 +315,22 @@ namespace osu.Game.Beatmaps
/// <param name="beatmapSet">The beatmap set to delete.</param>
public void Delete(BeatmapSetInfo beatmapSet)
{
lock (importContextLock)
using (var db = contextFactory.GetForWrite())
{
var context = importContext.Value;
var context = db.Context;
using (var transaction = context.BeginTransaction())
{
context.ChangeTracker.AutoDetectChangesEnabled = false;
// re-fetch the beatmap set on the import context.
beatmapSet = context.BeatmapSetInfo.Include(s => s.Files).ThenInclude(f => f.FileInfo).First(s => s.ID == beatmapSet.ID);
if (getBeatmapStoreWithContext(context).Delete(beatmapSet))
if (beatmaps.Delete(beatmapSet))
{
if (!beatmapSet.Protected)
getFileStoreWithContext(context).Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray());
files.Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray());
}
context.ChangeTracker.AutoDetectChangesEnabled = true;
context.SaveChanges(transaction);
}
}
}
@ -417,19 +378,11 @@ namespace osu.Game.Beatmaps
if (beatmapSet.Protected)
return;
lock (importContextLock)
using (var db = contextFactory.GetForWrite())
{
var context = importContext.Value;
using (var transaction = context.BeginTransaction())
{
context.ChangeTracker.AutoDetectChangesEnabled = false;
undelete(getBeatmapStoreWithContext(context), getFileStoreWithContext(context), beatmapSet);
context.ChangeTracker.AutoDetectChangesEnabled = true;
context.SaveChanges(transaction);
}
db.Context.ChangeTracker.AutoDetectChangesEnabled = false;
undelete(beatmapSet);
db.Context.ChangeTracker.AutoDetectChangesEnabled = true;
}
}
@ -452,7 +405,7 @@ namespace osu.Game.Beatmaps
/// <param name="beatmaps">The store to restore beatmaps from.</param>
/// <param name="files">The store to restore beatmap files from.</param>
/// <param name="beatmapSet">The beatmap to restore.</param>
private void undelete(BeatmapStore beatmaps, FileStore files, BeatmapSetInfo beatmapSet)
private void undelete(BeatmapSetInfo beatmapSet)
{
if (!beatmaps.Undelete(beatmapSet)) return;
@ -578,11 +531,6 @@ namespace osu.Game.Beatmaps
notification.State = ProgressNotificationState.Completed;
}
/// <summary>
/// Import a <see cref="BeatmapSetInfo"/> into the beatmap store.
/// </summary>
private void import(BeatmapSetInfo beatmapSet, OsuDbContext context) => getBeatmapStoreWithContext(context).Add(beatmapSet);
/// <summary>
/// Creates an <see cref="ArchiveReader"/> from a valid storage path.
/// </summary>
@ -689,19 +637,5 @@ namespace osu.Game.Beatmaps
return beatmapInfos;
}
private FileStore getFileStoreWithContext(OsuDbContext context) => new FileStore(() => context, files.Storage);
private BeatmapStore getBeatmapStoreWithContext(OsuDbContext context) => getBeatmapStoreWithContext(() => context);
private BeatmapStore getBeatmapStoreWithContext(Func<OsuDbContext> context)
{
var store = new BeatmapStore(context);
store.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s);
store.BeatmapSetRemoved += s => BeatmapSetRemoved?.Invoke(s);
store.BeatmapHidden += b => BeatmapHidden?.Invoke(b);
store.BeatmapRestored += b => BeatmapRestored?.Invoke(b);
return store;
}
}
}

View File

@ -20,7 +20,7 @@ namespace osu.Game.Beatmaps
public event Action<BeatmapInfo> BeatmapHidden;
public event Action<BeatmapInfo> BeatmapRestored;
public BeatmapStore(Func<OsuDbContext> factory)
public BeatmapStore(DatabaseContextFactory factory)
: base(factory)
{
}
@ -31,7 +31,9 @@ namespace osu.Game.Beatmaps
/// <param name="beatmapSet">The beatmap to add.</param>
public void Add(BeatmapSetInfo beatmapSet)
{
var context = GetContext();
using (var db = ContextFactory.GetForWrite())
{
var context = db.Context;
foreach (var beatmap in beatmapSet.Beatmaps.Where(b => b.Metadata != null))
{
@ -46,10 +48,9 @@ namespace osu.Game.Beatmaps
}
context.BeatmapSetInfo.Attach(beatmapSet);
context.SaveChanges();
BeatmapSetAdded?.Invoke(beatmapSet);
}
}
/// <summary>
/// Update a <see cref="BeatmapSetInfo"/> in the database. TODO: This only supports very basic updates currently.
@ -59,10 +60,8 @@ namespace osu.Game.Beatmaps
{
BeatmapSetRemoved?.Invoke(beatmapSet);
var context = GetContext();
context.BeatmapSetInfo.Update(beatmapSet);
context.SaveChanges();
using (var usage = ContextFactory.GetForWrite())
usage.Context.BeatmapSetInfo.Update(beatmapSet);
BeatmapSetAdded?.Invoke(beatmapSet);
}
@ -74,13 +73,13 @@ namespace osu.Game.Beatmaps
/// <returns>Whether the beatmap's <see cref="BeatmapSetInfo.DeletePending"/> was changed.</returns>
public bool Delete(BeatmapSetInfo beatmapSet)
{
var context = GetContext();
using ( ContextFactory.GetForWrite())
{
Refresh(ref beatmapSet, BeatmapSets);
if (beatmapSet.DeletePending) return false;
beatmapSet.DeletePending = true;
context.SaveChanges();
}
BeatmapSetRemoved?.Invoke(beatmapSet);
return true;
@ -93,13 +92,13 @@ namespace osu.Game.Beatmaps
/// <returns>Whether the beatmap's <see cref="BeatmapSetInfo.DeletePending"/> was changed.</returns>
public bool Undelete(BeatmapSetInfo beatmapSet)
{
var context = GetContext();
using ( ContextFactory.GetForWrite())
{
Refresh(ref beatmapSet, BeatmapSets);
if (!beatmapSet.DeletePending) return false;
beatmapSet.DeletePending = false;
context.SaveChanges();
}
BeatmapSetAdded?.Invoke(beatmapSet);
return true;
@ -112,15 +111,16 @@ namespace osu.Game.Beatmaps
/// <returns>Whether the beatmap's <see cref="BeatmapInfo.Hidden"/> was changed.</returns>
public bool Hide(BeatmapInfo beatmap)
{
var context = GetContext();
using (ContextFactory.GetForWrite())
{
Refresh(ref beatmap, Beatmaps);
if (beatmap.Hidden) return false;
beatmap.Hidden = true;
context.SaveChanges();
BeatmapHidden?.Invoke(beatmap);
}
return true;
}
@ -131,13 +131,13 @@ namespace osu.Game.Beatmaps
/// <returns>Whether the beatmap's <see cref="BeatmapInfo.Hidden"/> was changed.</returns>
public bool Restore(BeatmapInfo beatmap)
{
var context = GetContext();
using (ContextFactory.GetForWrite())
{
Refresh(ref beatmap, Beatmaps);
if (!beatmap.Hidden) return false;
beatmap.Hidden = false;
context.SaveChanges();
}
BeatmapRestored?.Invoke(beatmap);
return true;
@ -147,7 +147,9 @@ namespace osu.Game.Beatmaps
public void Cleanup(Expression<Func<BeatmapSetInfo, bool>> query)
{
var context = GetContext();
using (var usage = ContextFactory.GetForWrite())
{
var context = usage.Context;
var purgeable = context.BeatmapSetInfo.Where(s => s.DeletePending && !s.Protected)
.Where(query)
@ -164,17 +166,17 @@ namespace osu.Game.Beatmaps
// cascades down to beatmaps.
context.BeatmapSetInfo.RemoveRange(purgeable);
context.SaveChanges();
}
}
public IQueryable<BeatmapSetInfo> BeatmapSets => GetContext().BeatmapSetInfo
public IQueryable<BeatmapSetInfo> BeatmapSets => ContextFactory.Get().BeatmapSetInfo
.Include(s => s.Metadata)
.Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset)
.Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
.Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
.Include(s => s.Files).ThenInclude(f => f.FileInfo);
public IQueryable<BeatmapInfo> Beatmaps => GetContext().BeatmapInfo
public IQueryable<BeatmapInfo> Beatmaps => ContextFactory.Get().BeatmapInfo
.Include(b => b.BeatmapSet).ThenInclude(s => s.Metadata)
.Include(b => b.BeatmapSet).ThenInclude(s => s.Files).ThenInclude(f => f.FileInfo)
.Include(b => b.Metadata)

View File

@ -12,8 +12,8 @@ namespace osu.Game.Configuration
{
public event Action SettingChanged;
public SettingsStore(Func<OsuDbContext> createContext)
: base(createContext)
public SettingsStore(DatabaseContextFactory contextFactory)
: base(contextFactory)
{
}
@ -24,19 +24,16 @@ namespace osu.Game.Configuration
/// <param name="variant">An optional variant.</param>
/// <returns></returns>
public List<DatabasedSetting> Query(int? rulesetId = null, int? variant = null) =>
GetContext().DatabasedSetting.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList();
ContextFactory.Get().DatabasedSetting.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList();
public void Update(DatabasedSetting setting)
{
var context = GetContext();
using (ContextFactory.GetForWrite())
{
var newValue = setting.Value;
Refresh(ref setting);
setting.Value = newValue;
context.SaveChanges();
}
SettingChanged?.Invoke();
}

View File

@ -1,10 +1,8 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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;
@ -17,9 +15,7 @@ namespace osu.Game.Database
/// <summary>
/// Create a new <see cref="OsuDbContext"/> instance (separate from the shared context via <see cref="GetContext"/> for performing isolated operations.
/// </summary>
protected readonly Func<OsuDbContext> CreateContext;
private readonly ThreadLocal<OsuDbContext> queryContext;
protected readonly DatabaseContextFactory ContextFactory;
/// <summary>
/// Refresh an instance potentially from a different thread with a local context-tracked instance.
@ -29,7 +25,9 @@ namespace osu.Game.Database
/// <typeparam name="T">A valid EF-stored type.</typeparam>
protected virtual void Refresh<T>(ref T obj, IEnumerable<T> lookupSource = null) where T : class, IHasPrimaryKey
{
var context = GetContext();
using (var usage = ContextFactory.GetForWrite())
{
var context = usage.Context;
if (context.Entry(obj).State != EntityState.Detached) return;
@ -43,19 +41,11 @@ namespace osu.Game.Database
else
context.Add(obj);
}
}
/// <summary>
/// Retrieve a shared context for performing lookups (or write operations on the update thread, for now).
/// </summary>
protected OsuDbContext GetContext() => queryContext.Value;
protected DatabaseBackedStore(Func<OsuDbContext> createContext, Storage storage = null)
protected DatabaseBackedStore(DatabaseContextFactory contextFactory, 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<OsuDbContext>(CreateContext);
ContextFactory = contextFactory;
Storage = storage;
}

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Threading;
using osu.Framework.Platform;
namespace osu.Game.Database
@ -11,17 +12,70 @@ namespace osu.Game.Database
private const string database_name = @"client";
private ThreadLocal<OsuDbContext> threadContexts;
private readonly object writeLock = new object();
private OsuDbContext writeContext;
private volatile int currentWriteUsages;
public DatabaseContextFactory(GameHost host)
{
this.host = host;
recycleThreadContexts();
}
public OsuDbContext GetContext() => new OsuDbContext(host.Storage.GetDatabaseConnectionString(database_name));
/// <summary>
/// Get a context for read-only usage.
/// </summary>
public OsuDbContext Get() => threadContexts.Value;
/// <summary>
/// Request a context for write usage. Can be consumed in a nested fashion (and will return the same underlying context).
/// This method may block if a write is already active on a different thread.
/// </summary>
/// <returns>A usage containing a usable context.</returns>
public DatabaseWriteUsage GetForWrite()
{
lock (writeLock)
{
var usage = new DatabaseWriteUsage(writeContext ?? (writeContext = threadContexts.Value), usageCompleted);
Interlocked.Increment(ref currentWriteUsages);
return usage;
}
}
private void usageCompleted(DatabaseWriteUsage usage)
{
int usages = Interlocked.Decrement(ref currentWriteUsages);
if (usages == 0)
{
writeContext.Dispose();
writeContext = null;
// once all writes are complete, we want to refresh thread-specific contexts to make sure they don't have stale local caches.
recycleThreadContexts();
}
}
private void recycleThreadContexts() => threadContexts = new ThreadLocal<OsuDbContext>(CreateContext);
protected virtual OsuDbContext CreateContext()
{
var ctx = new OsuDbContext(host.Storage.GetDatabaseConnectionString(database_name));
ctx.Database.AutoTransactionsEnabled = false;
return ctx;
}
public void ResetDatabase()
{
// todo: we probably want to make sure there are no active contexts before performing this operation.
lock (writeLock)
{
recycleThreadContexts();
host.Storage.DeleteDatabase(database_name);
}
}
}
}

View File

@ -0,0 +1,28 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using Microsoft.EntityFrameworkCore.Storage;
namespace osu.Game.Database
{
public class DatabaseWriteUsage : IDisposable
{
public readonly OsuDbContext Context;
private readonly IDbContextTransaction transaction;
private readonly Action<DatabaseWriteUsage> usageCompleted;
public DatabaseWriteUsage(OsuDbContext context, Action<DatabaseWriteUsage> onCompleted)
{
Context = context;
transaction = Context.BeginTransaction();
usageCompleted = onCompleted;
}
public void Dispose()
{
Context.SaveChanges(transaction);
usageCompleted?.Invoke(this);
}
}
}

View File

@ -0,0 +1,21 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Database
{
public class SingletonContextFactory : DatabaseContextFactory
{
private readonly OsuDbContext context;
public SingletonContextFactory(OsuDbContext context)
: base(null)
{
this.context = context;
}
protected override OsuDbContext CreateContext()
{
return context;
}
}
}

View File

@ -21,14 +21,16 @@ namespace osu.Game.IO
public new Storage Storage => base.Storage;
public FileStore(Func<OsuDbContext> createContext, Storage storage) : base(createContext, storage.GetStorageForDirectory(@"files"))
public FileStore(DatabaseContextFactory contextFactory, Storage storage) : base(contextFactory, storage.GetStorageForDirectory(@"files"))
{
Store = new StorageBackedResourceStore(Storage);
}
public FileInfo Add(Stream data, bool reference = true)
{
var context = GetContext();
using (var usage = ContextFactory.GetForWrite())
{
var context = usage.Context;
string hash = data.ComputeSHA2Hash();
@ -54,38 +56,42 @@ namespace osu.Game.IO
return info;
}
}
public void Reference(params FileInfo[] files) => reference(GetContext(), files);
private void reference(OsuDbContext context, FileInfo[] files)
public void Reference(params FileInfo[] files)
{
using (var usage = ContextFactory.GetForWrite())
{
var context = usage.Context;
foreach (var f in files.GroupBy(f => f.ID))
{
var refetch = context.Find<FileInfo>(f.First().ID) ?? f.First();
refetch.ReferenceCount += f.Count();
context.FileInfo.Update(refetch);
}
context.SaveChanges();
}
}
public void Dereference(params FileInfo[] files) => dereference(GetContext(), files);
private void dereference(OsuDbContext context, FileInfo[] files)
public void Dereference(params FileInfo[] files)
{
using (var usage = ContextFactory.GetForWrite())
{
var context = usage.Context;
foreach (var f in files.GroupBy(f => f.ID))
{
var refetch = context.FileInfo.Find(f.Key);
refetch.ReferenceCount -= f.Count();
context.FileInfo.Update(refetch);
}
context.SaveChanges();
}
}
public override void Cleanup()
{
var context = GetContext();
using (var usage = ContextFactory.GetForWrite())
{
var context = usage.Context;
foreach (var f in context.FileInfo.Where(f => f.ReferenceCount < 1))
{
@ -99,8 +105,7 @@ namespace osu.Game.IO
Logger.Error(e, $@"Could not delete beatmap {f}");
}
}
context.SaveChanges();
}
}
}
}

View File

@ -16,8 +16,10 @@ namespace osu.Game.Input
{
public event Action KeyBindingChanged;
public KeyBindingStore(Func<OsuDbContext> createContext, RulesetStore rulesets, Storage storage = null)
: base(createContext, storage)
public KeyBindingStore(DatabaseContextFactory contextFactory, RulesetStore rulesets, Storage storage = null)
: base(contextFactory, storage)
{
using (ContextFactory.GetForWrite())
{
foreach (var info in rulesets.AvailableRulesets)
{
@ -26,15 +28,16 @@ namespace osu.Game.Input
insertDefaults(ruleset.GetDefaultKeyBindings(variant), info.ID, variant);
}
}
}
public void Register(KeyBindingContainer manager) => insertDefaults(manager.DefaultKeyBindings);
private void insertDefaults(IEnumerable<KeyBinding> defaults, int? rulesetId = null, int? variant = null)
{
var context = GetContext();
using (var transaction = context.BeginTransaction())
using (var usage = ContextFactory.GetForWrite())
{
var context = usage.Context;
// compare counts in database vs defaults
foreach (var group in defaults.GroupBy(k => k.Action))
{
@ -54,8 +57,6 @@ namespace osu.Game.Input
Variant = variant
});
}
context.SaveChanges(transaction);
}
}
@ -66,19 +67,16 @@ namespace osu.Game.Input
/// <param name="variant">An optional variant.</param>
/// <returns></returns>
public List<DatabasedKeyBinding> Query(int? rulesetId = null, int? variant = null) =>
GetContext().DatabasedKeyBinding.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList();
ContextFactory.Get().DatabasedKeyBinding.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList();
public void Update(KeyBinding keyBinding)
{
using (ContextFactory.GetForWrite())
{
var dbKeyBinding = (DatabasedKeyBinding)keyBinding;
var context = GetContext();
Refresh(ref dbKeyBinding);
dbKeyBinding.KeyCombination = keyBinding.KeyCombination;
context.SaveChanges();
}
KeyBindingChanged?.Invoke();
}

View File

@ -106,12 +106,12 @@ namespace osu.Game
Token = LocalConfig.Get<string>(OsuSetting.Token)
});
dependencies.Cache(RulesetStore = new RulesetStore(contextFactory.GetContext));
dependencies.Cache(FileStore = new FileStore(contextFactory.GetContext, Host.Storage));
dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory.GetContext, RulesetStore, API, Host));
dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, contextFactory.GetContext, Host, BeatmapManager, RulesetStore));
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory.GetContext, RulesetStore));
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory.GetContext));
dependencies.Cache(RulesetStore = new RulesetStore(contextFactory));
dependencies.Cache(FileStore = new FileStore(contextFactory, Host.Storage));
dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, API, Host));
dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, contextFactory, Host, BeatmapManager, RulesetStore));
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore));
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory));
dependencies.Cache(new OsuColour());
//this completely overrides the framework default. will need to change once we make a proper FontStore.
@ -179,8 +179,8 @@ namespace osu.Game
{
try
{
using (var context = contextFactory.GetContext())
context.Migrate();
using (var db = contextFactory.GetForWrite())
db.Context.Migrate();
}
catch (MigrationFailedException e)
{
@ -191,8 +191,8 @@ namespace osu.Game
contextFactory.ResetDatabase();
Logger.Log("Database purged successfully.", LoggingTarget.Database, LogLevel.Important);
using (var context = contextFactory.GetContext())
context.Migrate();
using (var db = contextFactory.GetForWrite())
db.Context.Migrate();
}
}

View File

@ -25,7 +25,7 @@ namespace osu.Game.Rulesets
loadRulesetFromFile(file);
}
public RulesetStore(Func<OsuDbContext> factory)
public RulesetStore(DatabaseContextFactory factory)
: base(factory)
{
AddMissingRulesets();
@ -56,7 +56,9 @@ namespace osu.Game.Rulesets
protected void AddMissingRulesets()
{
var context = GetContext();
using (var usage = ContextFactory.GetForWrite())
{
var context = usage.Context;
var instances = loaded_assemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, (RulesetInfo)null)).ToList();
@ -98,6 +100,7 @@ namespace osu.Game.Rulesets
AvailableRulesets = context.RulesetInfo.Where(r => r.Available).ToList();
}
}
private static void loadRulesetFromFile(string file)
{

View File

@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.IO;
using osu.Framework.Platform;
@ -27,7 +26,7 @@ namespace osu.Game.Rulesets.Scoring
// ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised)
private ScoreIPCChannel ipc;
public ScoreStore(Storage storage, Func<OsuDbContext> factory, IIpcHost importHost = null, BeatmapManager beatmaps = null, RulesetStore rulesets = null) : base(factory)
public ScoreStore(Storage storage, DatabaseContextFactory factory, IIpcHost importHost = null, BeatmapManager beatmaps = null, RulesetStore rulesets = null) : base(factory)
{
this.storage = storage;
this.beatmaps = beatmaps;

View File

@ -275,7 +275,9 @@
<Compile Include="Configuration\DatabasedConfigManager.cs" />
<Compile Include="Configuration\SpeedChangeVisualisationMethod.cs" />
<Compile Include="Database\DatabaseContextFactory.cs" />
<Compile Include="Database\DatabaseWriteUsage.cs" />
<Compile Include="Database\IHasPrimaryKey.cs" />
<Compile Include="Database\SingletonContextFactory.cs" />
<Compile Include="Graphics\Containers\LinkFlowContainer.cs" />
<Compile Include="Graphics\Textures\LargeTextureStore.cs" />
<Compile Include="Online\API\Requests\GetUserRequest.cs" />