diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index d9d23dea6b..3e0f0cb7f6 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -27,8 +27,8 @@ - - + + diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 42f70151ac..728af5124e 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -5,7 +5,7 @@ - + WinExe diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index e51b20c9fe..af16f39563 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -5,7 +5,7 @@ - + WinExe diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index f1f75148ef..3d2d1f3fec 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -5,7 +5,7 @@ - + WinExe diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index c9a320bdd5..fa00922706 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -5,7 +5,7 @@ - + WinExe diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 8cfe5d8af2..faa5d9e6fc 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -56,7 +56,6 @@ namespace osu.Game.Tests.Visual.Multiplayer beatmaps.Add(new BeatmapInfo { Ruleset = rulesets.GetRuleset(i % 4), - RulesetID = i % 4, // workaround for efcore 5 compatibility. OnlineBeatmapID = beatmapId, Length = length, BPM = bpm, diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index 9b8b74e6f6..53a956c77c 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -186,7 +186,6 @@ namespace osu.Game.Tests.Visual.SongSelect Metadata = metadata, BaseDifficulty = new BeatmapDifficulty(), Ruleset = ruleset, - RulesetID = ruleset.ID.GetValueOrDefault(), // workaround for efcore 5 compatibility. StarDifficulty = difficultyIndex + 1, Version = $"SR{difficultyIndex + 1}" }).ToList() diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 057b539e44..5731b1ac2c 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -911,11 +911,9 @@ namespace osu.Game.Tests.Visual.SongSelect int length = RNG.Next(30000, 200000); double bpm = RNG.NextSingle(80, 200); - var ruleset = getRuleset(); beatmaps.Add(new BeatmapInfo { - Ruleset = ruleset, - RulesetID = ruleset.ID.GetValueOrDefault(), // workaround for efcore 5 compatibility. + Ruleset = getRuleset(), OnlineBeatmapID = beatmapId, Version = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", Length = length, diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 6f8e0fac6f..e36b3cdc74 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 115d1b33bb..b4ea898b7d 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -171,8 +171,6 @@ namespace osu.Game.Beatmaps if (beatmapSet.Beatmaps.Any(b => b.BaseDifficulty == null)) throw new InvalidOperationException($"Cannot import {nameof(BeatmapInfo)} with null {nameof(BeatmapInfo.BaseDifficulty)}."); - beatmapSet.Requery(ContextFactory); - // check if a set already exists with the same online id, delete if it does. if (beatmapSet.OnlineBeatmapSetID != null) { diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 64428882ac..d809dbcb01 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -462,8 +462,6 @@ namespace osu.Game.Database // Dereference the existing file info, since the file model will be removed. if (file.FileInfo != null) { - file.Requery(usage.Context); - Files.Dereference(file.FileInfo); // This shouldn't be required, but here for safety in case the provided TModel is not being change tracked @@ -637,12 +635,10 @@ namespace osu.Game.Database { using (Stream s = reader.GetStream(file)) { - var fileInfo = files.Add(s); fileInfos.Add(new TFileModel { Filename = file.Substring(prefix.Length).ToStandardisedPath(), - FileInfo = fileInfo, - FileInfoID = fileInfo.ID // workaround for efcore 5 compatibility. + FileInfo = files.Add(s) }); } } diff --git a/osu.Game/Database/DatabaseWorkaroundExtensions.cs b/osu.Game/Database/DatabaseWorkaroundExtensions.cs deleted file mode 100644 index a3a982f232..0000000000 --- a/osu.Game/Database/DatabaseWorkaroundExtensions.cs +++ /dev/null @@ -1,71 +0,0 @@ -// 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 osu.Game.Beatmaps; -using osu.Game.Scoring; -using osu.Game.Skinning; - -namespace osu.Game.Database -{ - /// - /// Extension methods which contain workarounds to make EFcore 5.x work with our existing (incorrect) thread safety. - /// The intention is to avoid blocking package updates while we consider the future of the database backend, with a potential backend switch imminent. - /// - public static class DatabaseWorkaroundExtensions - { - /// - /// Re-query the provided model to ensure it is in a sane state. This method requires explicit implementation per model type. - /// - /// - /// - public static void Requery(this IHasPrimaryKey model, IDatabaseContextFactory contextFactory) - { - switch (model) - { - case SkinInfo skinInfo: - requeryFiles(skinInfo.Files, contextFactory); - break; - - case ScoreInfo scoreInfo: - requeryFiles(scoreInfo.Beatmap.BeatmapSet.Files, contextFactory); - requeryFiles(scoreInfo.Files, contextFactory); - break; - - case BeatmapSetInfo beatmapSetInfo: - var context = contextFactory.Get(); - - foreach (var beatmap in beatmapSetInfo.Beatmaps) - { - // Workaround System.InvalidOperationException - // The instance of entity type 'RulesetInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. - beatmap.Ruleset = context.RulesetInfo.Find(beatmap.RulesetID); - } - - requeryFiles(beatmapSetInfo.Files, contextFactory); - break; - - default: - throw new ArgumentException($"{nameof(Requery)} does not have support for the provided model type", nameof(model)); - } - - void requeryFiles(List files, IDatabaseContextFactory databaseContextFactory) where T : class, INamedFileInfo - { - var dbContext = databaseContextFactory.Get(); - - foreach (var file in files) - { - Requery(file, dbContext); - } - } - } - - public static void Requery(this INamedFileInfo file, OsuDbContext dbContext) - { - // Workaround System.InvalidOperationException - // The instance of entity type 'FileInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. - file.FileInfo = dbContext.FileInfo.Find(file.FileInfoID); - } - } -} diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 2342ab07d4..2aae62edea 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -3,6 +3,7 @@ using System; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Logging; using osu.Framework.Logging; using osu.Framework.Statistics; @@ -110,10 +111,10 @@ namespace osu.Game.Database { base.OnConfiguring(optionsBuilder); optionsBuilder - .UseSqlite(connectionString, - sqliteOptions => sqliteOptions - .CommandTimeout(10) - .UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery)) + // 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); } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 78101991f6..f5192f3a40 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -73,7 +73,7 @@ namespace osu.Game.Scoring } set { - modsJson = JsonConvert.SerializeObject(value.Select(m => new DeserializedMod { Acronym = m.Acronym })); + modsJson = null; mods = value; } } @@ -86,7 +86,16 @@ namespace osu.Game.Scoring [Column("Mods")] public string ModsJson { - get => modsJson; + get + { + if (modsJson != null) + return modsJson; + + if (mods == null) + return null; + + return modsJson = JsonConvert.SerializeObject(mods.Select(m => new DeserializedMod { Acronym = m.Acronym })); + } set { modsJson = value; diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 7d0abc5996..c7ee26c248 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -53,11 +53,6 @@ namespace osu.Game.Scoring this.configManager = configManager; } - protected override void PreImport(ScoreInfo model) - { - model.Requery(ContextFactory); - } - protected override ScoreInfo CreateModel(ArchiveReader archive) { if (archive == null) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 894a068b7f..9257636301 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -142,11 +142,6 @@ namespace osu.Game.Skinning } } - protected override void PreImport(SkinInfo model) - { - model.Requery(ContextFactory); - } - /// /// Retrieve a instance for the provided /// diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 360c522193..9731c1d5ea 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,10 +24,10 @@ - - + + - + diff --git a/osu.iOS.props b/osu.iOS.props index b763a91dfb..11677d345e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -90,6 +90,8 @@ + +