One concept, five dialects
A connection string is conceptually the same thing everywhere: enough information for a driver to open a socket, authenticate, and select a database. In practice, every ADO.NET provider invented its own key names for that same information, and mixing them up doesn't throw a helpful compile-time error — it throws a runtime connection failure, usually with a message that doesn't mention which key was wrong.
The provider comparison
// SQL Server (Microsoft.Data.SqlClient)
Server=localhost;Database=AppDb;User Id=appuser;Password=***;Encrypt=True;TrustServerCertificate=False;
// Azure SQL — tcp: prefix, explicit port 1433, encryption mandatory
Server=tcp:myserver.database.windows.net,1433;Initial Catalog=AppDb;User ID=appuser;Password=***;Encrypt=True;TrustServerCertificate=False;
// PostgreSQL (Npgsql)
Host=localhost;Port=5432;Database=AppDb;Username=appuser;Password=***;SSL Mode=Require;
// MySQL (MySqlConnector / MySql.Data)
Server=localhost;Port=3306;Database=AppDb;Uid=appuser;Pwd=***;SslMode=Required;
// Cosmos DB
AccountEndpoint=https://my-account.documents.azure.com:443/;AccountKey=***;
Four different names for "the username" (User Id, User ID, Username, Uid), three different names for "the password" (Password, Pwd, AccountKey), and Azure SQL's requirement for a tcp: prefix plus explicit port that on-prem SQL Server doesn't need at all. None of this is discoverable from the driver's compile-time API — the connection string is just a string until the provider parses it at runtime, so a typo'd key name is usually silently ignored rather than rejected, and you're left debugging why a setting you definitely configured "isn't taking effect."
Encrypt and TrustServerCertificate: the flags that actually matter
Encrypt=True wraps the connection in TLS, so credentials and query results can't be read by anything positioned between your app and the database. Azure SQL has required an encrypted connection since 2022 and rejects TrustServerCertificate=True for its own hostnames outright. On-premises SQL Server has no such requirement baked in — it will happily hand you an unencrypted connection if you don't ask for Encrypt=True explicitly, which is exactly the gap that makes a dev-to-prod parity check worth doing before every deployment.
TrustServerCertificate=True skips validating the server's TLS certificate against a trusted root. It exists for one legitimate reason — connecting to a local SQL Server instance using a self-signed development certificate — and it defeats the entire purpose of Encrypt=True if it ships to production, because it means your app will happily encrypt a connection to anything presenting any certificate at all, including a machine-in-the-middle. If TrustServerCertificate=True shows up in a production connection string, that's not a style preference, it's a real gap.
The default that changed under everyone's feet
Historically, Encrypt defaulted to false in both the legacy System.Data.SqlClient and early Microsoft.Data.SqlClient — meaning a connection string with no Encrypt key at all was unencrypted by default.
More recent versions of Microsoft.Data.SqlClient changed this default to encourage encryption out of the box, which is a good direction — but it also means the exact same connection string, with Encrypt simply omitted, can behave differently depending on which package version your project references. If a connection behaves differently after a NuGet update, checking the installed Microsoft.Data.SqlClient version against its changelog is worth doing before assuming your connection string itself is the problem.
Connection pooling and MARS: the silent defaults
Every SQL Server connection string is also implicitly a pooling configuration, whether you write any pooling keys or not. Max Pool Size defaults to 100 — under normal load that's plenty, but a service with a connection leak (a SqlConnection not disposed, or an await using missing on a hot path) will exhaust that pool under load and start throwing InvalidOperationException: Timeout expired errors that have nothing to do with the database itself being slow. Raising Max Pool Size masks the symptom; it doesn't fix a leak.
MultipleActiveResultSets=True (MARS) allows more than one pending command on a single connection at once — useful if your code interleaves reads without fully consuming one SqlDataReader before opening another, which is common with certain ORMs' internal query batching. It isn't free: MARS connections have measurably more overhead per command than a plain connection, and enabling it as a blanket default to work around one specific query pattern often costs more in aggregate than fixing the one query that actually needed it. Most application code that goes through EF Core exclusively doesn't need MARS at all, since EF Core fully consumes each command's results before issuing the next one on that connection.
Connection Timeout is a third silent default worth checking explicitly rather than assuming: it governs how long the driver waits to establish a new connection (15 seconds by default for SQL Server), which is a completely different setting from a command's own execution timeout. A slow, congested network path to a database — common right after a failover, or when connecting across regions — can exhaust the connection timeout well before any query has even started running, producing an error that looks like a query problem but is actually a connection-establishment problem with a different fix.
Where the password should never live
A connection string with a literal password sitting in appsettings.json is one accidental git add . away from a public GitHub credential leak — this remains one of the most common ways real production database credentials end up scraped by bots within minutes of a push. For local development, dotnet user-secrets keeps secrets in a JSON file outside the project directory entirely, so they never get committed by accident:
dotnet user-secrets init
dotnet user-secrets set "ConnectionStrings:Default" "Server=localhost;Database=AppDb;User Id=appuser;Password=dev-only-value;Encrypt=True;"
For production, the connection string — and especially the password inside it — belongs in Azure Key Vault (referenced via Azure.Extensions.AspNetCore.Configuration.Secrets and builder.Configuration.AddAzureKeyVault(...)) or your hosting platform's own secret store, such as Azure App Service Application Settings, injected as environment variables at runtime. Either way, the goal is the same: the literal password value should never exist as text inside a file that source control tracks. Generating a strong value for that secret in the first place is worth doing properly too — the Password Generator produces a cryptographically random value rather than a memorable one, which is exactly what a database credential should be.
Managed Identity: the connection string with no password
Azure SQL supports Azure AD / Managed Identity authentication as a connection string mode, which removes the password entirely: Authentication=Active Directory Default (or the more specific Managed Identity variant) tells the driver to fetch a token from Azure's identity infrastructure at connection time instead of sending a username and password at all. For an app already running inside Azure — App Service, Container Apps, AKS with workload identity — this is the strongest option available, since there's no password to rotate, leak, or accidentally commit, because there isn't one.
Validate before you deploy
The Connection String Builder & Validator builds a correctly-shaped string for any of the five dialects above from form fields, so you don't have to remember which provider uses Uid and which uses Username. Its Parse mode does the reverse — paste an existing connection string and it flags a missing Encrypt=True, an enabled TrustServerCertificate, or a plaintext credential before that string gets pasted somewhere it shouldn't go. Comparing the resulting values across environment-specific config files is the same instinct behind catching config drift between environments before it reaches production — worth doing with the Appsettings Diff tool alongside it. It also pairs naturally with scaffolding your EF Core entities once the connection string itself is confirmed correct — scaffolding is the next command you'll run once you know the connection string it depends on is right.