-
Notifications
You must be signed in to change notification settings - Fork 226
Upgrading from v3 to v4
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.
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.
| 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.
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. **
Update the EntityFramework Reverse POCO Generator extension in Visual Studio (Extensions → Manage Extensions → Updates), then restart Visual Studio.
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.
Three edits. The first two apply to everyone.
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) = Nulland drop it from the sub-folder test further down:
// v3
if (Settings.GenerateSeparateFiles && Settings.FileManagerType == FileManagerType.EfCore)
// v4
if (Settings.GenerateSeparateFiles)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.
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.
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.
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.
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.
| 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.
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.
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.
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, theSettings.MultiContext*settings, theMultiContext.*settings tables and the tool's--multi-contextoption. The generator now always produces one context; use the filters to shape it. -
File-based templates -
TemplateType.FileBased*andSettings.TemplateFolder. The templates live insideEF.Reverse.POCO.v4.ttinclude; edit them there and keep the edit as a patch to reapply on upgrade. -
Settings.GeneratorTypealtogether,Customand theGeneratorCustomclass included.TemplateTypealone decides:Ef6runs the EF6 generator, everyEfCore*template the EF Core one. -
JavaScript output -
Settings.GenerationLanguageandSettings.FileExtension. It only ever swapped the SQL Server type map; the generator writes C# to.csfiles. -
Settings.IncludeQueryTraceOn9481Flag, the SQL Server 2014 cardinality-estimator workaround. The schema reads moved into theefrpgtool, 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.
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";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.
- Home
- Compared with the Microsoft scaffolder
- Connection strings
- JetBrains Rider
- Upgrading from v3 to v4
- Saving .tt does nothing
- Settings A-Z - every setting, with a page each
- Common Settings Types Explained
- Settings Callbacks
- Settings runtime values and helpers
- Filtering
- Full Control Over the Generated Code
- Enum Generation from Table Data
- Owned Entities
- JSON column support
- Global Query Filters
- Extended Property Names Feature
- Partial Properties
- File-Scoped Namespaces
- Data Annotations
- Spatial Types
- HierarchyId
- RowVersion and TimeStamp columns
- Lazy Loading
- Stored proc result sets