59

I'm using Entity Framework 5 with Visual Studio with Entity Framework Power Tools Beta 2 to reverse engineer moderately sized databases (~100 tables).

Unfortunately, the navigation properties do not have meaningful names. For example, if there are two tables:

CREATE TABLE Contacts (
    ContactID INT IDENTITY (1, 1) NOT NULL,
    ...
    CONSTRAINT PK_Contacts PRIMARY KEY CLUSTERED (ContactID ASC)
}

CREATE TABLE Projects (
    ProjectID INT IDENTITY (1, 1) NOT NULL,
    TechnicalContactID INT NOT NULL,
    SalesContactID INT NOT NULL,
    ...
    CONSTRAINT PK_Projects PRIMARY KEY CLUSTERED (ProjectID ASC),
    CONSTRAINT FK_Projects_TechnicalContact FOREIGN KEY (TechnicalContactID)
        REFERENCES Contacts (ContactID),
    CONSTRAINT FK_Projects_SalesContact FOREIGN KEY (SalesContactID)
        REFERENCES Contacts (ContactID),
    ...
}

This will generate classes like this:

public class Contact
{
     public Contact()
     {
          this.Projects = new List<Project>();
          this.Projects1 = new List<Project>();
     }
     public int ContactID { get; set; }
     // ...
     public virtual ICollection<Project> Projects { get; set; }
     public virtual ICollection<Project> Projects1 { get; set; }
}

public class Project
{
     public Project()
     {

     }
     public int ProjectID { get; set; }
     public int TechnicalContactID { get; set; }
     public int SalesContactID { get; set; }
     // ...
     public virtual Contact Contact { get; set; }
     public virtual Contact Contact1 { get; set; }
}

I see several variants which would all be better than this:

  • Use the name of the foreign key: For example, everything after the last underscore (FK_Projects_TechnicalContact --> TechnicalContact). Though this probably would be the solution with the most control, this may be more difficult to integrate with the existing templates.
  • Use the property name corresponding to the foreign key column: Strip off the suffix ID (TechnicalContactID --> TechnicalContact)
  • Use the concatenation of property name and the existing solution: Example TechnicalContactIDProjects (collection) and TechnicalContactIDContact

Luckily, it is possible to modify the templates by including them in the project.

The modifications would have to be made to Entity.tt and Mapping.tt. I find it difficult due to the lack of intellisense and debug possibilities to make those changes.


Concatenating property names (third in above list) is probably the easiest solution to implement.

How to change the creation of navigational properties in Entity.tt and Mapping.tt to achieve the following result:

public class Contact
{
     public Contact()
     {
          this.TechnicalContactIDProjects = new List<Project>();
          this.SalesContactIDProjects = new List<Project>();
     }
     public int ContactID { get; set; }
     // ...
     public virtual ICollection<Project> TechnicalContactIDProjects { get; set; }
     public virtual ICollection<Project> SalesContactIDProjects { get; set; }
}

public class Project
{
     public Project()
     {

     }
     public int ProjectID { get; set; }
     public int TechnicalContactID { get; set; }
     public int SalesContactID { get; set; }
     // ...
     public virtual Contact TechnicalContactIDContact { get; set; }
     public virtual Contact SalesContactIDContact { get; set; }
}
marapet
  • 54,856
  • 12
  • 170
  • 184
  • 1
    FYI, you can download and use Tangible T4 editor to make T4 editing a little easier: http://t4-editor.tangible-engineering.com/T4-Editor-Visual-T4-Editing.html. The free version is limited, but it's better than nothing at all :) – Spikeh Oct 30 '12 at 12:04

4 Answers4

52

There a few things you need to change inside the .tt file. I choose to use the third solution you suggested but this requires to be formatted like FK_CollectionName_RelationName. I split them up with '_' and use the last string in the array. I use the RelationName with the ToEndMember property to create a property name. FK_Projects_TechnicalContact will result in

//Plularized because of EF. 
public virtual Contacts TechnicalContactContacts { get; set; }

and your projects will be like this.

public virtual ICollection<Projects> SalesContactProjects { get;  set; }
public virtual ICollection<Projects> TechnicalContactProjects { get;  set; }

Now the code you may ask. Ive added 2 functions to the CodeStringGenerator class in the T4 file. One which builds the propertyName recieving a NavigationProperty. and the other one generating the code for the property recieving a NavigationProperty and the name for the property.

