向Entity Framework代码中添加自定义属性特性

26

有没有一种方法可以向EF生成的代码中的属性添加自定义属性?我能想到的唯一可行的解决方案就是设计一个自定义T4模板。由于属性的属性无法确定,因此这样做可能不可行。

6个回答

37

您可以通过指定一个元数据类型来实现此操作,该类型镜像属性并仅用于归属。

[MetadataType(typeof(Dinner_Validation))] 
public partial class Dinner 
{} 

public class Dinner_Validation 
{ 
    [Required] 
    public string Title { get; set; } 
}

Steve Smith在这里写了博客。

不幸的是,上述方法容易因重构而变得脆弱。另一个选择是使用新的POCO实体。据我所知,它们完全避免了编译时代码生成。我还没有使用过它们,因此无法评论任何陷阱或权衡。


当我刚开始喜欢上EF时,突然意识到由于它们的处理方式,我的实体上的属性几乎不存在。真是气死人了。 - Chev
5
反射时,assembly.GetType(typeof(Dinner).ToString().GetProperties() 中的 property.Attributes 为空,并且 property.GetCustomAttributes(typeof(RequiredAttribute)) 返回一个长度为零的数组 - 应该反映 Dinner_Validation 还是其他问题出现了? - lukiffer

12

你可以使用设计器将此项添加到EDMX文件中:

<Property Name="Nome" Type="String" Nullable="false" MaxLength="50" Unicode="true" FixedLength="false" >
            <Documentation>
              <Summary>[MyCustomAttribute]</Summary>
            </Documentation>
</Property>

并替换T4:

void WriteProperty(CodeGenerationTools code, EdmProperty edmProperty)
{
    WriteProperty(Accessibility.ForProperty(edmProperty),
                  code.Escape(edmProperty.TypeUsage),
                  code.Escape(edmProperty),
                  code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
                  code.SpaceAfter(Accessibility.ForSetter(edmProperty)));
}

使用:

void WriteProperty(CodeGenerationTools code, EdmProperty edmProperty)
{
    if(edmProperty.Documentation != null && string.IsNullOrWhiteSpace(edmProperty.Documentation.Summary) == false)
    {
    #>
    <#=edmProperty.Documentation.Summary#>
<#+
    }
    WriteProperty(Accessibility.ForProperty(edmProperty),
                  code.Escape(edmProperty.TypeUsage),
                  code.Escape(edmProperty),
                  code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
                  code.SpaceAfter(Accessibility.ForSetter(edmProperty)));
}

你好,我该如何更改T4模板?我创建了一个Model1.tt文件,但它全是黑色文本,看起来像另一种语法。 - pungggi
更新,因为这个答案可能已经过时:对于EF版本6.2.0,您必须编辑方法public string Property(EdmProperty edmProperty)public string NavigationProperty(NavigationProperty navProp)。来自答案的if语句是相同的。 - H. Pauwelyn

9

您可以创建接口并在接口上声明属性。

partial class Person : IPerson {}

public interface IPerson
{
    [Required]
    string Name { get; set; }
}

3
作为对未来读者的一点提示,这在 asp.net-mvc 的验证中 不起作用 - Erik Philips
如果您实现了ValidationAttribute.IsValid,您可以反射到对象并获取接口。从那里,您可以获取该接口的成员,并查找具有自定义属性的任何成员。我正在使用上述方法来过滤掉具有[Password]属性的任何属性,以防止记录。 - spowser

4

您可以在EDMX文件中使用设计器添加以下内容:

<Property Name="Nome" Type="String" Nullable="false" MaxLength="50" Unicode="true" FixedLength="false" >
            <Documentation>
              <Summary>[MyCustomAttribute]</Summary>
            </Documentation>
</Property>

替换T4:

void WriteProperty(CodeGenerationTools code, EdmProperty edmProperty)
{
    WriteProperty(Accessibility.ForProperty(edmProperty),
                  code.Escape(edmProperty.TypeUsage),
                  code.Escape(edmProperty),
                  code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
                  code.SpaceAfter(Accessibility.ForSetter(edmProperty)));
}

使用:

void WriteProperty(CodeGenerationTools code, EdmProperty edmProperty)
{
    if(edmProperty.Documentation != null && string.IsNullOrWhiteSpace(edmProperty.Documentation.Summary) == false)
    {
    #>
    <#=edmProperty.Documentation.Summary#>
<#+
    }
    WriteProperty(Accessibility.ForProperty(edmProperty),
                  code.Escape(edmProperty.TypeUsage),
                  code.Escape(edmProperty),
                  code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
                  code.SpaceAfter(Accessibility.ForSetter(edmProperty)));
}

对于 Entity Framework 6,请替换

public string Property(EdmProperty edmProperty)
{
    return string.Format(
        CultureInfo.InvariantCulture,
        "{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)));
}

使用

public string Property(EdmProperty edmProperty)
{
    var description = String.Empty;
    bool isAttribute = false;

    if(edmProperty.Documentation != null &&
        string.IsNullOrWhiteSpace(edmProperty.Documentation.Summary) == false)
    {
        string summary = edmProperty.Documentation.Summary;
        if (!String.IsNullOrEmpty(summary) && summary.First() == '[' && summary.Last() == ']')
        {
            isAttribute = true;
        }

        if (isAttribute)
        {
            description = String.Format("\r\n\t{0}\r\n\t", summary);
        }
        else
        {
            description = String.Format("\r\n\t/// <summary>\r\n\t/// {0}\r\n\t/// </summary>\r\n\t", 
                summary);
        }

    }

    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)),
        description);
}

警告:

  • 命名空间需要绝对解析。
  • 假设属性以 '[' 开始以 ']' 结尾 -- 没有其他错误检查。
  • 如果找不到开放和关闭括号,则实体框架属性摘要将被包装在 XML 三斜杠注释中。
  • 尝试匹配默认的 Visual Studio 样式信息(实际上只是缩进),这可能适用于您的项目,也可能不适用。其中包括新行。

示例输出:

/// <summary>
/// content type
/// </summary>
public System.Guid ContentType { get; set; }

[System.ComponentModel.DisplayName("Last Modified")]
public System.DateTime LastModified { get; set; }

1
除了BurnsBA的回复之外,要将此应用于导航属性,还需要更新NavigationProperty()

public string NavigationProperty(NavigationProperty navProp)
{
    var description = String.Empty;
    if(navProp.Documentation != null && string.IsNullOrWhiteSpace(navProp.Documentation.Summary) == false)
    {
        string summary = navProp.Documentation.Summary;
        if (!String.IsNullOrEmpty(summary) && summary.First() == '[' && summary.Last() == ']')
        {
            description = String.Format("\r\n\t{0}\r\n\t", summary);
        }
        else
        {
            description = String.Format("\r\n\t/// <summary>\r\n\t/// {0}\r\n\t/// </summary>\r\n\t", summary);
        }
    }

    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)),
        description);
}

我使用这个来给我的属性添加 [Newtonsoft.Json.JsonIgnore]。
注意: 你必须将它们添加到 <...>Model.tt 而不是 <...>Model.Context.tt。

0

我认为你做不到。生成器将所有类声明为部分类,允许你进行扩展,但是它不允许你使用自定义属性标记属性,因为它会简单地覆盖它们。你能做的唯一一件事就是编写自己的实体。


这可能曾经是事实,但现在有几个选择。请查看我的答案获取更多信息。 - Drew Noakes

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