EFCorePowerTools icon indicating copy to clipboard operation
EFCorePowerTools copied to clipboard

Reverse Engineer - Rename navigation properties

Open WalterAtTally opened this issue 5 years ago • 47 comments

Is it possible to rename the generated navigation properties?

Steps to reproduce

Say there are two tables,

CREATE TABLE Product ( Id UNIQUEIDENTIFIER NOT NULL PRIMARY KEY, CategoryCode VARCHAR(20) FOREIGN KEY (CategoryCode) REFERENCES Category (Code) NULL, ParentId UNIQUEIDENTIFIER FOREIGN KEY (ParentId) REFERENCES Product (Id) NULL )

CREATE TABLE Category ( Code VARCHAR(20) NOT NULL PRIMARY KEY, Description VARCHAR(100) NOT NULL )

Product table references both Category and Product itself. The generated class contains the following navigation properties, which I very much want to rename to something more meaningful.

public Category CategoryCodeNavigation { get; set; } public Product ParentProduct {get;set;} public virtual ICollection<Product> InverseProduct {get; set;}

I have tried to use efpt.renaming.json, but it only supports renaming columns not FK relations.

I understand I can just manually rename the properties after the code-gen, but the changes will be overwritten when the project is re-generated which we do quite often.

So is there a workaround to rename FK navigation properties?

Thanks for any tips!

Further technical details

EF Core Power Tools version: (found in About dialog - blue questionmark icon on context menu) 2.2.12.0

Database engine: (SQlite, SQL Compact, SQL Server, Postgres) Azure SQL

Visual Studio version: (e.g. Visual Studio 2017 15.7) VS2019

WalterAtTally avatar Apr 25 '19 10:04 WalterAtTally

I think it is not possible

ErikEJ avatar Apr 27 '19 12:04 ErikEJ

Not even with Handlebars?

EDIT: I am facing a similar issue, where my navigation property name gets "ID" stripped from it, and leaves a weird name for the property.

germancasares avatar May 03 '19 16:05 germancasares

Feel free to give it a try

ErikEJ avatar May 03 '19 16:05 ErikEJ

+1 for this feature request. @ErikEJ - from your understanding, is this still not technically possible? It's been a year now, so I figured it was worth asking - even if a long shot. I'd be happy to have a go at contributing an update if there was a workable solution - would just need a few pointers to get going.

Thanks for this great tool. It is immensely helpful.

jamshally avatar Sep 18 '20 11:09 jamshally

@jamshally Let's have a look - please explain (with some sample code) what you see now, and what you would like to see instead.

ErikEJ avatar Sep 18 '20 12:09 ErikEJ

@ErikEJ - Thanks, here is the scenario:

I have a table with a two column foreign key. The Auto-name-generation of Navigation Property seems to look for common characters between the foreign key fields, sometimes resulting in unsuitable field names. I am looking for a way to rename these Navigation properties during a reverse-engineering code-generation cycle. Example code below:

Table SQL

CREATE TABLE [dbo].[Product] (
    [FactoryId]           NCHAR (10) NOT NULL,   -- // <-- Part of the Composite Primary Key
    [FactoryProductIndex] NCHAR (10) NOT NULL,   -- // <-- Part of the Composite Primary Key
    [Data]                NCHAR (10) NULL,
    CONSTRAINT [PK_Product] PRIMARY KEY CLUSTERED ([FactoryId] ASC, [FactoryProductIndex] ASC)
);

CREATE TABLE [dbo].[ProductMetaData] (
    [FactoryId]           NCHAR (10)   NOT NULL,
    [FactoryProductIndex] NCHAR (10)   NOT NULL,
    [MetaData]            VARCHAR (50) NULL,
    CONSTRAINT [PK_ProductMetaData] PRIMARY KEY CLUSTERED ([FactoryId] ASC, [FactoryProductIndex] ASC),
    CONSTRAINT [FK_ProductMetaData_Product] FOREIGN KEY ([FactoryId], [FactoryProductIndex]) REFERENCES [dbo].[Product] ([FactoryId], [FactoryProductIndex])
);

