Skip to content
Simon Hughes edited this page Aug 30, 2026 · 4 revisions

MySQL and MariaDB

Fully supported from v4. 🚀

MySQL is the reason the database reader moved out of the T4 template and into the efrpg dotnet tool. There is nothing to install into the GAC, nothing to add to machine.config, and no MySQL Connector/NET download. Install the tool and it works:

dotnet tool install -g Efrpg

If you followed the v3 instructions on this page, you can gacutil /u MySql.Data and delete the MySQL Data Provider entry you added to machine.config. Nothing in v4 reads them. See Upgrading from v3 to v4.

Settings

Settings.DatabaseType     = DatabaseType.MySql;
Settings.TemplateType     = TemplateType.EfCore10;   // or EfCore9, EfCore8
Settings.GeneratorType    = GeneratorType.EfCore;
Settings.OnConfiguration  = OnConfiguration.Omit;    // important - see below
Settings.ConnectionString = "Server=localhost;Port=3306;Database=Northwind;User Id=root;Password=***;";

Your project needs the Pomelo.EntityFrameworkCore.MySql NuGet package.

MariaDB works through the same driver and the same settings. The generator reads information_schema, which both engines implement.

Set OnConfiguration.Omit and wire the provider yourself

Pomelo's UseMySql() takes a mandatory ServerVersion argument, which the generator does not emit. If you leave Settings.OnConfiguration = OnConfiguration.ConnectionString, the generated OnConfiguring will not compile.

So set:

Settings.OnConfiguration = OnConfiguration.Omit;

and configure the context where you register it:

var cs = builder.Configuration.GetConnectionString("MyDbContext");
builder.Services.AddDbContext<MyDbContext>(o => o.UseMySql(cs, ServerVersion.AutoDetect(cs)));

ServerVersion.AutoDetect opens a connection at startup to ask the server what it is. If you would rather not pay for that, or you build in an environment with no database, pin it:

o.UseMySql(cs, new MySqlServerVersion(new Version(8, 0, 36)));
// or, for MariaDB
o.UseMySql(cs, new MariaDbServerVersion(new Version(11, 4, 0)));

This is the better pattern anyway, because it keeps your connection string out of generated source. See Settings.OnConfiguration.

One database is one schema

This is the biggest conceptual difference from SQL Server, PostgreSQL and Oracle.

MySQL has no separate concept of a schema inside a database. Database=Northwind in your connection string is the schema, and it is the only one the generator reads. Consequences:

  • Database= is not optional in the connection string.
  • Tables in another database on the same server are invisible, including through a foreign key that points at them. A cross-database relationship will not appear in the generated model.
  • Settings.PrependSchemaName has nothing to prepend, so class names come straight from table names.

If your model genuinely spans two MySQL databases, generate one DbContext per database, each from its own .tt file, and join across them in your own code.

Permissions

SELECT on the database you are reading is enough. information_schema filters itself to whatever the connected user can see, so a read-only account works:

CREATE USER 'efrpg_reader'@'%' IDENTIFIED BY '***';
GRANT SELECT ON northwind.* TO 'efrpg_reader'@'%';

What gets read

Feature Supported Notes
Tables, views, columns
Primary and foreign keys Within the one database
Indexes
AUTO_INCREMENT Generated as ValueGeneratedOnAdd(), which is all Pomelo needs
Column defaults
Column comments COLUMN_COMMENT is read and emitted as an XML doc comment
Triggers
Stored procedures and functions Signatures and parameters, so callers are generated
Stored procedure result models See below
Sequences n/a MySQL has none
Synonyms n/a MySQL has none
Temporal tables n/a
Cross-database foreign keys See above

Stored procedure result models

The name and parameters of every stored procedure are read and a caller is generated, but no return model class is produced, because MySQL has no catalogue describing what a procedure returns.

Two options:

  1. Point the procedure at an entity you already have:

    Settings.StoredProcedureReturnTypes.Add("SalesByYear", "SummaryOfSalesByYear");
  2. Write the result class yourself in a partial file and call the procedure with EF Core's SqlQuery<T> or FromSql.

Type mapping highlights

MySQL C#
tinyint(1) bool
tinyint SByte
tinyint unsigned byte
bit(1) bool
bit long
int unsigned long
bigint unsigned decimal
enum, set string
json string - see JSON column support to map it to a real type
time TimeSpan
year short
geometry, point, polygon, ... NetTopologySuite types

tinyint(1) mapping to bool is the MySQL convention and matches what Pomelo expects. If you really do store -128..127 in a tinyint(1), declare it as tinyint(4) in the database, or override the column in Settings.UpdateColumn.

Spatial columns need NetTopologySuite and o.UseMySql(cs, serverVersion, x => x.UseNetTopologySuite()). If you do not want them at all, set Settings.DisableGeographyTypes = true.

Case sensitivity

On Linux, MySQL table names are case sensitive by default (lower_case_table_names=0); on Windows and macOS they are not. Your regex filters run against the real name as MySQL reports it, so a filter that works on a developer's Windows machine can silently match nothing on a Linux CI agent. Use RegexOptions.IgnoreCase:

FilterSettings.TableFilters.Add(new RegexIncludeFilter(
    new Regex("^order.*", RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(200))));

See also

Clone this wiki locally