using LANCommander.Launcher.Data.Interceptors; using LANCommander.Launcher.Data.Models; using LANCommander.SDK; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace LANCommander.Launcher.Data { public class DatabaseContext : DbContext { private readonly ILoggerFactory LoggerFactory; public DatabaseContext() { } public DatabaseContext(ILoggerFactory loggerFactory, DbContextOptions options) : base(options) { LoggerFactory = loggerFactory; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { // Skip the SQLite default when the caller has already chosen a provider // (e.g. tests using EF InMemory) — EF errors out if two providers are registered. if (optionsBuilder.IsConfigured) return; var dbPath = AppPaths.GetConfigPath("LANCommander.db"); var connectionString = new SqliteConnectionStringBuilder { DataSource = dbPath, Cache = SqliteCacheMode.Shared, }.ToString(); optionsBuilder.AddInterceptors(new AuditingInterceptor()); optionsBuilder.UseLoggerFactory(LoggerFactory); optionsBuilder.UseSqlite(connectionString, options => { options.CommandTimeout(30); }); } /// /// Enables WAL mode for concurrent reads during background writes. /// Should be called once after the database is created/migrated. /// public async Task EnableWalModeAsync() { await Database.ExecuteSqlRawAsync("PRAGMA journal_mode=WAL;"); } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); builder.Entity() .HasMany(g => g.Games) .WithMany(g => g.Genres); builder.Entity() .HasMany(c => c.Games) .WithMany(g => g.Categories); builder.Entity() .HasMany(c => c.Children) .WithOne(c => c.Parent) .IsRequired(false); builder.Entity() .HasMany(t => t.Games) .WithMany(g => g.Tags); builder.Entity() .HasMany(p => p.Games) .WithMany(g => g.Platforms); builder.Entity() .HasOne(l => l.User) .WithOne(u => u.Library) .OnDelete(DeleteBehavior.Cascade); builder.Entity() .HasMany(l => l.Games) .WithMany(l => l.Libraries) .UsingEntity>( "LibraryGame", lg => lg.HasOne().WithMany().HasForeignKey("GameId").OnDelete(DeleteBehavior.Cascade), lg => lg.HasOne().WithMany().HasForeignKey("LibraryId").OnDelete(DeleteBehavior.Cascade) ); builder.Entity() .HasOne(u => u.Avatar) .WithOne(a => a.User) .OnDelete(DeleteBehavior.Cascade); #region Game Relationships builder.Entity() .Property(g => g.BaseGameId) .HasConversion(new GuidToNullConverter()); builder.Entity() .HasMany(g => g.MultiplayerModes) .WithOne(m => m.Game) .IsRequired(false) .OnDelete(DeleteBehavior.Cascade); builder.Entity() .HasMany(g => g.Media) .WithOne(m => m.Game) .IsRequired(false) .OnDelete(DeleteBehavior.Cascade); builder.Entity() .HasMany(g => g.Developers) .WithMany(c => c.DevelopedGames) .UsingEntity>( "GameDeveloper", g => g.HasOne().WithMany().HasForeignKey("DeveloperId"), g => g.HasOne().WithMany().HasForeignKey("GameId") ); builder.Entity() .HasMany(g => g.Publishers) .WithMany(c => c.PublishedGames) .UsingEntity>( "GamePublisher", g => g.HasOne().WithMany().HasForeignKey("PublisherId"), g => g.HasOne().WithMany().HasForeignKey("GameId") ); builder.Entity() .HasMany(g => g.Redistributables) .WithMany(r => r.Games) .UsingEntity>( "GameRedistributable", gr => gr.HasOne().WithMany().HasForeignKey("RedistributableId"), gr => gr.HasOne().WithMany().HasForeignKey("GameId") ); builder.Entity() .HasMany(g => g.DependentGames) .WithOne(g => g.BaseGame) .IsRequired(false) .OnDelete(DeleteBehavior.SetNull); builder.Entity() .HasOne(g => g.Engine) .WithMany(e => e.Games) .IsRequired(false) .OnDelete(DeleteBehavior.SetNull); builder.Entity() .HasMany(g => g.ExternalIds) .WithOne(e => e.Game) .IsRequired(false) .OnDelete(DeleteBehavior.Cascade); #endregion #region Collection Relationships builder.Entity() .HasMany(c => c.Games) .WithMany(g => g.Collections) .UsingEntity>( "CollectionGame", cg => cg.HasOne().WithMany().HasForeignKey("GameId"), cg => cg.HasOne().WithMany().HasForeignKey("CollectionId") ); #endregion } public DbSet? Engines { get; set; } public DbSet? Games { get; set; } public DbSet? Genres { get; set; } public DbSet? Categories { get; set; } public DbSet? Tags { get; set; } public DbSet? Companies { get; set; } public DbSet? Redistributables { get; set; } public DbSet? Media { get; set; } public DbSet? Libraries { get; set; } public DbSet? Users { get; set; } } }