For twenty years the .NET default was obvious: SQL Server, because it came from the same company as the runtime and SSMS was the best free database tool on earth. That default has quietly stopped being automatic. This post is the decision, written by someone who has run both in production behind .NET apps — including a PostgreSQL on the very server that delivered this page — and who still likes SQL Server. The differences that matter are money, ecosystem and a handful of habits, not capability.

The short answer: for a new .NET application in 2026, PostgreSQL is the better default. It's free with no edition matrix, the Npgsql EF Core provider is first-class, it runs anywhere from a Docker container to every cloud's managed service, and jsonb plus the extension ecosystem give you things SQL Server charges for or doesn't have. Stay on SQL Server when you're already deep in it — T-SQL procedures, SSIS packages, SSRS reports, Agent jobs, Windows authentication — when Azure SQL is your platform, or when your team's fluency in SSMS is a real asset you'd be throwing away. Both run .NET beautifully; the question is what else you're paying for.

Licensing and cost

SQL Server has a free tier and a bill. Express is free and capped (ten gigabytes per database is the cap people hit); Developer is free but not for production; Standard and Enterprise are licensed per core (Standard also sells as server-plus-CALs), and in the cloud, where cores are metered by the hour, that line item grows with your success. Managed SQL Server on AWS or Azure bakes the license into the hourly price, which is why the same instance size costs markedly more than its PostgreSQL neighbor. PostgreSQL is released under its own permissive license: no editions, no client access licenses, no audit. Every cloud runs it managed — Azure Database for PostgreSQL, Amazon RDS and Aurora, Google Cloud SQL, plus newer platforms built entirely around it — and a five-dollar VPS runs it too. ClinicLive's development database is one Docker line, and its tests run against a real postgres:18 container in CI. Part 1 of the migration series makes the fuller case.

EF Core: Npgsql versus SqlClient

This is the row .NET developers worry about most and the one that has aged best. Microsoft.EntityFrameworkCore.SqlServer is Microsoft's own provider on top of Microsoft.Data.SqlClient, and it's the one the EF team tests first. Npgsql.EntityFrameworkCore.PostgreSQL is open source, maintained by the Npgsql team, and ships in step with each EF Core release; its lead maintainer also works on EF Core itself, which is as close to first-party as a community provider gets. ClinicLive uses it without ceremony — the only PostgreSQL-specific line in the registration is the naming convention:

// Factory, not plain AddDbContext: interactive Blazor components outlive a request,
// so each operation needs its own short-lived context.
builder.Services.AddDbContextFactory<ApplicationDbContext>(options =>
    options.UseNpgsql(connectionString).UseSnakeCaseNamingConvention());

What differs in practice: identifiers fold to lowercase in PostgreSQL, so the idiomatic schema is snake_case and the EFCore.NamingConventions package maps your OrderItem class to order_items without touching a single entity; migrations regenerate because they contain provider SQL; and Npgsql exposes PostgreSQL's richer types — jsonb, arrays, ranges, citext — as ordinary C# properties. One rule catches everyone once: a DateTime mapped to timestamptz must have Kind set to UTC, or Npgsql throws. LINQ translates the same on both; raw SQL is where the dialect shows. Part 8 walks through the swap.

Tooling: SSMS versus pgAdmin and psql

What you reach for on each side
JobSQL ServerPostgreSQL
The everyday GUISSMS — free, Windows-only, superbpgAdmin 4 — free, cross-platform, adequate; DBeaver or DataGrip if you want SSMS-like comfort
Quick scriptingSSMS query window, sqlcmdpsql — the tool you'll resist and then love
Execution plansGraphical plans in SSMSEXPLAIN ANALYZE text, graphical in pgAdmin and DBeaver
Who's running whatActivity Monitor, sp_who2pg_stat_activity
In the editorVisual Studio's SQL tools, the VS Code MSSQL extensionRider and VS Code extensions; every editor speaks Postgres

Opinion, and I'll own it: SSMS is still the best free database GUI ever shipped, and nothing on the PostgreSQL side is quite as complete in one window. The PostgreSQL answer is that you stop needing one window. psql with \d orders describes a table faster than any tree view, DBeaver covers both engines if you must keep one GUI, and every editor has a decent Postgres extension. Budget a week of mild grumpiness, not a month.

Hosting options

SQL Server runs on Windows, on Linux, and in the official Docker image, and it's a first-class citizen on Azure — Azure SQL Database and Managed Instance are genuinely excellent, and if your organization already lives there, that is a strong reason to stay. AWS offers it managed too, license included. PostgreSQL runs everywhere the same way, plus the tiny end of the market SQL Server never served: a container on a laptop, a shared VPS, a free tier on a serverless Postgres platform. The practical difference is the floor. The cheapest production SQL Server is a license decision; the cheapest production PostgreSQL is an apt install.

