Fix several database issues

- Specify default value
- Cache all guild users on initial download
- Fix foreign key constraint issues
This commit is contained in:
Noi 2022-06-10 16:09:18 -07:00
parent da31ce3e0d
commit 6afa6dc2c6
6 changed files with 36 additions and 16 deletions

View file

@ -33,6 +33,7 @@ public class BotDatabaseContext : DbContext {
modelBuilder.Entity<CachedGuildUser>(entity => { modelBuilder.Entity<CachedGuildUser>(entity => {
entity.Navigation(e => e.User).AutoInclude(); entity.Navigation(e => e.User).AutoInclude();
entity.HasKey(e => new { e.UserId, e.GuildId }); entity.HasKey(e => new { e.UserId, e.GuildId });
entity.Property(e => e.FirstSeenTime).HasDefaultValueSql("now()");
}); });
modelBuilder.Entity<CachedGuildMessage>(entity => entity.Property(e => e.CreatedAt).HasDefaultValueSql("now()")); modelBuilder.Entity<CachedGuildMessage>(entity => entity.Property(e => e.CreatedAt).HasDefaultValueSql("now()"));
} }

View file

@ -13,8 +13,8 @@ using RegexBot.Data;
namespace RegexBot.Data.Migrations namespace RegexBot.Data.Migrations
{ {
[DbContext(typeof(BotDatabaseContext))] [DbContext(typeof(BotDatabaseContext))]
[Migration("20220513061851_InitialEFSetup")] [Migration("20220610210059_InitialMigration")]
partial class InitialEFSetup partial class InitialMigration
{ {
protected override void BuildTargetModel(ModelBuilder modelBuilder) protected override void BuildTargetModel(ModelBuilder modelBuilder)
{ {
@ -83,8 +83,10 @@ namespace RegexBot.Data.Migrations
.HasColumnName("guild_id"); .HasColumnName("guild_id");
b.Property<DateTimeOffset>("FirstSeenTime") b.Property<DateTimeOffset>("FirstSeenTime")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasColumnName("first_seen_time"); .HasColumnName("first_seen_time")
.HasDefaultValueSql("now()");
b.Property<DateTimeOffset>("GULastUpdateTime") b.Property<DateTimeOffset>("GULastUpdateTime")
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")

View file

@ -7,7 +7,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace RegexBot.Data.Migrations namespace RegexBot.Data.Migrations
{ {
public partial class InitialEFSetup : Migration public partial class InitialMigration : Migration
{ {
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)
{ {
@ -73,7 +73,7 @@ namespace RegexBot.Data.Migrations
user_id = table.Column<long>(type: "bigint", nullable: false), user_id = table.Column<long>(type: "bigint", nullable: false),
guild_id = table.Column<long>(type: "bigint", nullable: false), guild_id = table.Column<long>(type: "bigint", nullable: false),
gu_last_update_time = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false), gu_last_update_time = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
first_seen_time = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false), first_seen_time = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
nickname = table.Column<string>(type: "text", nullable: true) nickname = table.Column<string>(type: "text", nullable: true)
}, },
constraints: table => constraints: table =>

View file

@ -81,8 +81,10 @@ namespace RegexBot.Data.Migrations
.HasColumnName("guild_id"); .HasColumnName("guild_id");
b.Property<DateTimeOffset>("FirstSeenTime") b.Property<DateTimeOffset>("FirstSeenTime")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasColumnName("first_seen_time"); .HasColumnName("first_seen_time")
.HasDefaultValueSql("now()");
b.Property<DateTimeOffset>("GULastUpdateTime") b.Property<DateTimeOffset>("GULastUpdateTime")
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")

View file

@ -29,7 +29,8 @@ class Program {
MessageCacheSize = 0, // using our own MessageCacheSize = 0, // using our own
LogLevel = LogSeverity.Info, LogLevel = LogSeverity.Info,
GatewayIntents = GatewayIntents.All & ~GatewayIntents.GuildPresences, GatewayIntents = GatewayIntents.All & ~GatewayIntents.GuildPresences,
LogGatewayIntentWarnings = false LogGatewayIntentWarnings = false,
AlwaysDownloadUsers = true
}); });
// Kerobot class initialization - will set up services and modules // Kerobot class initialization - will set up services and modules

View file

@ -13,16 +13,35 @@ class UserCachingSubservice {
private static Regex DiscriminatorSearch { get; } = new(@"(.+)#(\d{4}(?!\d))", RegexOptions.Compiled); private static Regex DiscriminatorSearch { get; } = new(@"(.+)#(\d{4}(?!\d))", RegexOptions.Compiled);
internal UserCachingSubservice(RegexbotClient bot) { internal UserCachingSubservice(RegexbotClient bot) {
bot.DiscordClient.GuildMembersDownloaded += DiscordClient_GuildMembersDownloaded;
bot.DiscordClient.GuildMemberUpdated += DiscordClient_GuildMemberUpdated; bot.DiscordClient.GuildMemberUpdated += DiscordClient_GuildMemberUpdated;
bot.DiscordClient.UserUpdated += DiscordClient_UserUpdated; bot.DiscordClient.UserUpdated += DiscordClient_UserUpdated;
} }
private async Task DiscordClient_GuildMembersDownloaded(SocketGuild arg) {
using var db = new BotDatabaseContext();
foreach (var user in arg.Users) {
UpdateUser(user, db);
UpdateGuildUser(user, db);
}
await db.SaveChangesAsync();
}
private async Task DiscordClient_GuildMemberUpdated(Discord.Cacheable<SocketGuildUser, ulong> old, SocketGuildUser current) {
using var db = new BotDatabaseContext();
UpdateUser(current, db); // Update user data first (avoid potential foreign key constraint violation)
UpdateGuildUser(current, db);
await db.SaveChangesAsync();
}
private async Task DiscordClient_UserUpdated(SocketUser old, SocketUser current) { private async Task DiscordClient_UserUpdated(SocketUser old, SocketUser current) {
using var db = new BotDatabaseContext(); using var db = new BotDatabaseContext();
UpdateUser(current, db); UpdateUser(current, db);
await db.SaveChangesAsync(); await db.SaveChangesAsync();
} }
// IMPORTANT: Do NOT forget to save changes in database after calling this!
private static void UpdateUser(SocketUser user, BotDatabaseContext db) { private static void UpdateUser(SocketUser user, BotDatabaseContext db) {
CachedUser uinfo; CachedUser uinfo;
try { try {
@ -38,23 +57,18 @@ class UserCachingSubservice {
uinfo.ULastUpdateTime = DateTimeOffset.UtcNow; uinfo.ULastUpdateTime = DateTimeOffset.UtcNow;
} }
private async Task DiscordClient_GuildMemberUpdated(Discord.Cacheable<SocketGuildUser, ulong> old, SocketGuildUser current) { private static void UpdateGuildUser(SocketGuildUser user, BotDatabaseContext db) {
using var db = new BotDatabaseContext();
UpdateUser(current, db); // Update user data too (avoid potential foreign key constraint violation)
CachedGuildUser guinfo; CachedGuildUser guinfo;
try { try {
guinfo = db.GuildUserCache.Where(c => c.GuildId == (long)current.Guild.Id && c.UserId == (long)current.Id).First(); guinfo = db.GuildUserCache.Where(c => c.GuildId == (long)user.Guild.Id && c.UserId == (long)user.Id).First();
} catch (InvalidOperationException) { } catch (InvalidOperationException) {
guinfo = new() { GuildId = (long)current.Guild.Id, UserId = (long)current.Id }; guinfo = new() { GuildId = (long)user.Guild.Id, UserId = (long)user.Id };
db.GuildUserCache.Add(guinfo); db.GuildUserCache.Add(guinfo);
} }
guinfo.GULastUpdateTime = DateTimeOffset.UtcNow; guinfo.GULastUpdateTime = DateTimeOffset.UtcNow;
guinfo.Nickname = current.Nickname; guinfo.Nickname = user.Nickname;
// TODO guild-specific avatar, other details? // TODO guild-specific avatar, other details?
await db.SaveChangesAsync();
} }
// Hooked // Hooked