I just like this so much!
Experimenting with ef-core just got so much simpler.
With a simple script like this you get instant access to ef-core's generated sql!
// Kristoffer.cs
#:package Microsoft.EntityFrameworkCore.Design@10.0.0-preview.5.25277.114
#:package Npgsql.EntityFrameworkCore.PostgreSQL@10.0.0-preview.5
#:package Microsoft.EntityFrameworkCore@10.0.0-preview.5.25277.114
#:package Testcontainers@4.6.0
#:package Testcontainers.PostgreSql@4.6.0
// .NET Usings
using Microsoft.EntityFrameworkCore;
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Containers;
using Microsoft.Extensions.Logging;
// --- Application Logic ---
Console.WriteLine("🚀 Starting PostgreSQL container...");
await using var postgresContainer = new ContainerBuilder()
.WithImage("postgres:16-alpine")
.WithPortBinding(5432, true) // Map to a random host port
.WithEnvironment("POSTGRES_DB", "mydatabase")
.WithEnvironment("POSTGRES_USER", "myuser")
.WithEnvironment("POSTGRES_PASSWORD", "mypassword")
.WithWaitStrategy(Wait.ForUnixContainer().UntilCommandIsCompleted("pg_isready -U myuser -d mydatabase"))
.Build();
await postgresContainer.StartAsync();
var connectionString = $"Host={postgresContainer.Hostname};Port={postgresContainer.GetMappedPublicPort(5432)};Database=mydatabase;Username=myuser;Password=mypassword;Include Error Detail=true;";
Console.WriteLine($"✅ PostgreSQL container running on: {postgresContainer.Hostname}:{postgresContainer.GetMappedPublicPort(5432)}");
// Configure and use the database context
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseNpgsql(connectionString)
.EnableDetailedErrors().
EnableSensitiveDataLogging().
LogTo(Console.WriteLine,
LogLevel.Information)
.Options;
await using var dbContext = new AppDbContext(options);
await dbContext.Database.EnsureCreatedAsync();
// --- Data Operations ---
Console.WriteLine("\nAdding a new product...");
var newProduct = new Product { Name = "Keyboard", Price = 75.00m };
dbContext.Products.Add(newProduct);
await dbContext.SaveChangesAsync();
Console.WriteLine($"Added product: '{newProduct.Name}'");
Console.WriteLine("\nFetching all products...");
var products = await dbContext.Products.TagWith("Hello").TagWith("World").ToListAsync();
foreach (var product in products)
{
Console.WriteLine($" - ID: {product.Id}, Name: {product.Name}, Price: {product.Price:C}");
}
Console.WriteLine("\nTestcontainers operation completed. Stopping container...");
// --- Entity and DbContext Definitions ---
public class Product
{
public int Id { get; set; }
public required string Name { get; set; }
public decimal Price { get; set; }
}
public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
{
public DbSet<Product> Products { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Seed initial data
modelBuilder.Entity<Product>().HasData(
new Product { Id = 1, Name = "Laptop", Price = 1200.00m },
new Product { Id = 2, Name = "Mouse", Price = 25.00m }
);
}
}
Requirements
- dotnet 10 sdk/runtime
- docker or podman
Result:
> dotnet run Kristoffer.cs
__________________________________________________
Project "/Users/sukkerfrit/private/headles-cms/Kristoffer.csproj" (Build target(s)):
/Users/sukkerfrit/private/headles-cms/Kristoffer.cs(75,27): warning CS8618: Non-nullable property 'Products' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
Done building project "Kristoffer.csproj".
🚀 Starting PostgreSQL container...
[testcontainers.org 00:00:00.05] Connected to Docker:
Host: unix:///var/run/docker.sock
Server Version: 28.2.2
Kernel Version: 6.10.14-linuxkit
API Version: 1.50
Operating System: Docker Desktop
Total Memory: 26.14 GB
Labels:
com.docker.desktop.address=unix:///Users/sukkerfrit/Library/Containers/com.docker.docker/Data/docker-cli.sock
[testcontainers.org 00:00:00.14] Docker container 40986fe4400b created
[testcontainers.org 00:00:00.16] Start Docker container 40986fe4400b
[testcontainers.org 00:00:00.23] Wait for Docker container 40986fe4400b to complete readiness checks
[testcontainers.org 00:00:00.23] Docker container 40986fe4400b ready
[testcontainers.org 00:00:00.29] Docker container 8258a64bb442 created
[testcontainers.org 00:00:00.30] Start Docker container 8258a64bb442
[testcontainers.org 00:00:00.37] Wait for Docker container 8258a64bb442 to complete readiness checks
[testcontainers.org 00:00:00.38] Execute "/bin/sh -c pg_isready -U myuser -d mydatabase" at Docker container 8258a64bb442
[testcontainers.org 00:00:01.43] Execute "/bin/sh -c pg_isready -U myuser -d mydatabase" at Docker container 8258a64bb442
[testcontainers.org 00:00:01.49] Docker container 8258a64bb442 ready
✅ PostgreSQL container running on: 127.0.0.1:60363
warn: 6/27/2025 08:03:20.934 CoreEventId.SensitiveDataLoggingEnabledWarning[10400] (Microsoft.EntityFrameworkCore.Infrastructure)
Sensitive data logging is enabled. Log entries and exception messages may include sensitive application data; this mode should only be enabled during development.
info: 6/27/2025 08:03:21.064 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (20ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT CASE WHEN COUNT(*) = 0 THEN FALSE ELSE TRUE END
FROM pg_class AS cls
JOIN pg_namespace AS ns ON ns.oid = cls.relnamespace
WHERE
cls.relkind IN ('r', 'v', 'm', 'f', 'p') AND
ns.nspname NOT IN ('pg_catalog', 'information_schema') AND
-- Exclude tables which are members of PG extensions
NOT EXISTS (
SELECT 1 FROM pg_depend WHERE
classid=(
SELECT cls.oid
FROM pg_class AS cls
JOIN pg_namespace AS ns ON ns.oid = cls.relnamespace
WHERE relname='pg_class' AND ns.nspname='pg_catalog'
) AND
objid=cls.oid AND
deptype IN ('e', 'x')
)
info: 6/27/2025 08:03:21.120 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (12ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE "Products" (
"Id" integer GENERATED BY DEFAULT AS IDENTITY,
"Name" text NOT NULL,
"Price" numeric NOT NULL,
CONSTRAINT "PK_Products" PRIMARY KEY ("Id")
);
info: 6/27/2025 08:03:21.121 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
INSERT INTO "Products" ("Id", "Name", "Price")
VALUES (1, 'Laptop', 1200.0);
INSERT INTO "Products" ("Id", "Name", "Price")
VALUES (2, 'Mouse', 25.0);
info: 6/27/2025 08:03:21.123 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (2ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT setval(
pg_get_serial_sequence('"Products"', 'Id'),
GREATEST(
(SELECT MAX("Id") FROM "Products") + 1,
nextval(pg_get_serial_sequence('"Products"', 'Id'))),
false);
Adding a new product...
info: 6/27/2025 08:03:21.167 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (7ms) [Parameters=[@p0='Keyboard' (Nullable = false), @p1='75.00'], CommandType='Text', CommandTimeout='30']
INSERT INTO "Products" ("Name", "Price")
VALUES (@p0, @p1)
RETURNING "Id";
Added product: 'Keyboard'
Fetching all products...
info: 6/27/2025 08:03:21.240 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (2ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
-- Hello
-- World
SELECT p."Id", p."Name", p."Price"
FROM "Products" AS p
- ID: 1, Name: Laptop, Price: $1,200.00
- ID: 2, Name: Mouse, Price: $25.00
- ID: 3, Name: Keyboard, Price: $75.00
Testcontainers operation completed. Stopping container...
[testcontainers.org 00:00:02.02] Delete Docker container 8258a64bb442
Enjoy :)