JSON: jsonb versus JSON functions

PostgreSQL's jsonb is a real column type: binary-stored, validated on write, indexable with a GIN index, and queried with operators the planner understands. SQL Server's approach, since 2016, has been functions over text — JSON_VALUE, JSON_QUERY, OPENJSON, FOR JSON — on an NVARCHAR(MAX) column, with a native json type arriving in the newest release and Azure SQL. The everyday query shows the difference:

-- PostgreSQL: a real type, an index the query can use
CREATE INDEX ix_events_data ON events USING gin (data);
SELECT * FROM events WHERE data @> '{"status": "active"}';

-- SQL Server: functions over a string column
SELECT * FROM Events WHERE JSON_VALUE(Data, '$.status') = 'active';

Both work. The PostgreSQL version indexes the containment query directly; the SQL Server version needs a computed column and an index on that to avoid a scan. On the EF side, both providers map a C# object graph to a JSON column and let LINQ query inside it; Npgsql also maps a bare jsonb column to a POCO or a JsonDocument and exposes the containment operator through EF.Functions. If your schema has a "properties bag" column, Part 3 explains why it should become jsonb rather than be ported one-to-one.

Full-text search

SQL Server's Full-Text Search is a separate feature with its own catalogs, its own population process and the CONTAINS and FREETEXT predicates; it's powerful, and it's a component you install, configure and wait for. PostgreSQL's full-text search is built into the core engine: a tsvector column, a GIN index on it, and the match operator:

ALTER TABLE articles ADD COLUMN search tsvector
    GENERATED ALWAYS AS (to_tsvector('english', title || ' ' || body)) STORED;
CREATE INDEX ix_articles_search ON articles USING gin (search);

SELECT title FROM articles WHERE search @@ plainto_tsquery('english', 'migration plan');

The index updates with the row, in the same transaction, with nothing to schedule. For fuzzy matching and fast LIKE '%term%', the pg_trgm extension adds trigram indexes, which SQL Server has no direct equivalent for. The Npgsql EF provider exposes full-text search through EF.Functions, so it stays in LINQ.

Procedures: T-SQL versus PL/pgSQL

This is the row that decides most migrations, because it's the one with real work in it. T-SQL is one big tent — a procedure returns result sets, sets output parameters and manages transactions in the same batch. PL/pgSQL splits it: functions return values and can be used inside a SELECT; procedures (called with CALL) can commit mid-flight but don't return results the same way. Bodies are dollar-quoted, variables have no @ prefix, PRINT becomes RAISE NOTICE, TRY/CATCH becomes an EXCEPTION block, and there's no GO. The behavior that bites hardest isn't syntax: in PostgreSQL, any error aborts the whole transaction until you roll back or use a savepoint, where SQL Server by default carries on past a failed statement. Written down, it's a week of translation; Part 5 does one real procedure line by line. My advice for either engine, offered as opinion: keep business logic in C# where it has tests, and let procedures be thin.

The side-by-side

PostgreSQL and SQL Server for a .NET application
SQL ServerPostgreSQLEdge
CostFree Express (capped) and Developer; per-core Standard and EnterpriseFree, everywhere, no editionsPostgreSQL
EF Core providerMicrosoft's ownNpgsql, open source, ships with each EF releaseTie
ToolingSSMSpgAdmin, psql, DBeaverSQL Server, on polish
HostingWindows, Linux, Docker, Azure SQL, RDSEverything, down to a $5 VPSPostgreSQL, on range
JSONFunctions over text; native type in the newest releasejsonb with GIN indexes and operatorsPostgreSQL
Full-text searchSeparate feature, catalogs, populationBuilt in, transactional, plus pg_trgmPostgreSQL
ProceduresT-SQL, one tentPL/pgSQL, functions and proceduresFamiliarity: SQL Server
ConcurrencyLocking by default; snapshot isolation opt-inMVCC by default; readers never block writers; VACUUM to maintainDepends on your habits
Around the engineAgent, SSIS, SSRS, linked servers, Windows authpg_cron, foreign data wrappers, external toolsSQL Server, if you use them
ExtensionsCLR, limitedPostGIS, pgvector, pg_trgm, pg_cron, and hundreds morePostgreSQL