Generated Model Class

    public partial class Product
    {
        public string FactoryId { get; set; }            // <-- Part of the Composite Primary Key
        public string FactoryProductIndex { get; set; }  // <-- Part of the Composite Primary Key
        public string Data { get; set; }

        public virtual ProductMetaDatum ProductMetaDatum { get; set; }
    }

    public partial class ProductMetaDatum
    {
        public string FactoryId { get; set; }
        public string FactoryProductIndex { get; set; }
        public string MetaData { get; set; }

        public virtual Product Factory { get; set; } //<-- I would like to rename this Navigation property be "Product"
    }

jamshally avatar Sep 18 '20 21:09 jamshally

What custom name?

ErikEJ avatar Sep 19 '20 05:09 ErikEJ

What custom name? @ErikEJ - I have several similar scenarios in a database I am working with. In this instance I'd like to name the Navigation Property "Product"

I have updated the above example to be a little more intuitive, and illustrate the problem better. Due to the auto-naming algorithm around composite primary keys, the Navigation Property is named "Factory", whereas I would like to name it "Product"

jamshally avatar Sep 19 '20 07:09 jamshally

@jamshally Have you tried the renaming feature ?- with this efpt.renaming.json file, for example:

[
  {
    "UseSchemaName": false,
    "SchemaName": "dbo",
    "Tables": [
      {
        "Name": "ProductMetaData",
        "Columns": [
          {
            "Name": "FactoryId",
            "NewName": "ProductId"
          }
        ]
      }
    ]
  }
]

ErikEJ avatar Sep 20 '20 15:09 ErikEJ

Hi @ErikEJ , thanks for the suggestion.

This does result in the Navigation Property being generated with the name "Property", but then the name of the FactoryId is no longer correct/consistent with the business domain. The primary key of the Product is the combination of the FactoryId and the FactoryProductIndex.

Basically, the naming of the primary key fields are as-intended, it is just the auto-generated naming of the Navigation Property I would like to change.

jamshally avatar Sep 22 '20 05:09 jamshally

Not something I plan to implement

ErikEJ avatar Oct 25 '20 16:10 ErikEJ

Just coming to EF Core Power Tools, and it seems like this is should be an obvious problem. Every time I re-generate, I have to re-name several fields because they have bizarre names, to the extent that I'm the only one on my team who is allowed to re-generate... Bummed to discover there's no remedy and it isn't being considered.

Is this something you would accept a pull request on, if I could figure out how to get it done?

kinetiq avatar Dec 08 '20 15:12 kinetiq

@kinetiq sure, a PR that does not pull in huge amounts of EF Core code to do this would be appreciated.

ErikEJ avatar Dec 08 '20 15:12 ErikEJ

@ErikEJ Thanks, I will see what I can do. :)

kinetiq avatar Dec 08 '20 16:12 kinetiq

@kinetiq great, let me know if I can help in any way.

ErikEJ avatar Dec 08 '20 16:12 ErikEJ

@ErikEJ So I found what seem to be the key components, maybe you could confirm that I'm on the right track.

I'm also going to call on the other folks who care about this to pitch in and see if you can help solve this. There is plenty for everyone.

  • ReverseEngineer20.ReverseEngineer.ReplacingCandidateNamingService seems to be where the magic happens. Not a lot of comments to be found, but it seems this whole class is about overriding internal APIs to customize the reverse engineering process. GenerateCandidateIdentifier for instance looks like it might be specific to generating names for tables, and that isn't what I need (or is it?), but I can see a pattern for using the Schema class to customize things. @ErikEJ could you confirm that I'm in the right place, and that this is the only place I need to care about for this? The fact that the namespace is numbered worries me.
  • This lead me to Schema and the List of Schema on ReplacingCandidateNamingService. I initially wondered why a List<Schema> was required since Schema contains a list of table details, but then I realized it covers cases where a database goes beyond dbo and has multiple schema names. No problem. I also found some tests that were helpful. I could use some help understanding how to extend this.
  • Back to ReplacingCandidateNamingService, I see GetDependentEndCandidateNavigationPropertyName and it seems important. I see that it takes IForeignKey as a parameter and you are doing something custom with it. What is this? I did some googling and found no clues.
  • Well actually, I found out that GetPrincipalEndCandidateNavigationPropertyName also exists and can be overridden. Source for both is here: https://github.com/dotnet/efcore/blob/main/src/EFCore.Design/Scaffolding/Internal/CandidateNamingService.cs ... This appears to handle a lot of scary-looking edge cases without a single comment. The lack of comments is quite discouraging.
  • I would imagine that GetDependentEndCandidateNavigationPropertyName and GetPrincipalEndCandidateNavigationPropertyName might handle the two sides of the navigation property... Can anyone confirm how these fit together? Both of them take IForeignKey which seems strange; for my theory to be correct, one of them would probably be a primary key.

