Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

T4 customizations and tips #1499

Open
10 of 13 tasks
ErikEJ opened this issue Aug 23, 2022 · 27 comments
Open
10 of 13 tasks

T4 customizations and tips #1499

ErikEJ opened this issue Aug 23, 2022 · 27 comments
Labels

Comments

@ErikEJ
Copy link
Owner Author

ErikEJ commented Oct 28, 2022

Debugging templates

@sonnemaf
Copy link

sonnemaf commented Dec 2, 2022

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; }

image

This generates this code if you use the Northwind sample database. The ProductName property is now required.

image

Thanks for your great work!

Fons

@ErikEJ
Copy link
Owner Author

ErikEJ commented Dec 2, 2022

@sonnemaf Great suggestion!

@mrunks
Copy link

mrunks commented Dec 8, 2022

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.

@ErikEJ
Copy link
Owner Author

ErikEJ commented Dec 8, 2022

@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.

@mrunks
Copy link

mrunks commented Dec 9, 2022

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 ?

@ErikEJ
Copy link
Owner Author

ErikEJ commented Dec 9, 2022

@mrunks I suggest you create a new issue and explain your exact requirements

Repository owner deleted a comment from mrunks Dec 19, 2022
Repository owner deleted a comment from mrunks Dec 19, 2022
@KayakFisher205
Copy link

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;}

@ErikEJ
Copy link
Owner Author

ErikEJ commented Feb 13, 2023

@KayakFisher205 Where would the value for Name in Display come from?

@KayakFisher205
Copy link

Could it be from the Extended Properties, with the Name =FieldName and Value=the Display Name?
image

@ErikEJ
Copy link
Owner Author

ErikEJ commented Feb 13, 2023

It could be the comment from the model, yes. I will provide an example later.

@ErikEJ
Copy link
Owner Author

ErikEJ commented Feb 14, 2023

@KayakFisher205 Just have a look at how "comments" are currently applied in the templates.

@ErikEJ ErikEJ changed the title Ideas for T4 customizations T4 customizations and tips Mar 25, 2023
@AchrWasUnavailable
Copy link

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 schemas, string outputContextDir, string modelNamespace, string contextNamespace, string projectPath, string outputPath) in C:/projects/efcorepowertools/src/GUI/RevEng.Core/ReverseEngineerScaffolder.cs:line 102
at ReverseEngineerResult RevEng.Core.ReverseEngineerRunner.GenerateFiles(ReverseEngineerCommandOptions options) in C:/projects/efcorepowertools/src/GUI/RevEng.Core/ReverseEngineerRunner.cs:line 88
at async Task EfReveng.Program.Main(string[] args) in C:/projects/efcorepowertools/src/GUI/efreveng/Program.cs:line 81

at ReverseEngineerResult EFCorePowerTools.Handlers.ReverseEngineer.ResultDeserializer.BuildResult(string output)
at async Task EFCorePowerTools.Handlers.ReverseEngineer.EfRevEngLauncher.GetOutputAsync()
at async Task 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 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:
image

@ErikEJ
Copy link
Owner Author

ErikEJ commented May 31, 2023

@AchrWasUnavailable Please ask in the EF Core repo, it needs to be fixed there (or maybe even in VS)

@piskov
Copy link

piskov commented Oct 21, 2023

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;
    }
<#
        }

@ErikEJ
Copy link
Owner Author

ErikEJ commented Oct 22, 2023

@piskov Thanks! Is this not "Custom Collection Initializers" from the list above?

@piskov
Copy link

piskov commented Oct 22, 2023

@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 :-)

@ErikEJ
Copy link
Owner Author

ErikEJ commented Oct 22, 2023

@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.

@rajibsm
Copy link

rajibsm commented Oct 22, 2023

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.

@piskov
Copy link

piskov commented Oct 22, 2023

@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).

@ErikEJ
Copy link
Owner Author

ErikEJ commented Oct 23, 2023

@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.

@rajibsm
Copy link

rajibsm commented Nov 9, 2023

@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.

@ErikEJ
Copy link
Owner Author

ErikEJ commented Jan 8, 2024

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

@zoinkydoink
Copy link

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

@ErikEJ
Copy link
Owner Author

ErikEJ commented Jan 21, 2024

@rick1c
Copy link

rick1c commented May 22, 2024

Is it possible to have some (or maybe all) generated classes derive from a base entity? i.e. add
: BaseEntity
to each class?

@ErikEJ
Copy link
Owner Author

ErikEJ commented May 22, 2024

@rick1c Yes, but it would go against Database First reverse engineering

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

9 participants