//CodeStringGenerator class
public string GetPropertyNameForNavigationProperty(NavigationProperty navigationProperty)
{
    var ForeignKeyName = navigationProperty.RelationshipType.Name.Split('_');
    var propertyName = ForeignKeyName[ForeignKeyName.Length-1] + navigationProperty.ToEndMember.Name;
    return propertyName;
}

public string NavigationProperty(NavigationProperty navigationProperty, string name)
{
    var endType = _typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType());
    return string.Format(
        CultureInfo.InvariantCulture,
        "{0} {1} {2} {{ {3}get; {4}set; }}",
        AccessibilityAndVirtual(Accessibility.ForProperty(navigationProperty)),
        navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType,
        name,
        _code.SpaceAfter(Accessibility.ForGetter(navigationProperty)),
        _code.SpaceAfter(Accessibility.ForSetter(navigationProperty)));
}

If you place the above code in the class you still need to change 2 parts. You need to find the place where the constructor part and the navigation property part are being build up of the entity. In the constructor part (around line 60) you need to replace the existing code by calling the method GetPropertyNameForNavigationProperty and passing this into the escape method.

      var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty);
#>
      this.<#=code.Escape(propName)#> = new HashSet<<#=typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType())#>>();
<#

And in the NavigationProperties part (around line 100) you also need to replace the code with the following.

    var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty);
#>
    <#=codeStringGenerator.NavigationProperty(navigationProperty, propName)#>
<#

I hope this helps and you can always debug the GetPropertyNameForNavigationProperty function and play a little with the naming of the property.

Rik van den Berg
  • 2,840
  • 1
  • 18
  • 22
  • 1
    Which T4 file contains the `CodeStringGenerator` class? – marapet Oct 25 '12 at 08:35
  • 1
    the model.tt in EntityFramework 5. It hidden around around line 260. here is a screenshot that might help http://imgur.com/f6PNq – Rik van den Berg Oct 25 '12 at 08:49
  • Where is this file located? Did you use the "Reverse Engineer Code First" functionality of the "Entity Framework Power Tools Beta 2" ? – marapet Oct 25 '12 at 08:56
  • Ok I see you didn't use the power tools. I'll check whether I can adapt your changes for the power tools templates. – marapet Oct 25 '12 at 09:00
  • Sorry about that. Should have read it more carefully, but yes you can indeed change the tt files with some of my exmaples. Line 30, 51 in Entity.tt need to be changed to the last 2 examples I gave. Line 236 for the WithMany also need to be changed. Let me know if you need anything else – Rik van den Berg Oct 25 '12 at 09:19
  • 2
    I put the modified templates on github (http://git.io/I2iNGw), and adapted your input in the foreignkeyname branch. I'll finally probably use yet another version (propertyname branch) because the naming of the foreign key in the various databases isn't as consistent as I expected. The code is far from elegant and could use some t4-ninja-love to reduce duplication, but I just wanted to keep it simple to update when new versions of the Power Tools are released. – marapet Oct 28 '12 at 21:32
  • Let's not waste those bounty points - this wasn't exactly the answer I expected, but by far the best one :-) and enough to start modifying the templates. Thank you! – marapet Oct 30 '12 at 13:46
  • 1
    @marapet thanks for posting the templates on github, I needed to do exactly what you did in the propertyname branch (with a few tweaks) and your code saved me hours of frustration! much appreciated! – Rowan Mar 31 '13 at 08:39
  • @Rowan I'm glad it was useful. I currently use templates in the propertyname branch (they do create some looong property names...) but didn't have time to go back to work on them. I'd of course be interested in your tweaks if you don't mind sharing ;) – marapet Apr 01 '13 at 21:10
  • 4
    I got an error saying that the required property does not exist. It still *wants* the horrible navigation properties. I need to actually change the navigation properties themselves, rather than just change how the models are written – NullVoxPopuli Feb 05 '16 at 20:12
  • @NullVoxPopuli Hey, did you sort that problem out? Or is this solution useless? getting the same error.. Thanks – Mara Jul 15 '16 at 13:47
  • I solved the problem by using a different tool. lol https://visualstudiogallery.msdn.microsoft.com/ee4fcff9-0c4c-4179-afd9-7a2fb90f5838 – NullVoxPopuli Jul 15 '16 at 13:56
  • @Mara I'm having the same issue, did you ever resolve yours without using a different tool? – jtate Dec 09 '16 at 14:41
  • @jtate unfortunately not, the names were OK in the generated files, but not in the designer, gave up on it. – Mara Dec 21 '16 at 10:03
  • @Mara yea same. Everything would compile but then I would get a run time error when fetching something from the db. I gave up too :\ – jtate Dec 21 '16 at 13:42
  • 1
    @RikvandenBerg I was able to use your solution to build my models with the property names I wanted, however like NullVoxPopuli and Mara, I recieve runtime errors when EF attempts to use the original PropertyNames. Is there another part to this solution we are missing to resolve PropertyNames correctly in Runtime? – Arkiliknam Feb 17 '17 at 15:24
  • I'm getting "The entity type is not part of the model for the current context " Exception ... – cah1r Aug 03 '17 at 06:31
