Forge.Repository.PostgreSql
The PostgreSQL provider wires up Npgsql.EntityFrameworkCore.PostgreSQL, applies pool settings via NpgsqlConnectionStringBuilder, and includes a CustomHistoryRepository to handle PostgreSQL’s case-sensitive identifier rules for EF Core migrations.
NuGet: Forge.Repository.PostgreSql
Installation
dotnet add package Forge.Repository
dotnet add package Forge.Repository.PostgreSqlRegistration
using Forge.DbTuner;
using Forge.PostgreSql;
using Forge.Interfaces;
builder.Services.AddForgeRepositoryPostgreSql<AppDbContext>(
connectionString: builder.Configuration.GetConnectionString("Default")!,
poolingOptions: new ForgeDbContextPoolingOptions
{
EnablePooling = true,
MinPoolSize = 1,
MaxPoolSize = 50,
},
configure: tuner =>
{
tuner.SetRetry(3); // retry transient failures up to 3 times
tuner.SetTimeout(30); // command timeout of 30 seconds
tuner.ApplySpatial(); // enable spatial data support
tuner.EnableLazyLoading(); // enable lazy loading of navigation properties
});Connection String Reference
// appsettings.json
// Local development
"Default": "Host=localhost;Port=5432;Database=myapp;Username=postgres;Password=postgres"
// Production with SSL
"Default": "Host=prod-pg.internal;Port=5432;Database=myapp;Username=appuser;Password=secret;SSL Mode=Require;Trust Server Certificate=true"
// Supabase / managed PostgreSQL
"Default": "Host=db.abcdefgh.supabase.co;Port=5432;Database=postgres;Username=postgres;Password=yourpassword;SSL Mode=Require"Forge injects Pooling, MinPoolSize, and MaxPoolSize into the Npgsql connection string via NpgsqlConnectionStringBuilder. Do not set these in the raw connection string — use ForgeDbContextPoolingOptions instead.
PostGIS / Spatial Support
Calling tuner.ApplySpatial() adds NetTopologySuite support to the Npgsql provider. This enables EF Core’s geography and geometry types for spatial queries.
# Install NetTopologySuite in your app project
dotnet add package NetTopologySuite// Entity with spatial column
using NetTopologySuite.Geometries;
public class Location : BaseModel
{
public string Name { get; set; } = string.Empty;
public Point Position { get; set; } = null!; // PostGIS POINT
}Custom Migration History Table
When using PostgreSQL, Forge automatically replaces EF Core’s default IHistoryRepository with CustomHistoryRepository. This ensures the __EFMigrationsHistory table is created with a schema that avoids conflicts caused by PostgreSQL’s case-sensitive identifier handling.
No additional configuration is needed — this is wired up inside AddForgeRepositoryPostgreSql automatically.
DbContext
using Forge.Repository;
using Microsoft.EntityFrameworkCore;
public class AppDbContext(DbContextOptions<AppDbContext> options)
: Forge.Repository.DbContext(options)
{
public DbSet<Product> Products { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<Customer> Customers { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//Apply all configurations from assembly
modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
}
}ModelBuilder Configuration
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
public class OrderConfiguration : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> builder)
{
builder.ToTable("Orders");
// Global query filter to exclude soft-deleted records
builder.HasQueryFilter(x => !x.IsDeleted);
// Base
builder.HasKey(x => x.Id);
builder.Property(x => x.Id).HasMaxLength(36);
// Auditable
builder.Property(x => x.CreatedBy).HasMaxLength(36).IsRequired();
builder.Property(x => x.CreatedOn).IsRequired();
builder.Property(x => x.ModifiedBy).HasMaxLength(36).IsRequired(false);
builder.Property(x => x.ModifiedOn);
builder.Property(x => x.IsDeleted).IsRequired();
// Order
builder.Property(x => x.OrderDate).IsRequired();
builder.Property(x => x.TotalAmount).IsRequired().HasPrecision(18, 4);
// Relationships
builder.HasMany(x => x.OrderProducts)
.WithOne(x => x.Order)
.HasForeignKey(x => x.OrderId)
.OnDelete(DeleteBehavior.NoAction);
}
}Migrations
# Add a migration
dotnet ef migrations add InitialCreate --project src/MyApp
# Apply migrations
dotnet ef database update --project src/MyAppApplying migrations programmatically at startup
// Program.cs — apply pending migrations on startup
using (var scope = app.Services.CreateScope())
{
var initializer = scope.ServiceProvider.GetRequiredService<IDbInitializer>();
await initializer.InitializeSqlDb(scope, default);
}DbInitializer
DbInitializer is a Forge-provided utility designed to safely execute EF Core migrations during application startup.
It features an optional ignoreMigration parameter that accepts a target environment name. When this parameter matches the current ASPNETCORE_ENVIRONMENT variable, automatic migrations are bypassed.
This mechanism is especially valuable in development workflows: it prevents a developer’s local workstation from accidentally triggering unintended schema changes when connecting to a shared, UAT, or Production database.
public interface IDbInitializer
{
Task InitializeSqlDb(
IServiceScope scope,
CancellationToken cancellationToken,
string ignoreMigration = "Development");
}// Skip any pending migration if the environment is "Development"
await initializer.InitializeSqlDb(scope, ct, ignoreMigration: "Development");SeedData
Asynchronously seeds initial data for the application within the specified service scope.
public abstract Task SeedData (IServiceScope scope, CancellationToken cancellationToken);The SeedData method is an abstract lifecycle hook designed to be overridden in derived classes to implement custom data seeding logic.
It is automatically invoked during application startup after migrations are successfully applied, ensuring that any mandatory baseline data (e.g., default admin users, lookup tables, static system configurations) is present in the data store.
Because it passes an IServiceScope, you can safely resolve scoped services (like an Entity Framework DbContext) directly within your implementation.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
public class AppDbInitializer(ILogger<AppDbInitializer> logger) : DbInitializer<AppDbContext>(logger)
{
public override async Task SeedData(IServiceScope scope, CancellationToken cancellationToken)
{
// Your seed service
}
}