What hurts in a migration

  • Case. Unquoted identifiers fold to lowercase, and quoted ones are case-sensitive forever; string comparison is case-sensitive by default where SQL Server's usual collations are not. The fixes are snake_case, citext or ILIKE, and an index on lower(email)Part 2 and Part 4.
  • Timestamps. DATETIME2 becomes timestamp or timestamptz, and choosing wrong is the number-one migration bug — Part 3.
  • No cross-database queries. OtherDb.dbo.Table doesn't exist; schemas in one database, or a foreign data wrapper.
  • The procedures, as above, and everything dynamic or cursor-shaped inside them.
  • The things around the engine. SQL Agent, SSIS, SSRS and linked servers have answers but not drop-in replacements; "we only have two SSIS packages" is the longest line item in most plans.
  • Instincts. There is no clustered index, readers don't block writers, and VACUUM is a thing you now own — Part 6. Backups are pg_dump and WAL archiving, not .bak files — Part 7.

You don't have to guess how much of that applies to you. Paste a scripted database into the free migration assessment tool: it inventories tables, views, procedures, functions, triggers and indexes, runs the translator over the mechanical parts, and flags what genuinely needs a person — cursors, dynamic SQL, linked servers, Agent jobs. Then let the T-SQL to PostgreSQL translator do the phrasebook work, with every change explained and linked to the part that teaches it. Both run in-process and store nothing.

What you gain

  • The bill, and with it the freedom to run a real database on every laptop, in every CI job and on every tiny server.
  • Types you'll wonder how you lived without: jsonb, arrays, ranges, a real boolean, uuidv7() built in.
  • Phrasing that says what it means: RETURNING instead of SCOPE_IDENTITY(), INSERT … ON CONFLICT for upserts, LIMIT that reads like English.
  • A wider index shelf: partial and expression indexes, GIN for JSON and search, BRIN for huge append-only tables, all covered in Part 6.
  • Extensions instead of features waiting for the next major version: PostGIS for geography, pgvector for embeddings, pg_trgm for fuzzy search, pg_cron for scheduling.
  • Portability. Skills, schemas and dumps that move between clouds and employers unchanged.

Honestly, it depends

The strongest case for SQL Server isn't a feature; it's inertia, and inertia is a real cost. A team with a decade of T-SQL, SSRS reports the finance department lives in, Agent jobs nobody remembers writing and an Azure SQL bill already approved has nothing to gain from a migration this year and plenty to lose. Azure SQL in particular is a superb managed service with automatic tuning and point-in-time restore that just works, and if your identity story is Entra and Windows authentication end to end, SQL Server fits it like a glove. The migration series exists for teams that have decided; this post is for teams that are still deciding, and "stay" is a legitimate answer.

Two more honest notes. A SQL Server developer's first month on PostgreSQL feels like a downgrade — the GUI is plainer, errors are terser, and the case-folding rule will bite on day one; the series' Part 1 promises the differences are learnable in a focused week, and that has matched my experience, but the week is real. And the decision is independent of your front end: whether the app is Blazor Server, WebAssembly or a MAUI app — see Blazor Server vs WebAssembly vs Hybrid — the database under it faces exactly the choice above.

If you only remember one thing: both engines run .NET well, so decide on what surrounds the engine. If you're paying for cores or planning to, PostgreSQL. If you're paying in T-SQL, SSIS and SSRS you can't replace, SQL Server. Everything else — EF Core, LINQ, performance for an ordinary app — is a wash.

Frequently asked

Is PostgreSQL better than SQL Server for .NET applications?
For a new .NET application, PostgreSQL is usually the better default: it is free with no edition limits, the Npgsql EF Core provider is mature and ships with each EF Core release, and it runs anywhere from a Docker container to every cloud. SQL Server remains the right choice when a team already depends on T-SQL procedures, SSIS, SSRS, SQL Agent or Azure SQL, because both engines run .NET equally well and the difference is cost and ecosystem rather than capability.
Does Entity Framework Core work well with PostgreSQL?
Yes. The Npgsql.EntityFrameworkCore.PostgreSQL provider supports migrations, LINQ translation, JSON columns, arrays and full-text search, and is maintained in step with EF Core. Switching from SQL Server is mostly changing UseSqlServer to UseNpgsql, adding the snake_case naming convention package, regenerating migrations and translating any raw SQL.
What is the hardest part of migrating from SQL Server to PostgreSQL?
Stored procedures and everything around the engine. T-SQL procedures must be rewritten in PL/pgSQL, and SQL Agent jobs, SSIS packages, SSRS reports and linked servers have alternatives but no drop-in replacements. The schema and queries are mostly mechanical, with case-sensitive identifiers and string comparison and the timestamp versus timestamptz choice as the classic bugs.

If the answer for you is PostgreSQL, the eight-part SQL Server to PostgreSQL series is the translation guide, written for people who already know SQL Server deeply, and the two free tools above will tell you how big your particular move is before you commit to it. If the answer is SQL Server, you've lost nothing by checking — and you'll know exactly what you're paying for.