Skip to content

Upgrading from v3 to v4

Simon Hughes edited this page Sep 10, 2026 · 9 revisions

v4 moves all database reading out of the T4 template and into a separate dotnet command-line tool, efrpg, installed from NuGet. The template no longer opens a connection itself: it runs the tool, and the tool hands back the schema as XML.

This is why the upgrade needs more than a new .ttinclude. You must install the tool, or nothing will generate.

The short route: do steps 1 to 3 below (install the tool, update the extension, add the v4 include), then right-click your v3 .tt and choose Reverse POCO: Upgrade this template to v4.... The command makes every edit in step 4 for you and shows them before writing. The rest of this page explains what it changes and why, and is the manual route if you would rather see each edit yourself.

Why the split

The template used to load a database provider assembly inside the Visual Studio T4 host. That is what caused the long tail of "could not load file or assembly Npgsql / MySql.Data / System.Data.SQLite" problems, the mismatches between the provider your project referenced and the one the T4 host found, and the 32-bit/64-bit surprises.

Running the reader as its own .NET 10 process removes all of that. It also means:

  • MySQL and Oracle are now supported. 🚀 👏
  • Provider versions are the tool's business, not your project's.
  • Your connection string is passed to the tool over stdin, so it does not appear in process listings or in command-line audit logging (Sysmon event 1, EDR telemetry, ETW) the way a command-line argument would.

File changes

Version Files
v3 <Database>.tt, EF.Reverse.POCO.v3.ttinclude
v4 <Database>.tt, EF.Reverse.POCO.v4.ttinclude, plus the efrpg global tool

The v4 in the file name means v3 and v4 coexist safely in the same project. Each .tt file names the include it wants on its first line, so you can migrate one template at a time and leave the rest on v3 until you are ready. Delete EF.Reverse.POCO.v3.ttinclude once nothing includes it any more.

The template no longer depends on EF6.Utility.CS.ttinclude, EnvDTE or Microsoft.VisualStudio.TextTemplating.

Step 1 - install the tool

dotnet tool install -g Efrpg

It needs the .NET 10 runtime. Check it is on your PATH:

efrpg --help

To upgrade it later:

dotnet tool update -g Efrpg

The tool installs once per machine and is shared by every project on it. It is versioned independently of the generator, so you do not need to reinstall it every time you update the VSIX.

Anyone who regenerates needs it. Add it to your onboarding notes for the developers who run the *.tt templates. **

Step 2 - update the generator

Update the EntityFramework Reverse POCO Generator extension in Visual Studio (Extensions → Manage Extensions → Updates), then restart Visual Studio.

Step 3 - add EF.Reverse.POCO.v4.ttinclude

Add a new generator to the same folder as your existing one (right-click → Add → New Item → search "reverse poco"). Give it a temporary name, for example Hello1.tt. This drops EF.Reverse.POCO.v4.ttinclude in alongside your existing EF.Reverse.POCO.v3.ttinclude.

Keep Hello1.tt for now - it is the vanilla v4 template you will diff against in step 4. Delete it when you are done.

Do not hand-edit the .ttinclude. It is generated, and any change you make is lost on the next upgrade.

Step 4 - update your Database.tt

Three edits. The first two apply to everyone.

4a. Remove Settings.FileManagerType

Output is always written the same way now, so the setting is gone. Delete this line:

Settings.FileManagerType = FileManagerType.EfCore; // .NET Core project = EfCore; .NET 4.x project = VisualStudio; No output (testing only) = Null

and drop it from the sub-folder test further down:

// v3
if (Settings.GenerateSeparateFiles && Settings.FileManagerType == FileManagerType.EfCore)

// v4
if (Settings.GenerateSeparateFiles)

4a-i. Point the template at the v4 include

The first line of your Database.tt:

<#@ include file="EF.Reverse.POCO.v3.ttinclude" #>

becomes:

<#@ include file="EF.Reverse.POCO.v4.ttinclude" #>

Until you change this line the template keeps using v3, which is what lets you migrate one template at a time.

4b. Replace the block at the bottom of the file

This is the important one. The old entry point built a generator that read the database itself; the new one runs the tool first and passes its result in.

Find this, right at the end of your Database.tt:

    var outer = (GeneratedTextTransformation) this;

    // Show where the machine.config file is
    // outer.WriteLine("// " + System.Runtime.InteropServices.RuntimeEnvironment.SystemConfigurationFile);

    var fileManagement = new FileManagementService(outer);
    var generator = GeneratorFactory.Create(fileManagement, FileManagerFactory.GetFileManagerType());
    if (generator != null && generator.InitialisationOk)
    {
        generator.ReadDatabase();
        generator.GenerateCode();
    }
    fileManagement.Process(true);#>

