Chapter 21 · PostgreSQL, MySQL, and Cross-Provider Portability
Provider Ecosystem Awareness: Npgsql, MySQL/MariaDB Providers, Versions, and Compatibility Contracts
Turn provider choice into a versioned engineering contract by comparing Npgsql, Oracle MySQL, Pomelo/MariaDB support, package constraints, licenses, database versions, and reproducible provider test matrices.
Learning outcomes
Treat an EF Core provider as a versioned compatibility layer with its own release cadence, dependencies, maintainer, license, and database support matrix.
Pin the current stable EF Core 10 provider versions for PostgreSQL and Oracle MySQL instead of assuming a matching major version exists.
Explain why Pomelo 9.0.0 can run on .NET 10 yet still is not an EF Core 10 provider, and keep its MariaDB path separate from the EF10 mandatory lab.
Build a provider smoke-test matrix that proves connection, model construction, migrations, representative queries, writes, transactions, concurrency, and provider-specific features.
Record database server versions and provider-generated SQL so restore/build success is never mistaken for runtime compatibility.
Keep all mandatory work local/free and avoid embedding credentials in source or generated artifacts.
1. The problem: “EF Core 10” is not a provider compatibility guarantee
ServiceHub now has a mature EF Core model that has been
exercised against SQLite and an isolated SQL Server lab. A
customer asks for PostgreSQL, another asks for MySQL, and a
third runs MariaDB. The tempting response is to switch
UseSqlite to another Use... call and
assume EF normalizes the rest. That is exactly the portability
mistake this chapter prevents.
EF Core providers plug into internal and public EF services. A package can target .NET 10 and still be constrained to EF Core 9. Always inspect the provider package dependency range, maintainer documentation, supported database versions, and the exact provider build used in tests.
| Layer | Question to pin | Example for this chapter |
|---|---|---|
| EF runtime/tooling | Which EF patch and dotnet-ef? | 10.0.11 |
| Provider | Which package/patch and dependency range? |
Npgsql 10.0.3; Oracle MySQL
10.0.9
|
| ADO.NET driver | Which driver is underneath? | Npgsql / MySql.Data as selected by provider |
| Database | Which server major/minor? |
PostgreSQL 18.6; MySQL 8.4 LTS
|
| Topology | Local container, VM, managed service? | Free local container/instance for mandatory labs |
2. Freeze the current 2026 compatibility matrix before writing provider code
At this chapter-generation checkpoint, Microsoft EF Core and
dotnet-ef are 10.0.11. Npgsql has a stable EF Core
10 line at 10.0.3. Oracle publishes
MySql.EntityFrameworkCore 10.0.9 with EF Core 10
support. Pomelo’s latest stable release is still 9.0.0, whose
NuGet dependency range is EF Core 9.x; its EF Core 10
milestone/work is active but not a stable 10.0 release.
| Provider package | Maintainer / license | Stable compatibility used here | Database path |
|---|---|---|---|
Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3
|
Npgsql community · PostgreSQL License | EF Core 10 / .NET 10 | PostgreSQL 18.6 local |
MySql.EntityFrameworkCore 10.0.9 |
Oracle/MySQL · GPL-2.0 + Universal FOSS Exception | EF Core 10; current Oracle provider line | MySQL 8.4 LTS local |
Pomelo.EntityFrameworkCore.MySql 9.0.0
|
Pomelo Foundation · MIT | EF Core 9.x, .NET 8+; can run on .NET 10 but is not EF10 | MySQL/MariaDB observation path only |
Its published package constrains Microsoft.EntityFrameworkCore.Relational to the 9.0.x line. Ignoring or overriding the constraint is unsupported experimentation, not a production compatibility strategy. For MariaDB-specific Pomelo exercises, use a separate EF9 project or wait for a stable EF10 Pomelo release and re-run this matrix.
3. Build separate provider host projects around one domain contract
The safest learning topology keeps the portable ServiceHub domain/model conventions visible but isolates provider packages so their extension methods, migrations, and transitive drivers do not fight inside one project. The domain is shared; provider hosts are replaceable integration edges.
src/ ServiceHub.Domain/ ServiceHub.Persistence.Core/ ServiceHub.Persistence.Postgres/ ServiceHub.Persistence.MySql/tests/ ServiceHub.ProviderTests.Postgres/ ServiceHub.ProviderTests.MySql/ ServiceHub.ProviderContracts/
dotnet add src/ServiceHub.Persistence.Postgres package Npgsql.EntityFrameworkCore.PostgreSQL --version 10.0.3dotnet add src/ServiceHub.Persistence.MySql package MySql.EntityFrameworkCore --version 10.0.9dotnet add src/ServiceHub.Persistence.Postgres package Microsoft.EntityFrameworkCore.Design --version 10.0.11dotnet add src/ServiceHub.Persistence.MySql package Microsoft.EntityFrameworkCore.Design --version 10.0.11dotnet tool update --local dotnet-ef --version 10.0.11
Keep Microsoft.EntityFrameworkCore and Microsoft
design/tool packages at 10.0.11; provider packages are allowed
to have independent patch numbers because they release
independently.
4. Provider configuration is executable evidence
Construct the same context model through provider-specific options and immediately print provider/model facts. This catches “wrong provider loaded” and model-service replacement problems before a migration or request does real work.
public static DbContextOptions<ServiceHubContext> PostgresOptions(string cs) => new DbContextOptionsBuilder<ServiceHubContext>() .UseNpgsql(cs, o => o.SetPostgresVersion(18, 0)) .EnableDetailedErrors() .Options;public static DbContextOptions<ServiceHubContext> MySqlOptions(string cs) => new DbContextOptionsBuilder<ServiceHubContext>() .UseMySQL(cs) .EnableDetailedErrors() .Options;
await using var db = new ServiceHubContext(options);Console.WriteLine($"Provider={db.Database.ProviderName}");Console.WriteLine($"ModelEntities={db.Model.GetEntityTypes().Count()}");Console.WriteLine(db.WorkOrders.Where(w => w.Id > 0).Take(1).ToQueryString());
ProviderName, runtime model metadata, and one
translated query form a small but useful fingerprint. They do
not prove all features work; they prove the expected provider
actually constructed the model and translated at least one
representative query.
5. Database server version belongs in the test record
Provider features can branch on database server version. PostgreSQL 18 adds capabilities that earlier majors lack; MySQL has LTS and Innovation release tracks. Capture the database-reported version alongside the package graph so failures are reproducible.
-- PostgreSQLSELECT version();SHOW server_version;-- MySQLSELECT VERSION();SELECT @@version, @@version_comment, @@collation_server, @@character_set_server;
Do not assume a managed service, local Docker image, or developer workstation has the same minor version as CI. Store the result in test output, not secrets.
6. A provider test matrix is more valuable than a provider abstraction slogan
Portability means a defined set of behaviors is tested on every supported provider. A useful matrix starts with shared semantics and then adds provider-specific rows. It should not require every engine to support every feature.
| Contract area | PostgreSQL test | MySQL test | Failure means |
|---|---|---|---|
| Model boot | context builds; expected provider metadata | same | package/model incompatibility |
| Migrations | create/apply/rollback disposable schema | same | DDL/provider gap |
| Core queries | filters, ordering, paging, aggregates | same | translation/semantics drift |
| Writes | generated key + concurrency + transaction | same | store-generation/write gap |
| JSON/search/native types | Npgsql-specific tests | MySQL-specific tests | capability adapter regression |
| Plan/index evidence | EXPLAIN/ANALYZE as safe | EXPLAIN | performance portability issue |
7. Failure case: package restore succeeds, so the provider must be compatible
A developer upgrades the application to EF Core 10, leaves a provider on an EF9-only stable package, suppresses dependency warnings, and sees a successful build. At runtime model creation or query translation can fail because provider services target a different EF contract.
dotnet list src/ServiceHub.Persistence.Postgres package --include-transitivedotnet list src/ServiceHub.Persistence.MySql package --include-transitivedotnet restore --force-evaluate# CI policy: fail on package downgrade/version-range warnings.
Repair: use a provider version whose declared dependency range includes the chosen EF major. If a stable provider does not exist, either stay on the compatible EF major for that provider in an isolated service/project or choose a supported provider. Do not convert a compatibility gap into a hidden runtime experiment.
8. Secrets, local containers, and free reproducibility
Mandatory labs use local PostgreSQL and MySQL instances/containers with disposable databases. Connection strings come from environment variables or user secrets; no production hostname, password, cloud key, or certificate is written into lesson source.
# Bash / PowerShell equivalents are fine; values are local lab credentials only.export SERVICEHUB_PG="Host=localhost;Port=5432;Database=servicehub_pg_lab;Username=servicehub_lab;Password=..."export SERVICEHUB_MYSQL="Server=localhost;Port=3306;Database=servicehub_mysql_lab;User=servicehub_lab;Password=..."
PostgreSQL Community and MySQL Community are sufficient. MariaDB may be tested separately, but the stable Pomelo path is currently EF Core 9, so it is not a mandatory EF Core 10 lab in this chapter.
9. Mandatory lab: produce a compatibility evidence packet
-
Record
dotnet --info,dotnet ef --version, direct/transitive package graphs, and the current provider package versions. -
Start disposable PostgreSQL 18.6 and MySQL 8.4 LTS databases
or equivalent local installations. Record
version()/VERSION(). -
Construct
ServiceHubContextonce with Npgsql and once with Oracle MySQL. PrintDatabase.ProviderNameand model entity count. - Apply a provider-specific baseline migration to an empty disposable database for each engine; inspect the DDL before applying.
- Seed the same minimal ServiceHub rows and run five shared contract queries/writes.
-
Capture
ToQueryString()and command logs for at least one query per provider. - Create a matrix row for Pomelo 9.0.0 showing its EF Core 9 dependency constraint and mark EF Core 10 stable support as unavailable at this checkpoint rather than trying to force-install it.
- Drop the disposable databases after evidence is captured.
10. Production judgment and bridge
Provider portability begins with release engineering: package compatibility, database versions, support posture, license, and a repeatable test matrix. A provider is not “just a driver,” and package restore is not evidence of semantic portability. Lesson 2 moves from ecosystem compatibility into the physical type systems and value-generation features that differ even when the same ServiceHub CLR model compiles unchanged.
Check your understanding
- Why can a package target .NET 10 yet still not be an EF Core 10 provider?
- Which stable provider versions are used for the mandatory EF Core 10 PostgreSQL/MySQL labs?
- Why is Pomelo/MariaDB not a mandatory EF Core 10 lab here?
- What three runtime facts should accompany a provider query failure?
- What does a cross-provider contract test prove?
- Why isolate provider hosts/projects?
Review the answers
1. The target framework and the Microsoft.EntityFrameworkCore dependency range are separate contracts; Pomelo 9 targets compatible .NET runtimes but depends on EF Core 9.x.
2. Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3 and Oracle MySql.EntityFrameworkCore 10.0.9.
3. Pomelo’s latest stable package is 9.0.0 and declares EF Core 9.x compatibility; EF10 work is ongoing but not a stable 10.0 release at this checkpoint.
4. Exact EF/provider/driver package graph, database server version, and generated SQL/log/provider name.
5. A defined business/query/write behavior works on each supported provider; it does not prove every provider has identical SQL or feature sets.
6. It keeps provider extensions, migration assemblies, drivers and dependency constraints from contaminating the portable domain/core model and makes failures attributable.
Authoritative references
Provider compatibility must be verified from current provider/package documentation, not inferred from EF Core branding.
- EF Core database providers — provider plug-in compatibility guidance
- Npgsql EF Core 10 release notes — Npgsql EF Core 10 support and current features
- Npgsql.EntityFrameworkCore.PostgreSQL NuGet — stable package/version/license/dependencies
- MySql.EntityFrameworkCore NuGet — Oracle provider current EF Core 10 package line
- MySQL Connector/NET EF Core support — Oracle provider requirements and compatibility documentation
- Pomelo provider repository — published compatibility matrix and EF10 roadmap