EntityFramework t4 template - XML documentation

2019-05-28 20:08发布

问题:

I have the following problem with my EDMX file.

In it I have written some Documentation for the properties as well as the Entities, but the t4 template of my EF 5 doesnt generate those values.

My desired result should be

public class Person
{
    /// <summary>
    /// Hello World Summary!!
    /// </summary>
    /// <remarks>
    /// Hello World Remarks!!
    /// </remarks>
    public string Name { get; set; }
}

But I only get

public class Person
{
    public string Name { get; set; }
}

And if not for this, why would I else be able to set documentation properties in the edmx file.

回答1:

It does appear that entity framework isn't going to generate the objects for you with code documentation, and I cant see a solution on nuget, which is a shame.

You can modify the T4 template to do this for you without too much hassle. To Get you started, if you open the T4 template for your entities, and find the line that looks like this (about line 74 for me):

    <#=codeStringGenerator.Property(edmProperty)#>

Then change it to something like this:

<# if (edmProperty != null && edmProperty.Documentation != null) {#>
/// <summary>
/// <#= edmProperty.Documentation.Summary #>
/// </summary>
/// <remarks>
/// <#= edmProperty.Documentation.LongDescription #>
/// </remarks>
<# } #>
    <#=codeStringGenerator.Property(edmProperty)#>

This will generate the documentation for your properties.

And also if you go to the line that looks like this (about line 28 for me):

<#=codeStringGenerator.EntityClassOpening(entity)#>

Then change it to something like this:

<# if (entity != null && entity.Documentation != null) {#>
/// <summary>
/// <#= entity.Documentation.Summary #>
/// </summary>
/// <remarks>
/// <#= entity.Documentation.LongDescription #>
/// </remarks>
<# } #>
<#=codeStringGenerator.EntityClassOpening(entity)#>

This should give you your class documentation.

There's probably a few other places that will need updating (like complex and navigation properties), but it should get you most of the way.

Matt



回答2:

Here's my take on this.

In the generated CodeStringGenerator class in your .tt file, I added this method:

private string Comments(EdmMember member)
{
    string comments = member.Documentation != null
        ? (string.IsNullOrEmpty(member.Documentation.Summary)
            ? ""
            : @"
/// <summary>
/// " + member.Documentation.Summary.Replace("\n", Environment.NewLine + "    /// ") + @" 
/// </summary>
")
            +
            (string.IsNullOrEmpty(member.Documentation.LongDescription)
            ? ""
            : @"/// <remarks>
/// " + member.Documentation.LongDescription.Replace("\n", Environment.NewLine + "    /// ") + @"
/// </remarks>
")
        : "";
    return comments;
}

I admit this method has two problems: a) it assumes a certain level of indentation, although this is the expected indentation level for a property, b) if there is no Summary, an extra line is added before the comment. But this is unlikely to happen.

Then, I call this method from the method that generate simple and navigation properties:

public string Property(EdmProperty edmProperty)
{
    string comments = Comments(edmProperty);
    return string.Format(
        CultureInfo.InvariantCulture,
        @"{5}{0} {1} {2} {{ {3}get; {4}set; }}",
        Accessibility.ForProperty(edmProperty),
        _typeMapper.GetTypeName(edmProperty.TypeUsage),
        _code.Escape(edmProperty),
        _code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
        _code.SpaceAfter(Accessibility.ForSetter(edmProperty)),
        comments);
}

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


回答3:

I'd like to add more details to Matt Mhetton's answer because because of my current project configuration (CA and Style cop) everything MUST be documented..

On the one hand you will have to add documentation properties in the designer for everything you want to document (classes, simple properties, complex properties and navigation properties)

On the other hand you have to open WhateverModel.tt you havecreated and edit it to contain documentation, I explain it step by step:

1.- For class documentation, look for:

<#=codeStringGenerator.UsingDirectives(inHeader: false)#>
<#=codeStringGenerator.EntityClassOpening(entity)#>

(line 26 in my case) and place your comments just between those two lines like this:

<#=codeStringGenerator.UsingDirectives(inHeader: false)#>
<# if (entity != null && entity.Documentation != null) {#>
/// <summary>
/// <#= entity.Documentation.Summary #>
/// </summary>
<# } #>
<#=codeStringGenerator.EntityClassOpening(entity)#>

2.- For constructor documentation look for public <#=code.Escape(entity)#>() thaqt's what generates the constructor so place your documentation just before that line like this:

<# if (entity != null && entity.Documentation != null) {#>
    /// <summary>
    /// <#= entity.Documentation.Summary #>
    /// </summary>
<# } #>
    public <#=code.Escape(entity)#>()
    {

3.- To document every property look for foreach (var edmProperty in simpleProperties), this look is the one that will generate properties code so, it should looks like this:

foreach (var edmProperty in simpleProperties)
        {
#>
<# if (edmProperty != null && edmProperty.Documentation != null) {#>    
    /// <summary>
    /// <#= edmProperty.Documentation.Summary #>
    /// </summary>
<# } #>
    <#=codeStringGenerator.Property(edmProperty)#>
<#

        }

(NOTE: that's for simple properties but is going to be3 the same way for complex properties...)

4.- For navigation properties exactly the same, look for foreach (var navigationProperty in navigationProperties) and add your documentation like this:

foreach (var navigationProperty in navigationProperties)
        {
#>
<# if (navigationProperty != null && navigationProperty.Documentation != null) {#>
    /// <summary>
    /// <#= navigationProperty.Documentation.Summary #>
    /// </summary>
<# } #>
    <#=codeStringGenerator.NavigationProperty(navigationProperty)#>
<#

        }

4.- Last but not least, if you also need documentation in your context class you could use this (look for <# if (code.Escape(container) != null) {#> ):

<# if (code.Escape(container) != null) {#>
/// <summary>
/// Represents <#= code.Escape(container) #>
/// </summary>
<# } #>
<#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> : DbContext
{

<# if (code.Escape(container) != null) {#>
    /// <summary>
    /// Initializes a new instance of the <see cref="<#= code.Escape(container) #>"/> class.
    /// </summary>
<# } #>
    public <#=code.Escape(container)#>()
    : base("name=<#=container.Name#>")
    {
<#
if (!loader.IsLazyLoadingEnabled(container))
{
#>
        this.Configuration.LazyLoadingEnabled = false;
<#
}
#>
    }

    /// <summary>
    /// On Model Creating
    /// </summary>
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        throw new UnintentionalCodeFirstException();
    }

<#
    foreach (var entitySet in container.BaseEntitySets.OfType<EntitySet>())
    {
#>

   <# if (code.Escape(entitySet) != null) {#>
    /// <summary>
    /// Gets or sets the <#=code.Escape(entitySet)#>
    /// </summary>
    <# } #>

I hope it helps like Matt Whetton's answer helped me (thank you Matt).