refactor: pass DbContextOptions into context directly

turns out efcore doesn't like it when we create a new options instance
(which includes a new data source *and* a new logger factory)
every single time we create a context. this commit extracts
OnConfiguring into static methods which are called when the context is
added to the service collection and when it's manually created for
migrations and the importer.
This commit is contained in:
sam 2024-10-30 15:35:23 +01:00
parent 0077a165b5
commit d982342ab8
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
5 changed files with 69 additions and 37 deletions

View file

@ -9,10 +9,42 @@ using Npgsql;
namespace Foxnouns.Backend.Database; namespace Foxnouns.Backend.Database;
public class DatabaseContext : DbContext public class DatabaseContext(DbContextOptions options) : DbContext(options)
{ {
private readonly NpgsqlDataSource _dataSource; private static string GenerateConnectionString(Config.DatabaseConfig config)
private readonly ILoggerFactory? _loggerFactory; {
return new NpgsqlConnectionStringBuilder(config.Url)
{
Pooling = config.EnablePooling ?? true,
Timeout = config.Timeout ?? 5,
MaxPoolSize = config.MaxPoolSize ?? 50,
MinPoolSize = 0,
ConnectionPruningInterval = 10,
ConnectionIdleLifetime = 10,
}.ConnectionString;
}
public static NpgsqlDataSource BuildDataSource(Config config)
{
var dataSourceBuilder = new NpgsqlDataSourceBuilder(
GenerateConnectionString(config.Database)
);
dataSourceBuilder.UseNodaTime();
dataSourceBuilder.UseJsonNet();
return dataSourceBuilder.Build();
}
public static DbContextOptionsBuilder BuildOptions(
DbContextOptionsBuilder options,
NpgsqlDataSource dataSource,
ILoggerFactory? loggerFactory
) =>
options
.ConfigureWarnings(c => c.Ignore(CoreEventId.SaveChangesFailed))
.UseNpgsql(dataSource, o => o.UseNodaTime())
.UseLoggerFactory(loggerFactory)
.UseSnakeCaseNamingConvention()
.UseExceptionProcessor();
public DbSet<User> Users { get; set; } public DbSet<User> Users { get; set; }
public DbSet<Member> Members { get; set; } public DbSet<Member> Members { get; set; }
@ -26,36 +58,6 @@ public class DatabaseContext : DbContext
public DbSet<UserFlag> UserFlags { get; set; } public DbSet<UserFlag> UserFlags { get; set; }
public DbSet<MemberFlag> MemberFlags { get; set; } public DbSet<MemberFlag> MemberFlags { get; set; }
public DatabaseContext(Config config, ILoggerFactory? loggerFactory)
{
var connString = new NpgsqlConnectionStringBuilder(config.Database.Url)
{
Pooling = config.Database.EnablePooling ?? true,
Timeout = config.Database.Timeout ?? 5,
MaxPoolSize = config.Database.MaxPoolSize ?? 50,
MinPoolSize = 0,
ConnectionPruningInterval = 10,
ConnectionIdleLifetime = 10,
}.ConnectionString;
var dataSourceBuilder = new NpgsqlDataSourceBuilder(connString);
dataSourceBuilder.UseNodaTime();
dataSourceBuilder.UseJsonNet();
_dataSource = dataSourceBuilder.Build();
_loggerFactory = loggerFactory;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
optionsBuilder
.ConfigureWarnings(c =>
c.Ignore(CoreEventId.ManyServiceProvidersCreatedWarning)
.Ignore(CoreEventId.SaveChangesFailed)
)
.UseNpgsql(_dataSource, o => o.UseNodaTime())
.UseSnakeCaseNamingConvention()
.UseLoggerFactory(_loggerFactory)
.UseExceptionProcessor();
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{ {
// Snowflakes are stored as longs // Snowflakes are stored as longs
@ -125,6 +127,12 @@ public class DesignTimeDatabaseContextFactory : IDesignTimeDbContextFactory<Data
// Get the configuration as our config class // Get the configuration as our config class
.Get<Config>() ?? new(); .Get<Config>() ?? new();
return new DatabaseContext(config, null); var dataSource = DatabaseContext.BuildDataSource(config);
var options = DatabaseContext
.BuildOptions(new DbContextOptionsBuilder(), dataSource, null)
.Options;
return new DatabaseContext(options);
} }
} }

View file

@ -0,0 +1,21 @@
using Serilog;
namespace Foxnouns.Backend.Database;
public static class DatabaseServiceExtensions
{
public static IServiceCollection AddFoxnounsDatabase(
this IServiceCollection serviceCollection,
Config config
)
{
var dataSource = DatabaseContext.BuildDataSource(config);
var loggerFactory = new LoggerFactory().AddSerilog(dispose: false);
serviceCollection.AddDbContext<DatabaseContext>(options =>
DatabaseContext.BuildOptions(options, dataSource, loggerFactory)
);
return serviceCollection;
}
}

View file

@ -85,7 +85,7 @@ public static class WebApplicationExtensions
services services
.AddQueue() .AddQueue()
.AddSmtpMailer(ctx.Configuration) .AddSmtpMailer(ctx.Configuration)
.AddDbContext<DatabaseContext>() .AddFoxnounsDatabase(config)
.AddMetricServer(o => o.Port = config.Logging.MetricsPort) .AddMetricServer(o => o.Port = config.Logging.MetricsPort)
.AddMinio(c => .AddMinio(c =>
c.WithEndpoint(config.Storage.Endpoint) c.WithEndpoint(config.Storage.Endpoint)

View file

@ -2,7 +2,6 @@ using System.Net;
using System.Web; using System.Web;
using Foxnouns.Backend.Database; using Foxnouns.Backend.Database;
using Foxnouns.Backend.Database.Models; using Foxnouns.Backend.Database.Models;
using Minio.DataModel.ILM;
using Duration = NodaTime.Duration; using Duration = NodaTime.Duration;
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;

View file

@ -56,7 +56,11 @@ internal static class NetImporter
var loggerFactory = new LoggerFactory().AddSerilog(Log.Logger); var loggerFactory = new LoggerFactory().AddSerilog(Log.Logger);
var config = new Config { Database = new Config.DatabaseConfig { Url = connString } }; var config = new Config { Database = new Config.DatabaseConfig { Url = connString } };
var db = new DatabaseContext(config, loggerFactory); var dataSource = DatabaseContext.BuildDataSource(config);
var options = DatabaseContext
.BuildOptions(new DbContextOptionsBuilder(), dataSource, loggerFactory)
.Options;
var db = new DatabaseContext(options);
if ((await db.Database.GetPendingMigrationsAsync()).Any()) if ((await db.Database.GetPendingMigrationsAsync()).Any())
{ {