1 using Microsoft.EntityFrameworkCore;
2 using Microsoft.EntityFrameworkCore.Infrastructure;
3 using Microsoft.EntityFrameworkCore.Migrations;
4 using Microsoft.Extensions.DependencyInjection;
5 using Microsoft.Extensions.Logging;
6 using Microsoft.Extensions.Options;
11 using System.Threading.Tasks;
21 #pragma warning disable CA1506 // TODO: Decomplexify 30 public DbSet<User> Users {
get;
set; }
35 public DbSet<Instance> Instances {
get;
set; }
40 public DbSet<CompileJob> CompileJobs {
get;
set; }
45 public DbSet<RevisionInformation> RevisionInformations {
get;
set; }
55 public DbSet<ChatBot> ChatBots {
get;
set; }
70 public DbSet<InstanceUser> InstanceUsers {
get;
set; }
75 public DbSet<ChatChannel> ChatChannels {
get;
set; }
80 public DbSet<Job> Jobs {
get;
set; }
85 public DbSet<ReattachInformation> ReattachInformations {
get;
set; }
90 public DbSet<DualReattachInformation> WatchdogReattachInformations {
get;
set; }
95 public DbSet<TestMerge> TestMerges {
get;
set; }
100 public DbSet<RevInfoTestMerge> RevInfoTestMerges {
get;
set; }
105 protected ILogger Logger {
get; }
228 public DatabaseContext(DbContextOptions dbContextOptions, IOptions<DatabaseConfiguration> databaseConfigurationOptions,
IDatabaseSeeder databaseSeeder, ILogger logger) : base(dbContextOptions)
230 DatabaseConfiguration = databaseConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(databaseConfigurationOptions));
231 this.databaseSeeder = databaseSeeder ??
throw new ArgumentNullException(nameof(databaseSeeder));
232 Logger = logger ??
throw new ArgumentNullException(nameof(logger));
253 Logger.LogTrace(
"Building entity framework context...");
254 base.OnModelCreating(modelBuilder);
256 var userModel = modelBuilder.Entity<
User>();
257 userModel.HasIndex(x => x.CanonicalName).IsUnique();
258 userModel.HasIndex(x => x.SystemIdentifier).IsUnique();
259 userModel.HasMany(x => x.TestMerges).WithOne(x => x.MergedBy).OnDelete(DeleteBehavior.Restrict);
261 modelBuilder.Entity<
InstanceUser>().HasIndex(x =>
new { x.UserId, x.InstanceId }).IsUnique();
264 revInfo.HasMany(x => x.ActiveTestMerges).WithOne(x => x.RevisionInformation).OnDelete(DeleteBehavior.Cascade);
265 revInfo.HasOne(x => x.PrimaryTestMerge).WithOne(x => x.PrimaryRevisionInformation).OnDelete(DeleteBehavior.Cascade);
266 revInfo.HasIndex(x =>
new { x.InstanceId, x.CommitSha }).IsUnique();
274 revInfo.HasMany(x => x.CompileJobs).WithOne(x => x.RevisionInformation).OnDelete(DeleteBehavior.ClientNoAction);
277 modelBuilder.Entity<
TestMerge>().HasMany(x => x.RevisonInformations).WithOne(x => x.TestMerge).OnDelete(DeleteBehavior.ClientNoAction);
279 var compileJob = modelBuilder.Entity<
CompileJob>();
280 compileJob.HasIndex(x => x.DirectoryName);
281 compileJob.HasOne(x => x.Job).WithOne().OnDelete(DeleteBehavior.Cascade);
283 var chatChannel = modelBuilder.Entity<
ChatChannel>();
284 chatChannel.HasIndex(x =>
new { x.ChatSettingsId, x.IrcChannel }).IsUnique();
285 chatChannel.HasIndex(x =>
new { x.ChatSettingsId, x.DiscordChannelId }).IsUnique();
286 chatChannel.HasOne(x => x.ChatSettings).WithMany(x => x.Channels).HasForeignKey(x => x.ChatSettingsId).OnDelete(DeleteBehavior.Cascade);
288 modelBuilder.Entity<
ChatBot>().HasIndex(x =>
new { x.InstanceId, x.Name }).IsUnique();
290 var instanceModel = modelBuilder.Entity<
Instance>();
291 instanceModel.HasIndex(x => x.Path).IsUnique();
292 instanceModel.HasMany(x => x.ChatSettings).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade);
293 instanceModel.HasOne(x => x.DreamDaemonSettings).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade);
294 instanceModel.HasOne(x => x.DreamMakerSettings).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade);
295 instanceModel.HasOne(x => x.RepositorySettings).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade);
296 instanceModel.HasMany(x => x.RevisionInformations).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade);
297 instanceModel.HasMany(x => x.InstanceUsers).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade);
298 instanceModel.HasMany(x => x.Jobs).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade);
299 instanceModel.HasOne(x => x.WatchdogReattachInformation).WithOne().OnDelete(DeleteBehavior.Cascade);
303 public async Task
Initialize(CancellationToken cancellationToken)
305 ValidateDatabaseType();
309 Logger.LogCritical(
"DropDatabase configuration option set! Dropping any existing database...");
310 await Database.EnsureDeletedAsync(cancellationToken).ConfigureAwait(
false);
313 var migrations = await Database.GetAppliedMigrationsAsync(cancellationToken).ConfigureAwait(
false);
314 var wasEmpty = !migrations.Any();
316 if (wasEmpty || (await Database.GetPendingMigrationsAsync(cancellationToken).ConfigureAwait(
false)).Any())
318 Logger.LogInformation(
"Migrating database...");
319 await Database.MigrateAsync(cancellationToken).ConfigureAwait(
false);
322 Logger.LogDebug(
"No migrations to apply.");
324 wasEmpty |= (await Users.AsQueryable().CountAsync(cancellationToken).ConfigureAwait(
false)) == 0;
328 Logger.LogInformation(
"Seeding database...");
329 await databaseSeeder.SeedDatabase(
this, cancellationToken).ConfigureAwait(
false);
335 Logger.LogWarning(
"Enabling and resetting admin password due to configuration!");
336 await databaseSeeder.ResetAdminPassword(
this, cancellationToken).ConfigureAwait(
false);
339 await databaseSeeder.SanitizeDatabase(
this, cancellationToken).ConfigureAwait(
false);
344 public Task Save(CancellationToken cancellationToken) => SaveChangesAsync(cancellationToken);
350 throw new ArgumentNullException(nameof(version));
351 if (version <
new Version(4, 0))
352 throw new ArgumentOutOfRangeException(nameof(version), version,
"Not a valid V4 version!");
355 string targetMigration = null;
357 if (DatabaseType == DatabaseType.PostgresSql && version <
new Version(4, 3, 0))
358 throw new NotSupportedException(
"Cannot migrate below version 4.3.0 with PostgresSql!");
360 if (version <
new Version(4, 1, 0))
361 throw new NotSupportedException(
"Cannot migrate below version 4.1.0!");
363 if (version <
new Version(4, 2, 0))
366 if (targetMigration == null)
368 Logger.LogDebug(
"No down migration required.");
372 string migrationSubstitution;
373 switch (DatabaseType)
375 case DatabaseType.SqlServer:
377 migrationSubstitution = null;
379 case DatabaseType.MySql:
380 case DatabaseType.MariaDB:
381 migrationSubstitution =
"MY{0}";
383 case DatabaseType.Sqlite:
384 migrationSubstitution =
"SL{0}";
386 case DatabaseType.PostgresSql:
387 migrationSubstitution =
"PG{0}";
390 throw new InvalidOperationException($
"Invalid DatabaseType: {DatabaseType}");
393 if (migrationSubstitution != null)
394 targetMigration = String.Format(CultureInfo.InvariantCulture, migrationSubstitution, targetMigration.Substring(2));
397 var dbServiceProvider = ((IInfrastructure<IServiceProvider>)Database).Instance;
398 var migrator = dbServiceProvider.GetRequiredService<IMigrator>();
400 Logger.LogInformation(
"Migrating down to version {0}. Target: {1}", version, targetMigration);
403 await migrator.MigrateAsync(targetMigration, cancellationToken).ConfigureAwait(
false);
407 Logger.LogCritical(
"Failed to migrate! Exception: {0}", e);
414 protected abstract void ValidateDatabaseType();
IDatabaseCollection< RevisionInformation > RevisionInformations
The RevisionInformations in the IDatabaseContext
IDatabaseCollection< InstanceUser > InstanceUsers
The InstanceUsers in the IDatabaseContext
IDatabaseCollection< ChatChannel > ChatChannels
The ChatChannel in the IDatabaseContext
Use server authentication
readonly IDatabaseCollection< DualReattachInformation > watchdogReattachInformationsCollection
Backing field for IDatabaseContext.WatchdogReattachInformations.
readonly IDatabaseCollection< ChatChannel > chatChannelsCollection
Backing field for IDatabaseContext.ChatChannels.
For initially seeding a database
Fix cascading data deletes for Models.Instances on MSSQL.
IDatabaseCollection< CompileJob > CompileJobs
The CompileJobs in the IDatabaseContext
Backend abstract implementation of IDatabaseContext
IDatabaseCollection< Instance > Instances
The Instances in the IDatabaseContext
Rebuild of the schema for SQLite.
IDatabaseCollection< ChatBot > ChatBots
The ChatBots in the IDatabaseContext
DatabaseType DatabaseType
The Configuration.DatabaseType to create
IDatabaseCollection< RepositorySettings > RepositorySettings
The Models.RepositorySettings in the IDatabaseContext
readonly IDatabaseCollection< Instance > instancesCollection
Backing field for IDatabaseContext.Instances.
readonly IDatabaseSeeder databaseSeeder
The IDatabaseSeeder for the DatabaseContext
readonly IDatabaseCollection< DreamMakerSettings > dreamMakerSettingsCollection
Backing field for IDatabaseContext.DreamMakerSettings.
bool ResetAdminPassword
If the admin user should be enabled and have it's password reset
IDatabaseCollection< DreamMakerSettings > DreamMakerSettings
The Models.DreamMakerSettings in the IDatabaseContext
readonly IDatabaseCollection< User > usersCollection
Backing field for IDatabaseContext.Users.
readonly IDatabaseCollection< RevisionInformation > revisionInformationsCollection
Backing field for IDatabaseContext.RevisionInformations.
Represents an Api.Models.Instance in the database
async Task SchemaDowngradeForServerVersion(Version version, CancellationToken cancellationToken)
Attempt to downgrade the schema to the migration used for a given server version ...
DatabaseContext(DbContextOptions dbContextOptions, IOptions< DatabaseConfiguration > databaseConfigurationOptions, IDatabaseSeeder databaseSeeder, ILogger logger)
Construct a DatabaseContext
readonly IDatabaseCollection< CompileJob > compileJobsCollection
Backing field for IDatabaseContext.CompileJobs.
IDatabaseCollection< DualReattachInformation > WatchdogReattachInformations
The DbSet<TEntity> for DualReattachInformations
Configuration options for the Database.DatabaseContext
DatabaseType
Type of database to user
readonly IDatabaseCollection< InstanceUser > instanceUsersCollection
Backing field for IDatabaseContext.InstanceUsers.
bool DropDatabase
If the database should be deleted on application startup. Should not be used in production! ...
override void OnModelCreating(ModelBuilder modelBuilder)
readonly IDatabaseCollection< DreamDaemonSettings > dreamDaemonSettingsCollection
Backing field for IDatabaseContext.DreamDaemonSettings.
readonly IDatabaseCollection< ChatBot > chatBotsCollection
Backing field for IDatabaseContext.ChatBots.
readonly IDatabaseCollection< RepositorySettings > repositorySettingsCollection
Backing field for IDatabaseContext.RepositorySettings.
IDatabaseCollection< DreamDaemonSettings > DreamDaemonSettings
The Models.DreamDaemonSettings in the IDatabaseContext
IDatabaseCollection< ReattachInformation > ReattachInformations
The DbSet<TEntity> for ReattachInformations
IDatabaseCollection< Job > Jobs
The Jobs in the IDatabaseContext
async Task Initialize(CancellationToken cancellationToken)
Creates and migrates the IDatabaseContext
readonly IDatabaseCollection< ReattachInformation > reattachInformationsCollection
Backing field for IDatabaseContext.ReattachInformations.
IDatabaseCollection< User > Users
The Users in the IDatabaseContext
readonly IDatabaseCollection< Job > jobsCollection
Backing field for IDatabaseContext.Jobs.