osu/osu.Game/Database/OsuDbContext.cs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

213 lines
7.4 KiB
C#
Raw Normal View History

// 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.
2018-04-13 09:19:50 +00:00
2017-10-15 15:53:59 +00:00
using System;
2017-10-14 06:16:08 +00:00
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using osu.Framework.Logging;
using osu.Framework.Statistics;
2017-10-04 19:52:12 +00:00
using osu.Game.Beatmaps;
using osu.Game.Configuration;
2017-10-04 19:52:12 +00:00
using osu.Game.IO;
using osu.Game.Rulesets;
using osu.Game.Scoring;
using osu.Game.Skinning;
using SQLitePCL;
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
2018-04-13 09:19:50 +00:00
2017-10-04 19:52:12 +00:00
namespace osu.Game.Database
{
public class OsuDbContext : DbContext
{
public DbSet<EFBeatmapInfo> EFBeatmapInfo { get; set; }
public DbSet<EFBeatmapDifficulty> BeatmapDifficulty { get; set; }
public DbSet<EFBeatmapMetadata> BeatmapMetadata { get; set; }
public DbSet<EFBeatmapSetInfo> EFBeatmapSetInfo { get; set; }
2017-10-15 15:53:59 +00:00
public DbSet<FileInfo> FileInfo { get; set; }
public DbSet<EFRulesetInfo> RulesetInfo { get; set; }
public DbSet<EFSkinInfo> SkinInfo { get; set; }
public DbSet<EFScoreInfo> ScoreInfo { get; set; }
2018-04-13 09:19:50 +00:00
// migrated to realm
2021-09-15 06:22:43 +00:00
public DbSet<DatabasedSetting> DatabasedSetting { get; set; }
2017-10-04 19:52:12 +00:00
private readonly string connectionString;
2018-04-13 09:19:50 +00:00
private static readonly Lazy<OsuDbLoggerFactory> logger = new Lazy<OsuDbLoggerFactory>(() => new OsuDbLoggerFactory());
2018-04-13 09:19:50 +00:00
private static readonly GlobalStatistic<int> contexts = GlobalStatistics.Get<int>("Database", "Contexts");
2017-10-16 02:06:19 +00:00
static OsuDbContext()
2017-10-04 19:52:12 +00:00
{
2017-10-16 02:06:19 +00:00
// required to initialise native SQLite libraries on some platforms.
Batteries_V2.Init();
2019-07-06 03:32:16 +00:00
// https://github.com/aspnet/EntityFrameworkCore/issues/9994#issuecomment-508588678
raw.sqlite3_config(2 /*SQLITE_CONFIG_MULTITHREAD*/);
2017-10-04 19:52:12 +00:00
}
2018-04-13 09:19:50 +00:00
2017-10-17 08:52:02 +00:00
/// <summary>
/// Create a new in-memory OsuDbContext instance.
/// </summary>
public OsuDbContext()
: this("DataSource=:memory:")
2017-10-17 08:52:02 +00:00
{
// required for tooling (see https://wildermuth.com/2017/07/06/Program-cs-in-ASP-NET-Core-2-0).
2018-04-13 09:19:50 +00:00
Migrate();
2017-10-17 08:52:02 +00:00
}
2018-04-13 09:19:50 +00:00
2017-10-16 02:06:19 +00:00
/// <summary>
/// Create a new OsuDbContext instance.
/// </summary>
2017-10-17 08:52:02 +00:00
/// <param name="connectionString">A valid SQLite connection string.</param>
public OsuDbContext(string connectionString)
2017-10-04 19:52:12 +00:00
{
this.connectionString = connectionString;
2018-04-13 09:19:50 +00:00
2017-10-16 02:06:19 +00:00
var connection = Database.GetDbConnection();
2019-04-01 03:16:05 +00:00
try
2017-10-16 02:06:19 +00:00
{
connection.Open();
using (var cmd = connection.CreateCommand())
{
cmd.CommandText = "PRAGMA journal_mode=WAL;";
cmd.ExecuteNonQuery();
cmd.CommandText = "PRAGMA foreign_keys=OFF;";
cmd.ExecuteNonQuery();
}
}
2019-04-25 08:36:17 +00:00
catch
{
connection.Close();
throw;
2017-10-16 02:06:19 +00:00
}
contexts.Value++;
2017-10-04 19:52:12 +00:00
}
2018-04-13 09:19:50 +00:00
~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);
}
2017-10-04 19:52:12 +00:00
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
2017-10-20 00:25:54 +00:00
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.
.UseSqlite(connectionString, sqliteOptions => sqliteOptions.CommandTimeout(10))
2017-10-20 00:25:54 +00:00
.UseLoggerFactory(logger.Value);
2017-10-04 19:52:12 +00:00
}
2018-04-13 09:19:50 +00:00
2017-10-04 19:52:12 +00:00
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
2018-04-13 09:19:50 +00:00
modelBuilder.Entity<EFBeatmapInfo>().HasIndex(b => b.OnlineID).IsUnique();
modelBuilder.Entity<EFBeatmapInfo>().HasIndex(b => b.MD5Hash);
modelBuilder.Entity<EFBeatmapInfo>().HasIndex(b => b.Hash);
2018-04-13 09:19:50 +00:00
modelBuilder.Entity<EFBeatmapSetInfo>().HasIndex(b => b.OnlineID).IsUnique();
modelBuilder.Entity<EFBeatmapSetInfo>().HasIndex(b => b.DeletePending);
modelBuilder.Entity<EFBeatmapSetInfo>().HasIndex(b => b.Hash).IsUnique();
2018-04-13 09:19:50 +00:00
modelBuilder.Entity<EFSkinInfo>().HasIndex(b => b.Hash).IsUnique();
modelBuilder.Entity<EFSkinInfo>().HasIndex(b => b.DeletePending);
modelBuilder.Entity<EFSkinInfo>().HasMany(s => s.Files).WithOne(f => f.SkinInfo);
modelBuilder.Entity<DatabasedSetting>().HasIndex(b => new { b.RulesetID, b.Variant });
2018-04-13 09:19:50 +00:00
2017-10-04 19:52:12 +00:00
modelBuilder.Entity<FileInfo>().HasIndex(b => b.Hash).IsUnique();
modelBuilder.Entity<FileInfo>().HasIndex(b => b.ReferenceCount);
2018-04-13 09:19:50 +00:00
modelBuilder.Entity<EFRulesetInfo>().HasIndex(b => b.Available);
modelBuilder.Entity<EFRulesetInfo>().HasIndex(b => b.ShortName).IsUnique();
2018-04-13 09:19:50 +00:00
modelBuilder.Entity<EFBeatmapInfo>().HasOne(b => b.BaseDifficulty);
2018-11-28 09:52:57 +00:00
modelBuilder.Entity<EFScoreInfo>().HasIndex(b => b.OnlineID).IsUnique();
2017-10-04 19:52:12 +00:00
}
2018-04-13 09:19:50 +00:00
2017-10-14 06:16:08 +00:00
private class OsuDbLoggerFactory : ILoggerFactory
{
2017-10-15 15:53:59 +00:00
#region Disposal
2018-04-13 09:19:50 +00:00
2017-10-14 06:16:08 +00:00
public void Dispose()
{
}
2018-04-13 09:19:50 +00:00
2017-10-15 15:53:59 +00:00
#endregion
2018-04-13 09:19:50 +00:00
2017-10-14 06:16:08 +00:00
public ILogger CreateLogger(string categoryName) => new OsuDbLogger();
2018-04-13 09:19:50 +00:00
2017-10-16 03:51:46 +00:00
public void AddProvider(ILoggerProvider provider)
{
2017-10-17 08:52:02 +00:00
// no-op. called by tooling.
2017-10-16 03:51:46 +00:00
}
2018-04-13 09:19:50 +00:00
2017-10-14 06:16:08 +00:00
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;
2018-04-13 09:19:50 +00:00
Framework.Logging.LogLevel frameworkLogLevel;
2018-04-13 09:19:50 +00:00
switch (logLevel)
{
default:
frameworkLogLevel = Framework.Logging.LogLevel.Debug;
break;
2019-04-01 03:16:05 +00:00
case LogLevel.Warning:
frameworkLogLevel = Framework.Logging.LogLevel.Important;
break;
2019-04-01 03:16:05 +00:00
case LogLevel.Error:
case LogLevel.Critical:
frameworkLogLevel = Framework.Logging.LogLevel.Error;
break;
}
2018-04-13 09:19:50 +00:00
Logger.Log(formatter(state, exception), LoggingTarget.Database, frameworkLogLevel);
}
2018-04-13 09:19:50 +00:00
public bool IsEnabled(LogLevel logLevel)
{
#if DEBUG_DATABASE
return logLevel > LogLevel.Debug;
#else
return logLevel > LogLevel.Information;
#endif
}
2018-04-13 09:19:50 +00:00
2017-10-14 06:16:08 +00:00
public IDisposable BeginScope<TState>(TState state) => null;
}
}
2018-04-13 09:19:50 +00:00
public void Migrate() => Database.Migrate();
}
2017-10-04 19:52:12 +00:00
}