Skip to content

Commit 75e3e25

Browse files
committed
fix: add missing/repair misconfigured Guild FK in Players table
1 parent 051f18d commit 75e3e25

14 files changed

+2067
-30
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Intersect.Server.Database;
2+
3+
public sealed class CleanDatabaseMigrationMetadata : MigrationMetadata
4+
{
5+
public override MigrationType MigrationType => MigrationType.Clean;
6+
}

Intersect.Server.Core/Database/DbInterface.cs

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using Intersect.Collections;
1111
using Intersect.Config;
1212
using Intersect.Enums;
13+
using Intersect.Framework.Reflection;
1314
using Intersect.GameObjects;
1415
using Intersect.GameObjects.Crafting;
1516
using Intersect.GameObjects.Events;
@@ -18,7 +19,6 @@
1819
using Intersect.Logging;
1920
using Intersect.Logging.Output;
2021
using Intersect.Models;
21-
using Intersect.Reflection;
2222
using Intersect.Server.Core;
2323
using Intersect.Server.Database.GameData;
2424
using Intersect.Server.Database.Logging;
@@ -249,7 +249,7 @@ private static void ProcessMigrations<TContext>(TContext context)
249249
{
250250
if (!context.HasPendingMigrations)
251251
{
252-
Log.Verbose("No pending migrations, skipping...");
252+
Log.Verbose($"No pending migrations for {context.GetType().GetName(qualified: true)}, skipping...");
253253
return;
254254
}
255255

@@ -272,40 +272,43 @@ private static void ProcessMigrations<TContext>(TContext context)
272272

273273
private static bool EnsureUpdated(IServerContext serverContext)
274274
{
275-
Log.Verbose("Creating game context...");
275+
var gameDatabaseOptions = Options.Instance.GameDatabase;
276+
Log.Info($"Creating game context using {gameDatabaseOptions.Type}...");
276277
using var gameContext = GameContext.Create(new DatabaseContextOptions
277278
{
278-
ConnectionStringBuilder = Options.Instance.GameDatabase.Type.CreateConnectionStringBuilder(
279-
Options.Instance.GameDatabase,
279+
ConnectionStringBuilder = gameDatabaseOptions.Type.CreateConnectionStringBuilder(
280+
gameDatabaseOptions,
280281
GameDbFilename
281282
),
282-
DatabaseType = Options.Instance.GameDatabase.Type,
283+
DatabaseType = gameDatabaseOptions.Type,
283284
EnableDetailedErrors = true,
284285
EnableSensitiveDataLogging = true,
285286
LoggerFactory = new IntersectLoggerFactory(nameof(GameContext)),
286287
});
287288

288-
Log.Verbose("Creating player context...");
289+
var playerDatabaseOptions = Options.Instance.PlayerDatabase;
290+
Log.Info($"Creating player context using {playerDatabaseOptions.Type}...");
289291
using var playerContext = PlayerContext.Create(new DatabaseContextOptions
290292
{
291-
ConnectionStringBuilder = Options.Instance.PlayerDatabase.Type.CreateConnectionStringBuilder(
292-
Options.Instance.PlayerDatabase,
293+
ConnectionStringBuilder = playerDatabaseOptions.Type.CreateConnectionStringBuilder(
294+
playerDatabaseOptions,
293295
PlayersDbFilename
294296
),
295-
DatabaseType = Options.Instance.PlayerDatabase.Type,
297+
DatabaseType = playerDatabaseOptions.Type,
296298
EnableDetailedErrors = true,
297299
EnableSensitiveDataLogging = true,
298300
LoggerFactory = new IntersectLoggerFactory(nameof(PlayerContext)),
299301
});
300302

301-
Log.Verbose("Creating logging context...");
303+
var loggingDatabaseOptions = Options.Instance.LoggingDatabase;
304+
Log.Info($"Creating logging context using {loggingDatabaseOptions.Type}...");
302305
using var loggingContext = LoggingContext.Create(new DatabaseContextOptions
303306
{
304-
ConnectionStringBuilder = Options.Instance.LoggingDatabase.Type.CreateConnectionStringBuilder(
305-
Options.Instance.LoggingDatabase,
307+
ConnectionStringBuilder = loggingDatabaseOptions.Type.CreateConnectionStringBuilder(
308+
loggingDatabaseOptions,
306309
LoggingDbFilename
307310
),
308-
DatabaseType = Options.Instance.LoggingDatabase.Type,
311+
DatabaseType = loggingDatabaseOptions.Type,
309312
EnableDetailedErrors = true,
310313
EnableSensitiveDataLogging = true,
311314
LoggerFactory = new IntersectLoggerFactory(nameof(LoggingContext)),
@@ -397,7 +400,7 @@ private static bool EnsureUpdated(IServerContext serverContext)
397400
}
398401
else
399402
{
400-
Console.WriteLine("No migrations pending, skipping...");
403+
Console.WriteLine("No migrations pending that require user acceptance, skipping prompt...");
401404
}
402405

403406
var contexts = new List<DbContext> { gameContext, playerContext, loggingContext };

Intersect.Server.Core/Database/IntersectDbContext.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
using Intersect.Config;
66
using Intersect.Framework.Reflection;
77
using Intersect.Logging;
8-
using Intersect.Reflection;
98
using Intersect.Server.Core;
9+
#if DIAGNOSTIC
10+
using Intersect.Server.Database.PlayerData;
11+
#endif
1012
using Intersect.Server.Database.PlayerData.Players;
1113
using Intersect.Server.Entities;
1214
using Microsoft.EntityFrameworkCore;
@@ -91,7 +93,7 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
9193
#if DIAGNOSTIC
9294
if (this is PlayerContext)
9395
{
94-
loggerFactory ??= new IntersectLoggerFactory();
96+
loggerFactory ??= new IntersectLoggerFactory(GetType().GetName(qualified: true));
9597
}
9698
#endif
9799

Intersect.Server.Core/Database/MigrationMetadata.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,4 @@ public static MigrationMetadata CreateSchemaMigrationMetadata(string schemaMigra
4848
Name = schemaMigrationName,
4949
};
5050
}
51-
}
51+
}

Intersect.Server.Core/Database/MigrationScheduler.cs

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Intersect.Framework.Reflection;
12
using Intersect.Logging;
23
using Microsoft.EntityFrameworkCore.Infrastructure;
34
using Microsoft.EntityFrameworkCore.Migrations;
@@ -43,6 +44,29 @@ public void ApplyScheduledMigrations()
4344
var migrator = _context.Database.GetService<IMigrator>();
4445

4546
var scheduleSegment = _scheduledMigrations;
47+
if (scheduleSegment.Any(migration => migration is CleanDatabaseMigrationMetadata))
48+
{
49+
if (scheduleSegment.Count != 1)
50+
{
51+
var migrationTypeNames = string.Join(", ", scheduleSegment.Select(migration => migration.GetType().GetName(qualified: true)));
52+
throw new InvalidOperationException(
53+
$"There should only be 1 {nameof(CleanDatabaseMigrationMetadata)} migration if one is present in the schedule but there were {scheduleSegment.Count} migrations scheduled: {migrationTypeNames}"
54+
);
55+
}
56+
57+
// If we are just doing a clean database "migration" (really just a creation), invoke once and exit early
58+
if (_context.Database.EnsureCreated())
59+
{
60+
Log.Verbose("Database created successfully.");
61+
}
62+
else
63+
{
64+
Log.Verbose("Failed to ensure database was created, using the migrator instead...");
65+
migrator.Migrate();
66+
}
67+
return;
68+
}
69+
4670
while (scheduleSegment.Any())
4771
{
4872
var targetMigration = scheduleSegment
@@ -80,20 +104,38 @@ public void ApplyScheduledMigrations()
80104

81105
scheduleSegment = scheduleSegment
82106
.Skip(1)
83-
.ToList();;
107+
.ToList();
84108
}
85109
}
86110

87111
public MigrationScheduler<TContext> SchedulePendingMigrations()
88112
{
89113
var pendingDataMigrations = _context.PendingDataMigrations;
90-
List<MigrationMetadata> scheduledMigrations = new();
91114

92-
var pendingSchemaMigrationNames = _context.PendingSchemaMigrations.ToList();
115+
var allSchemaMigrationNames = _context.AllSchemaMigrations.ToArray();
116+
var pendingSchemaMigrationNames = _context.PendingSchemaMigrations.ToArray();
117+
var isCleanDatabase = allSchemaMigrationNames.Length == pendingSchemaMigrationNames.Length &&
118+
pendingSchemaMigrationNames.Select(
119+
(name, index) => string.Equals(
120+
name,
121+
allSchemaMigrationNames[index],
122+
StringComparison.Ordinal
123+
)
124+
).All(equality => equality);
125+
126+
if (isCleanDatabase)
127+
{
128+
// If we're dealing with a totally empty database, just "migrate" cleanly to the latest
129+
_scheduledMigrations = [new CleanDatabaseMigrationMetadata()];
130+
return this;
131+
}
132+
93133
var pendingSchemaMigrations = pendingSchemaMigrationNames
94134
.Select(pendingMigration => MigrationMetadata.CreateSchemaMigrationMetadata(pendingMigration, typeof(TContext)))
95135
.ToList();
96136

137+
List<MigrationMetadata> scheduledMigrations = [];
138+
97139
// Add schema migrations in their order and interleave any data migrations
98140
foreach (var pendingMigration in pendingSchemaMigrations)
99141
{
@@ -104,7 +146,7 @@ public MigrationScheduler<TContext> SchedulePendingMigrations()
104146
string.Equals(
105147
pendingMigration.Name,
106148
dataMigration.SchemaMigrations
107-
.OrderBy(name => pendingSchemaMigrationNames.IndexOf(name))
149+
.OrderBy(name => Array.IndexOf(pendingSchemaMigrationNames, name))
108150
.Last(),
109151
StringComparison.Ordinal
110152
)

Intersect.Server.Core/Database/MigrationType.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
public enum MigrationType
44
{
5+
Clean,
56
Data,
6-
7-
Schema
7+
Schema,
88
}

Intersect.Server.Core/Database/PlayerData/PlayerContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
7575

7676
modelBuilder.Entity<User>().HasMany(b => b.Players).WithOne(p => p.User);
7777

78+
modelBuilder.Entity<Player>().HasOne(p => p.DbGuild).WithMany().OnDelete(DeleteBehavior.SetNull);
79+
7880
modelBuilder.Entity<Player>()
7981
.HasMany(b => b.Friends)
8082
.WithOne(p => p.Owner)

Intersect.Server.Core/Entities/Player.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,12 +205,15 @@ public ulong PlayTimeSeconds
205205

206206
[NotMapped][JsonIgnore] public bool IsInGuild => Guild != null;
207207

208-
[NotMapped] public Guid GuildId => DbGuild?.Id ?? default;
208+
[NotMapped] public Guid GuildId => DbGuildId ?? default;
209+
210+
[JsonIgnore]
211+
public Guid? DbGuildId { get; set; }
209212

210213
/// <summary>
211214
/// This field is used for EF database fields only and should never be assigned to or used, instead the guild instance will be assigned to CachedGuild above
212215
/// </summary>
213-
[JsonIgnore] public Guild DbGuild { get; set; }
216+
[JsonIgnore] [ForeignKey(nameof(DbGuildId))] public Guild? DbGuild { get; set; }
214217

215218
[NotMapped]
216219
[JsonIgnore]

0 commit comments

Comments
 (0)