and replace it with this:

    var outer = (GeneratedTextTransformation) this;
    var fileManagement = new FileManagementService(outer);

    EfrpgResult toolResult = null;
    var efrpgToolOk = true;
    try
    {
        // Connection strings are passed to the tool over stdin, never on the command line, so they stay out of
        // process listings and command-line audit logs. See SecretsXml and EfrpgToolRunner.
        toolResult = EfrpgToolRunner.ReadDatabase(
            FilterSettings.IncludeStoredProcedures || FilterSettings.IncludeTableValuedFunctions || FilterSettings.IncludeScalarValuedFunctions,
            FilterSettings.IncludeSynonyms);
    }
    catch (Exception efrpgEx)
    {
        fileManagement.Error("// -----------------------------------------------------------------------------------------");
        if (efrpgEx is System.ComponentModel.Win32Exception)
            fileManagement.Error("// efrpg tool not found. Install it with: dotnet tool install -g Efrpg");
        else
            fileManagement.Error("// efrpg tool reported an error:");
        fileManagement.Error("// " + efrpgEx.Message.Replace("\r\n", " ").Replace("\n", " "));
        fileManagement.Error("// -----------------------------------------------------------------------------------------");
        efrpgToolOk = false;
    }

    if (efrpgToolOk)
    {
        var generator = GeneratorFactory.Create(toolResult, fileManagement);
        if (generator != null && generator.InitialisationOk)
        {
            generator.ReadDatabase();
            generator.GenerateCode();
        }
        fileManagement.Process(true);
    }
#>

Note the last line is #> on its own, not fileManagement.Process(true);#>.

The easiest way to get this right is to diff your Database.tt against the vanilla Hello1.tt from step 3.

4c. Only if you customised the foreign key naming callback

DatabaseReader no longer exists in the template - the reader moved into the tool. CleanUp now lives on NamingHelper:

// v3
fkName = DatabaseReader.CleanUp(fkName);

// v4
fkName = NamingHelper.CleanUp(fkName);

If you never touched Settings.ForeignKeyName, you will not have this line.

4d. Delete the settings v4 removed

Multi-context generation, file-based templates and the experimental JavaScript output are gone (see Removed in v4), and the settings behind them no longer compile. Settings.GeneratorType went too, because TemplateType.Ef6 already says which generator runs, and so did the SQL Server 2014 trace flag. Delete these thirteen assignments. The first nine are one line each:

Settings.GeneratorType                        = GeneratorType.EfCore;
Settings.GenerationLanguage                   = GenerationLanguage.CSharp;
Settings.FileExtension                        = ".cs";
Settings.IncludeQueryTraceOn9481Flag          = false;
Settings.TemplateFolder                       = Path.Combine(Settings.Root, "Templates");
Settings.GenerateSingleDbContext              = true;
Settings.MultiContextSettingsConnectionString = "";
Settings.MultiContextSettingsPlugin           = "";
Settings.MultiContextAttributeDelimiter       = '~';

The last four are delegates spanning several lines each, from the assignment down to the closing };:

Settings.MultiContextAllFieldsColumnProcessing = delegate (Column column, Table table, Dictionary<string, object> allFields)
{
    ...
};
Settings.MultiContextAllFieldsTableProcessing = delegate (Table table, Dictionary<string, object> allFields)
{
    ...
};
Settings.MultiContextAllFieldsStoredProcedureProcessing = delegate (StoredProcedure sp, Dictionary<string, object> allFields)
{
    ...
};
Settings.MultiContextAllFieldsFunctionProcessing = delegate (StoredProcedure sp, Dictionary<string, object> allFields)
{
    ...
};

The comment lines above them can stay or go as you prefer; only the assignments matter to the compiler.

If your template had Settings.GenerateSingleDbContext = false, a TemplateType.FileBased* template type or GeneratorType.Custom, there is nothing to migrate to: stay on v3 for that project.

The right-click command does all of step 4 for you. With the v4 extension installed, right-click a v3 .tt and choose Reverse POCO: Upgrade this template to v4.... It shows every edit before writing, deletes all nine settings above whole, and refuses a file that uses one of the removed features rather than leaving it half converted.

Step 5 - save and regenerate

Delete the temporary Hello1.tt from step 3, then save Database.tt. Generation should proceed as before. Diff the generated .cs against source control: for most projects on SQL Server, PostgreSQL or SQLite the output is unchanged.

Settings that changed

v3 v4
Settings.FileManagerType Removed. Output is always written the same way.
Settings.DatabaseReaderPlugin Removed. See below.
DatabaseType.Plugin Removed.
DatabaseType.SqlCe Removed. See below.
DatabaseType.MySql Now implemented.
DatabaseType.Oracle Now implemented.
DatabaseReader.CleanUp(...) NamingHelper.CleanUp(...)

