改进反向工程数据库时的导航属性名称

59
我正在使用Entity Framework 5和Visual Studio,以及Entity Framework Power Tools Beta 2来反向工程化中等大小的数据库(~100张表)。
不幸的是,导航属性没有有意义的名称。例如,如果有两个表:
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),
    ...
}

这将生成如下类:

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

我看到有几种替代方案都比这更好:

  • 使用外键的名称: 例如,除了最后一个下划线后面的所有内容 (FK_Projects_TechnicalContact --> TechnicalContact)。虽然这可能是最有控制力的解决方案,但这可能更难与现有模板集成。
  • 使用与外键列对应的属性名称:去掉后缀ID (TechnicalContactID --> TechnicalContact)
  • 使用属性名称和现有方案的连接:例如TechnicalContactIDProjects(集合)和TechnicalContactIDContact

幸运的是,可以通过将模板包含在项目中来修改模板

必须对Entity.ttMapping.tt进行修改。由于缺乏智能感和调试功能,我发现这很困难。


连接属性名称(上述列表中的第三个)可能是最容易实现的解决方案。

如何更改Entity.ttMapping.tt中导航属性的创建以实现以下结果:

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

1
你可以下载并使用Tangible T4编辑器来使T4编辑变得更加容易:http://t4-editor.tangible-engineering.com/T4-Editor-Visual-T4-Editing.html。免费版本有限制,但总比没有好 :) - Spikeh
4个回答

52

在.tt文件内有一些需要更改的地方。我选择了你提出的第三种解决方案,但这需要格式类似于FK_CollectionName_RelationName。我使用下划线将它们分开,并使用数组中的最后一个字符串。我使用ToEndMember属性中的RelationName创建属性名称。FK_Projects_TechnicalContact将会是结果。

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

而你的项目将会像这样。

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

现在你可能会问代码。我已经在T4文件中的CodeStringGenerator类中添加了2个函数。其中一个函数用于构建接收NavigationProperty的propertyName,另一个函数用于生成接收NavigationProperty和属性名称的属性代码。

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

如果您将上述代码放入类中,仍需更改2个部分。您需要找到构造函数部分和实体的导航属性部分正在构建的位置。在构造函数部分(大约在第60行左右),您需要替换现有代码,通过调用方法GetPropertyNameForNavigationProperty并将其传递到转义方法来完成。

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

并且在NavigationProperties部分(大约在第100行左右),您还需要用以下代码替换代码。

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

希望这可以帮到您,您始终可以调试 GetPropertyNameForNavigationProperty 函数并尝试更改属性的命名。


1
哪个 T4 文件包含 CodeStringGenerator 类? - marapet
1
EntityFramework 5 中的 model.tt 文件在大约 260 行左右。这里有一张截图可能会有所帮助 http://imgur.com/f6PNq - Rik van den Berg
2
我将修改后的模板放在了 GitHub 上(http://git.io/I2iNGw),并在 foreignkeyname 分支中使用了您的输入。最终,我可能会使用另一个版本(propertyname 分支),因为各种数据库中外键的命名并不像我预期的那样一致。代码离优雅还有很大距离,可以使用一些 t4-ninja-love 来减少重复,但我只是想保持简单,以便在发布 Power Tools 的新版本时更新。 - marapet
4
我收到一个错误提示,说需要的属性不存在。它仍然“想要”可怕的导航属性。我需要实际更改导航属性本身,而不仅仅是更改模型的编写方式。 - NullVoxPopuli
1
@RikvandenBerg 我能够使用您的解决方案来构建我想要的属性名称的模型,但是像NullVoxPopuli和Mara一样,当EF尝试使用原始PropertyNames时,我会收到运行时错误。我们是否缺少其他部分来正确解析运行时中的PropertyNames? - Arkiliknam
显示剩余12条评论

9

在BikeMrown的回答基础上,我们可以使用在MSSQL中设置的RelationshipName来为属性添加Intellisense:

MSSQL relationships

编辑你的VS项目中的model.tt文件,并将其更改为以下内容:

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

转换为:

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

现在,当您开始输入属性名称时,您会得到如下的提示框: 智能感知工具提示框 需要注意的是,如果更改了数据库模型,属性可能会指向不同的数据库字段,因为 EF 生成导航属性名称基于它们各自的数据库字段名称的字母优先级!

这个对我来说很完美!简单而干净的解决方案。 - Ricky Yip

4

发现这个问题/答案非常有帮助。然而我不想像Rikko的回答那样做那么多。我只需要找到与NavigationProperty相关的列名,但是在任何示例中都无法看到如何获得它(至少没有edmx可以提取)。

<#
  var association = (AssociationType)navProperty.RelationshipType;
#>  //  <#= association.ReferentialConstraints.Single().ToProperties.Single().Name #>

1
所选答案很棒,让我朝着正确的方向前进。但我的一个大问题是它将我已经工作的导航属性全部附加了基类型名称,因此你会得到像以下这样的东西。
public virtual Need UnitNeed { get; set;}
public virtual ShiftEntered UnitShiftEntered {get; set;}`

我深入研究了.tt文件中提出的添加内容,并进行了一些修改,以消除重复的类型命名并使其更加简洁。我想肯定还有其他人也需要同样的东西,所以我决定在这里发布我的解决方案。

以下是需要更新的代码,位于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;
}

这是更新模型生成过程中的代码:
更新这两个位置的代码:
var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty)

这个的意思是“将此转换为”。
var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty, entity.Name);

我刚刚将此添加到Github(免责声明,我对Github并不熟悉)https://github.com/DylanTheDev/EF-NavPropFix/blob/master/EFModel.tt - Dylan Hayes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接