Doka.EntityFrameworkCore.MySql is an Entity Framework Core 10 provider for
MySQL and MariaDB, built on the asynchronous
MySqlConnector ADO.NET driver. It provides one
EF Core model across both database families while keeping engine differences
explicit, testable, and observable.
Use it when an application needs current .NET and EF Core support, a small public API, portable MySQL/MariaDB behavior, and qualification against every advertised LTS line.
| Package | Purpose |
|---|---|
Doka.EntityFrameworkCore.MySql |
Core EF Core provider, migrations, scaffolding, type mappings, and query translation |
Doka.EntityFrameworkCore.MySql.NetTopologySuite |
Optional NetTopologySuite mappings, spatial indexes, scaffolding, and spatial query translation |
Doka.Caching.MySql |
Standalone .NET 10 IDistributedCache and IBufferDistributedCache implementation; available from 10.1.0 |
The cache package, connection-string detection, and scalar Like<T> are
available from 10.1.0. They are not present in the 10.0.0
packages.
- An application targeting .NET 10 or later
- EF Core 10.0.x for the provider and spatial extension; the supported package
range is
>= 10.0.8and< 10.1.0 - MySqlConnector 2.x; the supported package range is
>= 2.5.0and< 3.0.0 - A supported MySQL or MariaDB server from the matrix below
Building the repository itself requires the exact .NET SDK declared in
global.json. Docker is required only for live integration,
example, benchmark, and release-qualification runs.
The following .NET 10 command installs the latest stable provider package and writes its resolved version to the project file:
dotnet package add Doka.EntityFrameworkCore.MySqlAdd the spatial extension only when the model uses NetTopologySuite types:
dotnet package add Doka.EntityFrameworkCore.MySql.NetTopologySuiteInstall the standalone distributed cache only when the application needs a
MySQL or MariaDB-backed IDistributedCache:
dotnet package add Doka.Caching.MySqlFor reproducible installs, add --version followed by the exact version from
the GitHub release
or NuGet.org package page.
10.4.0 lets custom migration-operation handlers explicitly consume
validation-only and control-only operations without generating synthetic SQL.
Diagnostics, activities, metrics, and the bounded outcome code remain intact.
It also prevents MariaDB JSON aliases from recreating their engine-owned
JSON_VALID constraint as a duplicate user CHECK during reverse engineering.
Char36 and Binary16 remain fully supported GUID storage formats. Pin the
current stable version explicitly when validating an affected application:
dotnet package add Doka.EntityFrameworkCore.MySql --version 10.4.0See Migration Operation Handlers for commandless outcomes and the public metadata projection, and Migrating from Pomelo for the GUID mapping and representation-migration contracts.
Configure the provider with the database family and server release line, then use the normal EF Core APIs:
using Doka.EntityFrameworkCore.MySql;
using Microsoft.EntityFrameworkCore;
var connectionString =
"Server=localhost;Database=my_app;User ID=app;Password=secret;";
var serverVersion = MySqlServerVersion.MySql(new Version(8, 4, 0));
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseMySql(connectionString, serverVersion)
.Options;
await using var context = new AppDbContext(options);
await context.Database.EnsureCreatedAsync();
context.Products.Add(new Product { Name = "Widget", Price = 9.99m });
await context.SaveChangesAsync();
var products = await context.Products
.AsNoTracking()
.OrderBy(product => product.Name)
.ToListAsync();EnsureCreatedAsync() keeps this first-use example small. Applications whose
schema evolves should use EF Core migrations instead.
MariaDB uses the same provider surface with a different version factory:
var serverVersion = MySqlServerVersion.MariaDb(new Version(11, 8, 0));When the exact server version is not known during configuration, detect it from an open connection:
using Doka.EntityFrameworkCore.MySql;
using MySqlConnector;
var connectionString =
"Server=localhost;Database=my_app;User ID=app;Password=secret;";
await using var connection = new MySqlConnection(connectionString);
await connection.OpenAsync();
var serverVersion = MySqlServerVersion.AutoDetect(connection);The new connection-string overload manages the temporary connection itself:
var serverVersion = MySqlServerVersion.AutoDetect(connectionString);It opens synchronously once and disposes the connection on success or failure.
Reuse the descriptor for an unchanged server target instead of detecting it
for every context. Both detection paths use SupportedOnly by default; see
Provider Configuration.
Register a context with a connection string:
services.AddDbContext<AppDbContext>(options =>
options.UseMySql(
connectionString,
MySqlServerVersion.MySql(new Version(8, 4, 0))));For centralized pooling and connector logging, register a
MySqlDataSource instead:
var dataSourceConnectionString = new MySqlConnectionStringBuilder(connectionString)
{
GuidFormat = MySqlConnector.MySqlGuidFormat.Binary16,
}.ConnectionString;
var dataSource = new MySqlDataSourceBuilder(dataSourceConnectionString)
.UseLoggerFactory(loggerFactory)
.Build();
services.AddDbContext<AppDbContext>(options =>
options.UseMySql(
dataSource,
MySqlServerVersion.MySql(new Version(8, 4, 0))));The provider also accepts an existing DbConnection. See
Host Integration for connection ownership, pooling, retry,
health-check, and telemetry guidance.
Provider-owned connection strings are normalized to Doka's Binary16 connector
transport. Caller-owned connections and data sources must already specify
GuidFormat=Binary16; Doka validates them without mutation. Every connection
path rejects UseAffectedRows=true because EF optimistic concurrency requires
matched-row semantics.
| Engine | Supported line | JSON storage | Sequences | RETURNING |
Temporal tables |
|---|---|---|---|---|---|
| MySQL | 8.4 LTS | native | emulated | unavailable in the engine | provider emulation |
| MySQL | 9.7 LTS | native | emulated | unavailable in the engine | provider emulation |
| MariaDB | 10.11 LTS | validated alias | native | native | native |
| MariaDB | 11.4 LTS | validated alias | native | native | native |
| MariaDB | 11.8 LTS | validated alias | native | native | native |
| MariaDB | 12.3 LTS | validated alias | native | native | native |
All six lines provide native CTE support. The exact qualified patch pins, lifecycle sources, live-test ownership, and unsupported-version policy are in Supported Databases.
Runtime capability diagnostics classify provider behavior as Native,
Emulated, or UnsupportedByEngine. Unsupported server releases are rejected
by default. MySqlServerVersionCompatibilityMode.AllowUnsupported is an
explicit escape hatch without a support guarantee and emits
MySqlEventId.UnsupportedServerVersion.
- Engine-aware migrations and scaffolding: advisory-lock protection, idempotent scripts, rename and sequence handling, generated and invisible columns, spatial indexes, JSON aliases, and custom migration-operation handlers.
- Portable temporal modeling: native MariaDB system, application, and bitemporal tables plus provider-owned MySQL history-table emulation behind one model and query API.
- MySQL-family query translation: JSON functions, regular expressions,
full-text search, scalar
Like<T>, CTE composition, bulk update/delete, and engine-specific SQL selected from declared capabilities. - Provider-owned type mappings: JSON DOM types,
Binary16andChar36GUIDs, temporal CLR types, generated defaults, complex types, and optional NetTopologySuite geometries. - Production behavior: transient-failure retries, savepoints, connection pooling, structured diagnostics, trimming analysis, compiled models, and precompiled query coverage.
- Standalone distributed caching: standard .NET cache contracts, database-UTC expiration, buffer-based reads, and bounded expired-row cleanup without an EF Core dependency.
The documentation index owns the complete behavioral contracts and limitations. The sections below show only the main entry points.
options.UseMySql(connectionString, serverVersion, mysql =>
mysql.EnableRetryOnFailure(maxRetryCount: 5));Libraries or applications that use session-local @name variables can require
the connector capability through provider configuration:
options.UseMySql(connectionString, serverVersion, mysql =>
mysql.RequireUserVariables());Doka adds AllowUserVariables=true when an owned connection string omits it.
Borrowed connections and data sources must configure it explicitly because
Doka never rebuilds caller-owned objects. See
Provider Configuration for runtime replacement and
ownership rules.
options.UseMySql(connectionString, serverVersion, mysql =>
mysql.DefaultGuidFormat(
Doka.EntityFrameworkCore.MySql.MySqlGuidFormat.Char36));
modelBuilder.Entity<OrderWithGuid>()
.Property(order => order.Id)
.HasMySqlGuidFormat(
Doka.EntityFrameworkCore.MySql.MySqlGuidFormat.Binary16);modelBuilder.Entity<Employee>().ToTable(
"Employees",
table => table.IsTemporal(temporal =>
{
temporal.UseHistoryTable("EmployeeHistory");
temporal.HasPeriodStart("ValidFrom");
temporal.HasPeriodEnd("ValidTo");
}));
var history = await context.Employees
.TemporalAll()
.OrderBy(employee => EF.Property<DateTime>(employee, "ValidFrom"))
.ToListAsync();See Temporal Tables and Common Table Expressions for portability rules and complete query examples.
After installing the NetTopologySuite package, activate it in provider options:
options.UseMySql(connectionString, serverVersion, mysql =>
mysql.UseNetTopologySuite());
modelBuilder.Entity<Place>()
.Property(place => place.Location)
.HasColumnType("point")
.HasSrid(4326);
modelBuilder.Entity<Place>()
.HasIndex(place => place.Location)
.IsSpatial();The provider uses the standard EF Core tooling:
dotnet ef migrations add InitialCreate
dotnet ef database updateConcurrent migrators are serialized with a dedicated advisory lock. Custom packages can add exact migration-operation handlers without replacing the provider SQL generator; see Migration Operation Handlers.
Existing Pomelo applications should start with Migrating from Pomelo. It distinguishes API changes from schema changes and preserves deployed migration history.
Doka.Caching.MySql provides IDistributedCache and
IBufferDistributedCache for MySQL and MariaDB. It is a standalone .NET 10
package: neither the EF Core provider nor a DbContext is required.
Install the package:
dotnet package add Doka.Caching.MySqlFirst, generate the cache table script for an existing database:
using Doka.Caching.MySql;
var script = MySqlCacheSchema.GetCreateScript("app_cache", "DistributedCache");
Console.WriteLine(script);Review and execute the script separately during deployment. Registration and cache operations never create or upgrade database objects. When replacing another cache implementation, provision a new Doka cache table.
Register the cache with the application's connection string:
using Doka.Caching.MySql;
using Microsoft.Extensions.DependencyInjection;
services.AddDistributedMySqlCache(options =>
{
options.ConnectionString = connectionString;
options.SchemaName = "app_cache";
options.TableName = "DistributedCache";
});Inject IDistributedCache and pass the operation's cancellation token:
using Microsoft.Extensions.Caching.Distributed;
await cache.SetStringAsync(
"greeting",
"Hello from Doka",
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10),
SlidingExpiration = TimeSpan.FromMinutes(2),
},
cancellationToken);
var greeting = await cache.GetStringAsync("greeting", cancellationToken);Both interfaces resolve the same singleton. Expiration uses database UTC;
absolute deadlines cap sliding refreshes. For binary payloads,
IBufferDistributedCache reads into caller-owned buffers without an extra
value-sized result array. The application identity needs only SELECT,
INSERT, UPDATE, and DELETE on the deployed table.
See Distributed Cache for data-source ownership, concurrency, cleanup, schema deployment, and buffer usage.
MySqlConnectoris the only supported ADO.NET driver.- Azure Database for MySQL is not yet an advertised compatibility target.
- Amazon Aurora MySQL is intentionally outside the supported scope.
- Unsupported query translations fail instead of falling back to client evaluation.
- EF provider NativeAOT readiness remains blocked by upstream EF Core precompiled-query constraints; trimming is continuously validated. The standalone cache has no EF Core dependency and is verified separately.
See External Limitations for the canonical boundary ledger. Provider-owned gaps have a zero budget and do not belong in that ledger.
- Documentation index
- Provider architecture
- Runnable examples
- Complex types
- Temporal tables
- Common table expressions
- Host integration
- IDE integration
- Provider configuration
- Query functions
- Migrating from Pomelo
- Distributed cache
- Support and issue reporting
- Security policy
- Security assurance case
- Release verification
- Project governance
- Project roadmap
- OpenSSF Best Practices evidence
- Changelog
Use GitHub Issues for reproducible defects, feature requests, compatibility reports, and usage questions. Report suspected vulnerabilities privately through SECURITY.md.
Repository setup, test tiers, coding conventions, public API governance, and pull-request requirements are documented in CONTRIBUTING.md. Performance evidence is independent engineering feedback and does not block release publication; its measurement and triage contract lives in the Performance Evidence runbook.
MIT -- see LICENSE.