EFCorePowerTools
EFCorePowerTools copied to clipboard
T4 customizations and tips
- [x] Enum support sample here
- [ ] INotifyPropertyChanged properties
- [x] Custom collection initializers - see https://github.com/dotnet/efcore/issues/14249 and proposal: https://github.com/ErikEJ/EFCorePowerTools/issues/1499#issuecomment-1773937177
- [ ] dbcontext splitting https://github.com/dotnet/EntityFramework.Docs/pull/3995#pullrequestreview-1083179761 https://github.com/dotnet/EntityFramework.Docs/blob/main/entity-framework/core/managing-schemas/scaffolding/templates.md#entity-configuration-classes https://github.com/dotnet/EntityFramework.Docs/pull/3995 https://github.com/R4ND3LL/EntityFrameworkRuler/blob/main/src/EntityFrameworkRuler.Design/Resources/EntityTypeConfiguration.t4
- [x] Custom collection types: https://github.com/dotnet/EntityFramework.Docs/blob/main/entity-framework/core/managing-schemas/scaffolding/templates.md#customize-the-entity-types
- [ ] Remove default constraints
- [x] restore collection setter (N/A - fixed in EF Core)
- [x] Pure POCO classes (new POCO template added)
- [x] Multiple sets of files generated (built-in, see the wiki docs)
- [x] sharing same template across multiple projects
- [x] Adding Obsolete attribute to some columns
- [x] Fix bad mappings
- [x] Property renaming
I love the new T4 templates. I have used it to create C# 11 required properties.
This is my line 104 of the EntityType.t4
public <#= needsInitializer ? "required " : "" #><#= code.Reference(property.ClrType) #><#= needsNullable ? "?" : "" #> <#= property.Name #> { get; set; }
This generates this code if you use the Northwind
sample database. The ProductName property is now required
.
Thanks for your great work!
Fons
@sonnemaf Great suggestion!
Would there be a way to support any T4 file name that is dropped into the templates folder rather than just the specific named three that are used for scaffolding out of the box. For example I could make a files called Services.T4, or Controllers.T4, Views.T4 etc. My work around now is to create that code then rename them upon running the PowerTools.
@mrunks not possible. But a setting to specify the location of the folder structure is very possible. Maybe create a separate issue and I will investigate.
I am not clear how a different folder structure would help. I just want to be able to read additional files. Or are you suggesting having multiple folders all with the same file names and then it would iterate through each of the folders ?
@mrunks I suggest you create a new issue and explain your exact requirements
Is it possible to add the DisplayName Attribute in the T4 template? I've did a search but I've not see where anyone asked this or seen it implemented.
[Display(Name= "Price")] public decimal? ListPrice {get; set;}
@KayakFisher205 Where would the value for Name in Display come from?
Could it be from the Extended Properties, with the Name =FieldName and Value=the Display Name?
It could be the comment from the model, yes. I will provide an example later.
@KayakFisher205 Just have a look at how "comments" are currently applied in the templates.
When there is an error in the T4, the reverse engineering produces a general error message in the output window which makes it hard to find the root cause. Would it be possible to produce clear error messages like the output of a tt file > Run custom tool?
E.g. output reverse engineer:
`System.InvalidOperationException: Reverse engineer error:
Microsoft.EntityFrameworkCore.Design.OperationException: Processing 'C:\Development\Playground\OEFS.Test\OEFS.Test.Data\OEFS.Test.Data\CodeTemplates\EFCore\DbContext.t4' failed.
at void Microsoft.EntityFrameworkCore.Scaffolding.Internal.TextTemplatingModelGenerator.HandleErrors(TextTemplatingEngineHost host)
at ScaffoldedModel Microsoft.EntityFrameworkCore.Scaffolding.Internal.TextTemplatingModelGenerator.GenerateModel(IModel model, ModelCodeGenerationOptions options)
at ScaffoldedModel RevEng.Core.ReverseEngineerScaffolder.ScaffoldModel(string connectionString, DatabaseModelFactoryOptions databaseOptions, ModelReverseEngineerOptions modelOptions, ModelCodeGenerationOptions codeOptions, bool removeNullableBoolDefaults, bool dbContextOnly, bool entitiesOnly, bool useSchemaFolders) in C:/projects/efcorepowertools/src/GUI/RevEng.Core/ReverseEngineerScaffolder.cs:line 375
at SavedModelFiles RevEng.Core.ReverseEngineerScaffolder.GenerateDbContext(ReverseEngineerCommandOptions options, List
at ReverseEngineerResult EFCorePowerTools.Handlers.ReverseEngineer.ResultDeserializer.BuildResult(string output) at async Task<ReverseEngineerResult> EFCorePowerTools.Handlers.ReverseEngineer.EfRevEngLauncher.GetOutputAsync() at async Task<ReverseEngineerResult> EFCorePowerTools.Handlers.ReverseEngineer.EfRevEngLauncher.LaunchExternalRunnerAsync(ReverseEngineerOptions options, CodeGenerationMode codeGenerationMode, Project project) at async Task EFCorePowerTools.Handlers.ReverseEngineer.ReverseEngineerHandler.GenerateFilesAsync(Project project, ReverseEngineerOptions options, string missingProviderPackage, bool onlyGenerate, List<NuGetPackage> packages, string referenceRenamingPath) at async Task EFCorePowerTools.Handlers.ReverseEngineer.ReverseEngineerHandler.ReverseEngineerCodeFirstAsync(Project project, string optionsPath, bool onlyGenerate, bool fromSqlProj)`
E.g. output tt file > Run custom tool:
@AchrWasUnavailable Please ask in the EF Core repo, it needs to be fixed there (or maybe even in VS)
I have two customizations in my own code. First one is required
(which I see mentioned above, so I will not repeat it).
But I also have the code to avoid empty list allocations for collection navigations. So that we can go from this:
public ICollection<Item> Items { get; set;} = new List<Item>(); // wasted if collection was not included
…to this:
private ICollection<Item>? _items; // sic! the nullable
public ICollection<Item> Items // not nullable but allocates an empty list only when needed (lazily)
{
get => _items ??= new List<TestExample>();
set => _items = value;
}
This is done with this quick hack (obviously copy-pasting 3 times the field name generation logic is a no-no)
#region Fields
<#
foreach (var navigation in EntityType.GetNavigations())
{
var targetType = navigation.TargetEntityType.Name;
if (navigation.IsCollection)
{
#>
private ICollection<<#= targetType #>>? _<#= navigation.Name[0].ToString().ToLower() + navigation.Name.Substring(1) #>;
<#
}
}
#>
#endregion
… and inside properties loop:
if (navigation.IsCollection)
{
#>
public ICollection<<#= targetType #>> <#= navigation.Name #>
{
get => _<#= navigation.Name[0].ToString().ToLower() + navigation.Name.Substring(1) #> ??= new List<<#= targetType #>>();
set => _<#= navigation.Name[0].ToString().ToLower() + navigation.Name.Substring(1) #> = value;
}
<#
}
@piskov Thanks! Is this not "Custom Collection Initializers" from the list above?
@ErikEJ yeah, you’re absolutely right: to my shame, I’ve dismissed it due to “hashset” and “constructor” in the name and reinvented the wheel :-)
@piskov no worries, your sample code is very valuable!
I am comsidering at one point to create a "super" template where you can toggle these advanced options on.
Hi Erik, I see "INotifyPropertyChanged properties" in the checklist above. I am guessing this means the tool will be able to create model properties with property change notification. Any idea when this will be released? Thanks for all your hard work on this tool.
@rajibsm it’s really easy doing it yourself by customizing EntityType.t4 template.
For example to use ObservableCollection instead of list replace
if (navigation.IsCollection)
{
#>
public virtual ICollection<<#= targetType #>> <#= navigation.Name #> { get; set } =
new List<<#= targetType #>>();
<#
}
… with this
public virtual ICollection<<#= targetType #>> <#= navigation.Name #> { get; set } =
new ObservableCollection<<#= targetType #>>();
Same goes for properties: you need to split them into fields + property declarations which is really easy if you use my code as a sample.
Both things should not take more than 30 minutes :-) It will probably not cover most of the EF cases but will do for your code (i. e. work correctly 95% of the time with some manual adjustments required later).
@rajibsm This is currently just a list of customizations users have been asking for, with examples when available. As I mention here, maybe one day I will consolidate in a "super" template.
@piskov Thank you for pointing me in the right direction. This was my first time editing T4 templates, but I was able to do it. Installing a T4 formatter extension for Visual Studio really helped to navigate the code template. @ErikEJ also has this helpful youtube video that will help with editing T4 templates.
I have uploaded the start of a "Power" template, with the following features:
- Enum support
- Property renaming
- File header
https://github.com/ErikEJ/EFCorePowerTools/tree/master/samples/CodeTemplates/EFCore
I downloaded the templates mentioned above, but wasnt really sure what I needed to change to get the enum mapping support. I think this feature should be controlled by the .json file you have and not template editing if possible.
Neverless, I have a entity called Activity and a property called ActivityType (int in db) What changes would i need to make sure this property is a enum of type ActivityTypes after reverse engineering according to the table/template above?
Thank you
@zoinkydoink https://github.com/ErikEJ/EFCorePowerTools/issues/1472#issuecomment-1220273164
Is it possible to have some (or maybe all) generated classes derive from a base entity? i.e. add : BaseEntity to each class?
@rick1c Yes, but it would go against Database First reverse engineering