So this is turning into a lot. Any help would be appreciated.

Update: heading to bed but I just found this and I wanted to capture it for the future! It seems to document how these two key methods work. https://entityframeworkcore.com/knowledge-base/50621409/ef-core--nomenclatura-de-propiedades-de-navegacion-en-andamios ... Could it be as simple as checking the schema at the top of these two methods and returning whatever it contains?

kinetiq avatar Dec 11 '20 03:12 kinetiq

Not detecting a lot of passion on this. :)

kinetiq avatar Dec 14 '20 22:12 kinetiq

Loud and clear, this is not something anyone cares to help with. Might as well close this one, I am tapping out.

kinetiq avatar Dec 21 '20 14:12 kinetiq

There are a lot of complexities involved here, like how do you specify the revised naming rules, that need to be sorted out before actually implementation of any renaming mechanics

ErikEJ avatar Dec 21 '20 15:12 ErikEJ

I wonder if the ability to rename tables and columns solves this issue?

ErikEJ avatar Dec 21 '20 15:12 ErikEJ

There are a lot of complexities involved here, like how do you specify the revised naming rules, that need to be sorted out before actually implementation of any renaming mechanics

True, but that is relatively straightforward compared to the technical part. My intention was to set this up so that it does whatever it normally does, but defers to some new items in the configuration file.

I wonder if the ability to rename tables and columns solves this issue?

Based on the 9/22 response from @jamshally, it seemed like this was not direct enough. Although I should probably test it for my own purposes.

kinetiq avatar Dec 21 '20 17:12 kinetiq

Could you outline what the configuration entries could look like?

ErikEJ avatar Dec 21 '20 18:12 ErikEJ

@ErikEJ I haven't gotten that far, I am still in the "is this even possible and where would the code go" phase.

Maybe under Tables > Columns there could be a setting specifically for naming navigation properties, or maybe even more control than that would be required.

kinetiq avatar Dec 21 '20 18:12 kinetiq

Initial (experimental) implementation of this is now available in the latest daily build, pls consult the PR to understand the changes and how to test it: https://github.com/ErikEJ/EFCorePowerTools/pull/734

ErikEJ avatar Jan 26 '21 18:01 ErikEJ

The current experimental implementation is causing too much confusion and is broken.

I will remove and re-think.

ErikEJ avatar May 02 '21 07:05 ErikEJ

Yes, really hoping you get this working at some point. Seems like in the experimental version, I was able to get it to work but there were cases that it just didn't cover.

kinetiq avatar May 03 '21 00:05 kinetiq

but there were cases that it just didn't cover.

@kinetiq Exactly, and I did not want that support burden of a broken feature.

The previous attempt: https://github.com/ErikEJ/EFCorePowerTools/commit/f9e962d6607bca53e3787aa985a73028644a16a0

ErikEJ avatar May 03 '21 06:05 ErikEJ

@ErikEJ Wouldn't it be possible to just use the "plural naming convention" on the auto generated navigation properties?

Please see the sample code below:

public partial class Order
{
	public long OrderId { get; set; }
	public long CustomerId { get; set; } 

	public Customer Customer { get; set; }
	
	public virtual List<OrderItem> OrderItems { get; set; } // <-- Plural naming convention
	// or public virtual IEnumerable<OrderItem> OrderItems { get; set; }
	// or public virtual ICollection<OrderItem> OrderItems { get; set; }
	// ...
}

public partial class OrderItem
{
	public long OrderItemId { get; set; }
	public string Description { get; set; }	
	// ...
}

OculiViridi avatar Jun 07 '21 12:06 OculiViridi

The hook that is currently there is too early in the naming pipeline

ErikEJ avatar Jun 07 '21 13:06 ErikEJ

Might be just very nainve suggestion, but wouldn't refactor/rename do the trick? I mean that's what I'm actually doing after running the tool.

viktorszekeress avatar Jun 24 '21 07:06 viktorszekeress