Everything else - ConnectionString, TemplateType, all of FilterSettings, the naming callbacks, ElementsToGenerate, the folder settings - is unchanged. Unlike v2 to v3, this is not a settings rewrite. Almost all of the work is the entry-point block.

Breaking changes with no direct replacement

SQL Server Compact

DatabaseType.SqlCe has been removed. SQL Server Compact reached end of support in July 2021 and its provider does not exist for modern .NET. If you are still on SQL CE, stay on v3 until you have migrated the database, typically to SQLite or LocalDB.

Custom database reader plugins

Settings.DatabaseReaderPlugin and DatabaseType.Plugin (issue #501) are gone. A plugin was a .NET Framework assembly loaded into the T4 host, and there is nothing to load it into any more.

If you were using one, please open an issue describing what your plugin did. If it was reading a database EFRPG does not support, that is a case for adding the dialect to the tool.

Removed in v4

Five rarely used features did not make the move to v4. Each stays exactly as it was in v3, which remains downloadable and continues to work, so a project that depends on one keeps using v3.

  • Multiple DbContexts in one go - Settings.GenerateSingleDbContext = false, the Settings.MultiContext* settings, the MultiContext.* settings tables and the tool's --multi-context option. The generator now always produces one context; use the filters to shape it.
  • File-based templates - TemplateType.FileBased* and Settings.TemplateFolder. The templates live inside EF.Reverse.POCO.v4.ttinclude; edit them there and keep the edit as a patch to reapply on upgrade.
  • Settings.GeneratorType altogether, Custom and the GeneratorCustom class included. TemplateType alone decides: Ef6 runs the EF6 generator, every EfCore* template the EF Core one.
  • JavaScript output - Settings.GenerationLanguage and Settings.FileExtension. It only ever swapped the SQL Server type map; the generator writes C# to .cs files.
  • Settings.IncludeQueryTraceOn9481Flag, the SQL Server 2014 cardinality-estimator workaround. The schema reads moved into the efrpg tool, which never took the flag.

Step 4d lists the thirteen assignments to delete by hand; the right-click Upgrade this template to v4 command deletes them for you, delegate bodies and all, and refuses a file that actually uses one of the first three, saying which. The pages that described them are still in this wiki's history.

Troubleshooting

efrpg tool not found. Install it with: dotnet tool install -g Efrpg

The tool is not installed, or %USERPROFILE%\.dotnet\tools is not on your PATH. Install it, then restart Visual Studio - it caches the environment it was started with, so a PATH change made after VS launched is invisible to it.

ErrorGeneratingOutput or ErrorDebuggingTemplate in the output file

ErrorDebuggingTemplate means the template did not compile; ErrorGeneratingOutput means it compiled but threw at run time. Both usually mean step 4b was applied incompletely - a common one is leaving the old GeneratorFactory.Create(fileManagement, FileManagerFactory.GetFileManagerType()) line in place. Check the Error List in Visual Studio for the actual compiler error.

The name 'DatabaseReader' does not exist in the current context

Step 4c. Change it to NamingHelper.CleanUp.

efrpg tool returned no output

The tool started but produced nothing. Run it by hand with the same connection string (see below) to see what it says on stderr.

Your generated namespace changed

v4 asks the T4 host for the project's root namespace before falling back to the template file name, which is what v3 did via the Visual Studio automation model. If a template that used to produce MyCompany.Data now produces something else, set it explicitly:

Settings.Namespace = "MyCompany.Data";

Running the tool directly

Useful for diagnosing a connection problem, and for scripting.

efrpg --database SqlServer --connection "Data Source=(local);Initial Catalog=Northwind;Integrated Security=True;Encrypt=false;TrustServerCertificate=true"
Option Meaning
--database, -d SqlServer, PostgreSQL, MySql, SQLite, Oracle
--secrets-stdin Read connection strings as XML from stdin. Preferred - the value never reaches a process listing or a command-line audit log. This is what the template uses.
--connection, -c Connection string. Convenient interactively, but visible in process listings.
--connection-base64 As --connection, UTF-8 base64 encoded to survive shell quoting. Base64 is transport encoding, not protection.
--timeout, -t Command timeout in seconds (default 600)
--stored-procedures, -sp Also read stored procedures, table-valued and scalar-valued functions. Slow on large databases.
--synonyms Include synonyms when reading tables, foreign keys and stored procedures

Output is XML on stdout, errors on stderr. Exit codes: 0 success, 1 startup error, 2 partial failure.

Prefer --secrets-stdin in any script that a CI system runs, for the same reason the template uses it.

Clone this wiki locally