mirror of
https://github.com/ppy/osu
synced 2024-12-29 02:12:43 +00:00
a16c0641b2
This reverts commitf3faad74d5
, reversing changes made to712e7bc7bf
. Several issues arose after migrating to 5.0, including, but possibly not limited to, performance regressions in song select, as well as failures when attempting to save beatmaps after metadata changes in the editor.
215 lines
7.8 KiB
C#
215 lines
7.8 KiB
C#
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
// See the LICENCE file in the repository root for full licence text.
|
|
|
|
using System;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.EntityFrameworkCore.Diagnostics;
|
|
using Microsoft.Extensions.Logging;
|
|
using osu.Framework.Logging;
|
|
using osu.Framework.Statistics;
|
|
using osu.Game.Beatmaps;
|
|
using osu.Game.Configuration;
|
|
using osu.Game.IO;
|
|
using osu.Game.Rulesets;
|
|
using osu.Game.Scoring;
|
|
using DatabasedKeyBinding = osu.Game.Input.Bindings.DatabasedKeyBinding;
|
|
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
|
|
using osu.Game.Skinning;
|
|
|
|
namespace osu.Game.Database
|
|
{
|
|
public class OsuDbContext : DbContext
|
|
{
|
|
public DbSet<BeatmapInfo> BeatmapInfo { get; set; }
|
|
public DbSet<BeatmapDifficulty> BeatmapDifficulty { get; set; }
|
|
public DbSet<BeatmapMetadata> BeatmapMetadata { get; set; }
|
|
public DbSet<BeatmapSetInfo> BeatmapSetInfo { get; set; }
|
|
public DbSet<DatabasedKeyBinding> DatabasedKeyBinding { get; set; }
|
|
public DbSet<DatabasedSetting> DatabasedSetting { get; set; }
|
|
public DbSet<FileInfo> FileInfo { get; set; }
|
|
public DbSet<RulesetInfo> RulesetInfo { get; set; }
|
|
public DbSet<SkinInfo> SkinInfo { get; set; }
|
|
public DbSet<ScoreInfo> ScoreInfo { get; set; }
|
|
|
|
private readonly string connectionString;
|
|
|
|
private static readonly Lazy<OsuDbLoggerFactory> logger = new Lazy<OsuDbLoggerFactory>(() => new OsuDbLoggerFactory());
|
|
|
|
private static readonly GlobalStatistic<int> contexts = GlobalStatistics.Get<int>("Database", "Contexts");
|
|
|
|
static OsuDbContext()
|
|
{
|
|
// required to initialise native SQLite libraries on some platforms.
|
|
SQLitePCL.Batteries_V2.Init();
|
|
|
|
// https://github.com/aspnet/EntityFrameworkCore/issues/9994#issuecomment-508588678
|
|
SQLitePCL.raw.sqlite3_config(2 /*SQLITE_CONFIG_MULTITHREAD*/);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a new in-memory OsuDbContext instance.
|
|
/// </summary>
|
|
public OsuDbContext()
|
|
: this("DataSource=:memory:")
|
|
{
|
|
// required for tooling (see https://wildermuth.com/2017/07/06/Program-cs-in-ASP-NET-Core-2-0).
|
|
|
|
Migrate();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a new OsuDbContext instance.
|
|
/// </summary>
|
|
/// <param name="connectionString">A valid SQLite connection string.</param>
|
|
public OsuDbContext(string connectionString)
|
|
{
|
|
this.connectionString = connectionString;
|
|
|
|
var connection = Database.GetDbConnection();
|
|
|
|
try
|
|
{
|
|
connection.Open();
|
|
|
|
using (var cmd = connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = "PRAGMA journal_mode=WAL;";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
connection.Close();
|
|
throw;
|
|
}
|
|
|
|
contexts.Value++;
|
|
}
|
|
|
|
~OsuDbContext()
|
|
{
|
|
// DbContext does not contain a finalizer (https://github.com/aspnet/EntityFrameworkCore/issues/8872)
|
|
// This is used to clean up previous contexts when fresh contexts are exposed via DatabaseContextFactory
|
|
Dispose();
|
|
}
|
|
|
|
private bool isDisposed;
|
|
|
|
public override void Dispose()
|
|
{
|
|
if (isDisposed) return;
|
|
|
|
isDisposed = true;
|
|
|
|
base.Dispose();
|
|
|
|
contexts.Value--;
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
|
{
|
|
base.OnConfiguring(optionsBuilder);
|
|
optionsBuilder
|
|
// this is required for the time being due to the way we are querying in places like BeatmapStore.
|
|
// if we ever move to having consumers file their own .Includes, or get eager loading support, this could be re-enabled.
|
|
.ConfigureWarnings(warnings => warnings.Ignore(CoreEventId.IncludeIgnoredWarning))
|
|
.UseSqlite(connectionString, sqliteOptions => sqliteOptions.CommandTimeout(10))
|
|
.UseLoggerFactory(logger.Value);
|
|
}
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
modelBuilder.Entity<BeatmapInfo>().HasIndex(b => b.OnlineBeatmapID).IsUnique();
|
|
modelBuilder.Entity<BeatmapInfo>().HasIndex(b => b.MD5Hash);
|
|
modelBuilder.Entity<BeatmapInfo>().HasIndex(b => b.Hash);
|
|
|
|
modelBuilder.Entity<BeatmapSetInfo>().HasIndex(b => b.OnlineBeatmapSetID).IsUnique();
|
|
modelBuilder.Entity<BeatmapSetInfo>().HasIndex(b => b.DeletePending);
|
|
modelBuilder.Entity<BeatmapSetInfo>().HasIndex(b => b.Hash).IsUnique();
|
|
|
|
modelBuilder.Entity<SkinInfo>().HasIndex(b => b.Hash).IsUnique();
|
|
modelBuilder.Entity<SkinInfo>().HasIndex(b => b.DeletePending);
|
|
|
|
modelBuilder.Entity<DatabasedKeyBinding>().HasIndex(b => new { b.RulesetID, b.Variant });
|
|
modelBuilder.Entity<DatabasedKeyBinding>().HasIndex(b => b.IntAction);
|
|
modelBuilder.Entity<DatabasedKeyBinding>().Ignore(b => b.KeyCombination);
|
|
modelBuilder.Entity<DatabasedKeyBinding>().Ignore(b => b.Action);
|
|
|
|
modelBuilder.Entity<DatabasedSetting>().HasIndex(b => new { b.RulesetID, b.Variant });
|
|
|
|
modelBuilder.Entity<FileInfo>().HasIndex(b => b.Hash).IsUnique();
|
|
modelBuilder.Entity<FileInfo>().HasIndex(b => b.ReferenceCount);
|
|
|
|
modelBuilder.Entity<RulesetInfo>().HasIndex(b => b.Available);
|
|
modelBuilder.Entity<RulesetInfo>().HasIndex(b => b.ShortName).IsUnique();
|
|
|
|
modelBuilder.Entity<BeatmapInfo>().HasOne(b => b.BaseDifficulty);
|
|
|
|
modelBuilder.Entity<ScoreInfo>().HasIndex(b => b.OnlineScoreID).IsUnique();
|
|
}
|
|
|
|
private class OsuDbLoggerFactory : ILoggerFactory
|
|
{
|
|
#region Disposal
|
|
|
|
public void Dispose()
|
|
{
|
|
}
|
|
|
|
#endregion
|
|
|
|
public ILogger CreateLogger(string categoryName) => new OsuDbLogger();
|
|
|
|
public void AddProvider(ILoggerProvider provider)
|
|
{
|
|
// no-op. called by tooling.
|
|
}
|
|
|
|
private class OsuDbLogger : ILogger
|
|
{
|
|
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
|
{
|
|
if (logLevel < LogLevel.Information)
|
|
return;
|
|
|
|
Framework.Logging.LogLevel frameworkLogLevel;
|
|
|
|
switch (logLevel)
|
|
{
|
|
default:
|
|
frameworkLogLevel = Framework.Logging.LogLevel.Debug;
|
|
break;
|
|
|
|
case LogLevel.Warning:
|
|
frameworkLogLevel = Framework.Logging.LogLevel.Important;
|
|
break;
|
|
|
|
case LogLevel.Error:
|
|
case LogLevel.Critical:
|
|
frameworkLogLevel = Framework.Logging.LogLevel.Error;
|
|
break;
|
|
}
|
|
|
|
Logger.Log(formatter(state, exception), LoggingTarget.Database, frameworkLogLevel);
|
|
}
|
|
|
|
public bool IsEnabled(LogLevel logLevel)
|
|
{
|
|
#if DEBUG_DATABASE
|
|
return logLevel > LogLevel.Debug;
|
|
#else
|
|
return logLevel > LogLevel.Information;
|
|
#endif
|
|
}
|
|
|
|
public IDisposable BeginScope<TState>(TState state) => null;
|
|
}
|
|
}
|
|
|
|
public void Migrate() => Database.Migrate();
|
|
}
|
|
}
|