9

Building on BikeMrown's answer, we can add Intellisense to the properties using the RelationshipName that is set in MSSQL:

MSSQL relationships

Edit model.tt in your VS Project, and change this:

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
<#
            }
#>
    <#=codeStringGenerator.NavigationProperty(navigationProperty)#>
<#
        }
    }

to this:

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
<#
            }
#>
    /// <summary>
    /// RelationshipName: <#=code.Escape(navigationProperty.RelationshipType.Name)#>
    /// </summary>
    <#=codeStringGenerator.NavigationProperty(navigationProperty)#>
<#
        }
    }

Now when you start typing a property name, you get a tooltip like this: Intellisense tooltip

It's probably worth noting that if you change your DB model, the properties may find themselves pointing at different DB fields because the EF generates navigation property names based on their respective DB field name's alphabetic precedence!

4

Found this question/answer very helpful. However, I didn't want to do as much as Rikko's answer. I just needed to find the column name involved in the NavigationProperty and wasn't seeing how to get that in any of the samples (at least not without an edmx to pull from).

<#
  var association = (AssociationType)navProperty.RelationshipType;
#>  //  <#= association.ReferentialConstraints.Single().ToProperties.Single().Name #>
BikeMrown
  • 1,140
  • 2
  • 10
  • 17
1

The selected answer is awesome and got me going in the right direction for sure. But my big problem with it is that it took all of my already working navigation properties and appended the base type name to them, so you'd end up with with things like the following.

public virtual Need UnitNeed { get; set;}
public virtual ShiftEntered UnitShiftEntered {get; set;}`

So I dug into the proposed additions to the .tt file and modified them a bit to remove duplicate type naming and clean things up a bit. I figure there's gotta be someone else out there that would want the same thing so I figured I'd post my resolution here.

Here's the code to update within the public class CodeStringGenerator

public string GetPropertyNameForNavigationProperty(NavigationProperty navigationProperty, string entityname = "")
{
    var ForeignKeyName = navigationProperty.RelationshipType.Name.Split('_');
    var propertyName = "";

    if (ForeignKeyName[ForeignKeyName.Length-1] != entityname){
        var prepender = (ForeignKeyName[ForeignKeyName.Length-1].EndsWith(entityname)) ? ReplaceLastOccurrence(ForeignKeyName[ForeignKeyName.Length-1], entityname, "") :  ForeignKeyName[ForeignKeyName.Length-1];
        propertyName = prepender + navigationProperty.ToEndMember.Name;
    }
    else {
        propertyName = navigationProperty.ToEndMember.Name;
    }

    return propertyName;
}

public string NavigationProperty(NavigationProperty navigationProperty, string name)
{
    var endType = _typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType());

    var truname = name;

    if(navigationProperty.ToEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many){
        if(name.Split(endType.ToArray<char>()).Length > 1){
            truname = ReplaceLastOccurrence(name, endType, "");
        }
    }

    return string.Format(
        CultureInfo.InvariantCulture,
        "{0} {1} {2} {{ {3}get; {4}set; }}",
        AccessibilityAndVirtual(Accessibility.ForProperty(navigationProperty)),
        navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType,
        truname,
        _code.SpaceAfter(Accessibility.ForGetter(navigationProperty)),
        _code.SpaceAfter(Accessibility.ForSetter(navigationProperty)));
}

public static string ReplaceLastOccurrence(string Source, string Find, string Replace)
{
        int place = Source.LastIndexOf(Find);

        if(place == -1)
           return Source;

        string result = Source.Remove(place, Find.Length).Insert(place, Replace);
        return result;
}

and here's the code to update within the model generation,

update both occurrences of this:

var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty)

to this

var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty, entity.Name);
Dylan Hayes
  • 2,331
  • 1
  • 23
  • 33
  • I've also just now added this to github (disclaimer, im not well versed in github) https://github.com/DylanTheDev/EF-NavPropFix/blob/master/EFModel.tt – Dylan Hayes Oct 25 '16 at 16:00