|
| 1 | +# Pandatech.SharedKernel.Postgres |
| 2 | + |
| 3 | +PostgreSQL integration helpers for ASP.NET Core 10. Wraps Npgsql, Entity Framework Core, health checks, exception |
| 4 | +mapping, query locks, snake_case naming, and audit trail wiring into a small set of extension methods so every service |
| 5 | +starts from the same consistent baseline. |
| 6 | + |
| 7 | +Requires **.NET 10.0**. Uses C# 14 extension members and cannot be downgraded. |
| 8 | + |
| 9 | +--- |
| 10 | + |
| 11 | +## Table of Contents |
| 12 | + |
| 13 | +1. [Installation](#installation) |
| 14 | +2. [What is included](#what-is-included) |
| 15 | +3. [Registering a DbContext](#registering-a-dbcontext) |
| 16 | +4. [Migrations](#migrations) |
| 17 | +5. [Model configuration helpers](#model-configuration-helpers) |
| 18 | +6. [Health checks](#health-checks) |
| 19 | + |
| 20 | +--- |
| 21 | + |
| 22 | +## Installation |
| 23 | + |
| 24 | +```bash |
| 25 | +dotnet add package Pandatech.SharedKernel.Postgres |
| 26 | +``` |
| 27 | + |
| 28 | +--- |
| 29 | + |
| 30 | +## What is included |
| 31 | + |
| 32 | +Every `AddPostgresContext*` overload automatically applies the following to your DbContext: |
| 33 | + |
| 34 | +| Feature | Source package | |
| 35 | +|-----------------------------------|---------------------------------------------| |
| 36 | +| Npgsql provider | `Npgsql.EntityFrameworkCore.PostgreSQL` | |
| 37 | +| Snake_case naming convention | `EFCore.NamingConventions` | |
| 38 | +| Query locks (`FOR UPDATE` etc.) | `Pandatech.EFCore.PostgresExtensions` | |
| 39 | +| Friendly exception mapping | `EntityFrameworkCore.Exceptions.PostgreSQL` | |
| 40 | +| `AuditBase` property validation | `Pandatech.EFCore.AuditBase` | |
| 41 | +| Audit trail interceptors (opt-in) | `Pandatech.EFCore.Audit` | |
| 42 | +| Postgres health check | `AspNetCore.HealthChecks.NpgSql` | |
| 43 | +| Bulk extensions | `EFCore.BulkExtensions.PostgreSql` | |
| 44 | +| Gridify query extensions | `Pandatech.GridifyExtensions` | |
| 45 | + |
| 46 | +--- |
| 47 | + |
| 48 | +## Registering a DbContext |
| 49 | + |
| 50 | +All overloads are on `WebApplicationBuilder` and return `WebApplicationBuilder` for chaining. Every variant |
| 51 | +automatically registers a Postgres health check named `postgres_{DatabaseName}`. |
| 52 | + |
| 53 | +### Basic registration (no pooling, no audit trail) |
| 54 | + |
| 55 | +```csharp |
| 56 | +// Minimal — connection string only |
| 57 | +builder.AddPostgresContext<AppDbContext>(connectionString); |
| 58 | + |
| 59 | +// With migrations assembly by name |
| 60 | +builder.AddPostgresContext<AppDbContext>(connectionString, "MyApp.Migrations"); |
| 61 | + |
| 62 | +// With migrations assembly by Assembly reference |
| 63 | +builder.AddPostgresContext<AppDbContext>(connectionString, typeof(Program).Assembly); |
| 64 | + |
| 65 | +// With migrations assembly by marker type |
| 66 | +builder.AddPostgresContext<AppDbContext, Program>(connectionString); |
| 67 | + |
| 68 | +// Full control via NpgsqlDbContextOptionsBuilder callback |
| 69 | +builder.AddPostgresContext<AppDbContext>(connectionString, npgsql => |
| 70 | +{ |
| 71 | + npgsql.MigrationsAssembly("MyApp.Migrations"); |
| 72 | + npgsql.CommandTimeout(60); |
| 73 | +}); |
| 74 | +``` |
| 75 | + |
| 76 | +### With connection pooling |
| 77 | + |
| 78 | +Same set of overloads, prefixed with `Pool`: |
| 79 | + |
| 80 | +```csharp |
| 81 | +builder.AddPostgresContextPool<AppDbContext>(connectionString); |
| 82 | +builder.AddPostgresContextPool<AppDbContext>(connectionString, "MyApp.Migrations"); |
| 83 | +builder.AddPostgresContextPool<AppDbContext, Program>(connectionString); |
| 84 | +builder.AddPostgresContextPool<AppDbContext>(connectionString, npgsql => { ... }); |
| 85 | +``` |
| 86 | + |
| 87 | +Use pooling for high-throughput services. Note that DbContext pooling requires that your context has no scoped |
| 88 | +dependencies injected via the constructor; use service provider overloads (`(sp, options) => ...`) for those cases. |
| 89 | + |
| 90 | +### With audit trail (no pooling) |
| 91 | + |
| 92 | +Registers the `Pandatech.EFCore.Audit` interceptors alongside the standard options: |
| 93 | + |
| 94 | +```csharp |
| 95 | +builder.AddPostgresContextWithAuditTrail<AppDbContext>(connectionString); |
| 96 | +builder.AddPostgresContextWithAuditTrail<AppDbContext>(connectionString, "MyApp.Migrations"); |
| 97 | +builder.AddPostgresContextWithAuditTrail<AppDbContext, Program>(connectionString); |
| 98 | +builder.AddPostgresContextWithAuditTrail<AppDbContext>(connectionString, npgsql => { ... }); |
| 99 | +``` |
| 100 | + |
| 101 | +### With pooling and audit trail |
| 102 | + |
| 103 | +```csharp |
| 104 | +builder.AddPostgresContextPoolWithAuditTrail<AppDbContext>(connectionString); |
| 105 | +builder.AddPostgresContextPoolWithAuditTrail<AppDbContext>(connectionString, "MyApp.Migrations"); |
| 106 | +builder.AddPostgresContextPoolWithAuditTrail<AppDbContext, Program>(connectionString); |
| 107 | +builder.AddPostgresContextPoolWithAuditTrail<AppDbContext>(connectionString, npgsql => { ... }); |
| 108 | +``` |
| 109 | + |
| 110 | +--- |
| 111 | + |
| 112 | +## Migrations |
| 113 | + |
| 114 | +Apply pending migrations at startup: |
| 115 | + |
| 116 | +```csharp |
| 117 | +// Synchronous |
| 118 | +app.MigrateDatabase<AppDbContext>(); |
| 119 | + |
| 120 | +// Asynchronous |
| 121 | +await app.MigrateDatabaseAsync<AppDbContext>(ct); |
| 122 | +``` |
| 123 | + |
| 124 | +Both create a scoped service provider, resolve the context, and call `Database.Migrate` / `Database.MigrateAsync`. |
| 125 | +Place these calls after `app.Build()` and before `app.Run()`. |
| 126 | + |
| 127 | +--- |
| 128 | + |
| 129 | +## Model configuration helpers |
| 130 | + |
| 131 | +Add to `ConfigureConventions` and `OnModelCreating` in your DbContext: |
| 132 | + |
| 133 | +```csharp |
| 134 | +protected override void ConfigureConventions(ModelConfigurationBuilder builder) |
| 135 | +{ |
| 136 | + // Map all decimal properties to NUMERIC(40, 20) — prevents precision loss |
| 137 | + builder.ConfigureDecimalType(); |
| 138 | +} |
| 139 | + |
| 140 | +protected override void OnModelCreating(ModelBuilder modelBuilder) |
| 141 | +{ |
| 142 | + // Set all foreign key delete behavior to Restrict (no accidental cascades) |
| 143 | + modelBuilder.RestrictFkDeleteBehaviorByDefault(); |
| 144 | +} |
| 145 | +``` |
| 146 | + |
| 147 | +`ConfigureDecimalType` sets precision `(40, 20)` globally. Override individual properties via `[Precision]` or fluent |
| 148 | +API if you need a narrower type for a specific column. |
| 149 | + |
| 150 | +`RestrictFkDeleteBehaviorByDefault` iterates all foreign keys at model build time and sets `DeleteBehavior.Restrict`. |
| 151 | +Override individual relationships fluently after this call if cascade delete is intentionally needed. |
| 152 | + |
| 153 | +--- |
| 154 | + |
| 155 | +## Health checks |
| 156 | + |
| 157 | +The health check is registered automatically by every `AddPostgresContext*` overload. It uses |
| 158 | +`AspNetCore.HealthChecks.NpgSql` with a 5-second timeout and is named `postgres_{DatabaseName}` where the database |
| 159 | +name is parsed from the connection string. |
| 160 | + |
| 161 | +To expose the health check endpoint, use `MapHealthCheckEndpoints()` from `Pandatech.SharedKernel`: |
| 162 | + |
| 163 | +```csharp |
| 164 | +app.MapHealthCheckEndpoints(); // /above-board/health |
| 165 | +``` |
| 166 | + |
| 167 | +Or register your own endpoint: |
| 168 | + |
| 169 | +```csharp |
| 170 | +app.MapHealthChecks("/health"); |
| 171 | +``` |
| 172 | + |
| 173 | +If the database name cannot be parsed from the connection string, registration throws `ArgumentException` at startup |
| 174 | +rather than silently registering a misnamed check. |
| 175 | + |
| 176 | +--- |
| 177 | + |
| 178 | +## License |
| 179 | + |
| 180 | +MIT |
0 commit comments