65.9K
CodeProject 正在变化。 阅读更多。
Home

来自 EDMX 文件的元数据类或数据注释

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.69/5 (7投票s)

2013年10月8日

CPOL

2分钟阅读

viewsIcon

31103

根据 EDMX 为视图模型或验证生成元数据类

引言

在代码优先方法中,所有 POCO 类都是手动编写的,并带有必要的数据标注。但在数据库优先方法中,类是基于 EDMX 文件生成的,而 EDMX 文件已经包含了一定数量的信息来填充数据标注属性。

在本教程中,我们将探讨如何基于 EDMX 文件生成元数据类,并将生成的类与实际模型关联。这些可以直接用作视图模型,或在任何层上进行验证。

用于元数据生成的 T4 模板

让我们从创建一个新的 T4 模板文件开始,命名为 DbModelMetadata.tt。我将在代码中添加大量注释,使其易于理解。

<#@ template language="C#" debug="true" hostspecific="true"#>
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Diagnostics" #>
<#@ include file="EF.Utility.CS.ttinclude"#><#@
 output extension=".cs"#><#

// Formatting helper for code
CodeGenerationTools code = new CodeGenerationTools(this);

// object for creating entity information
MetadataLoader loader = new MetadataLoader(this);
 
// TODO: NEED TO PROVIDE EDMX FILE LOCATION
string inputFile = @"EDMX file location";

// File generation suffix
string suffix = "Metadata";
 
// Meta data information for the conceptual model
EdmItemCollection ItemCollection = loader.CreateEdmItemCollection(inputFile);

// Suggested namespace
string namespaceName = code.VsNamespaceSuggestion();// + suffix;

// File generator according to different section
EntityFrameworkTemplateFileManager fileManager = 
            EntityFrameworkTemplateFileManager.Create(this);
    

// Loop through each entity type
foreach (EntityType entity in 
            ItemCollection.GetItems<EntityType>().OrderBy(e => e.Name))
{
    // File name for data annotation file
    string fileName = entity.Name + suffix + ".cs";
    
    // Check for file existence, If it does not
    // exist create new file for data annotation
    if (!DoesFileExist(fileName))
    {    
    
        // Header for file
        WriteHeader(fileManager);
        
        // Create new file
        fileManager.StartNewFile(fileName);    

        // Add namespaces into file
        BeginNamespace(namespaceName, code);
#>
/// <summary>
/// <#=code.Escape(entity)#> Metadata class
/// </summary>
<#= Accessibility.ForType(entity)#> 
  <#=code.SpaceAfter(code.AbstractOption(entity))#>partial class <#=code.Escape(entity) + suffix#>
{
<#
    // Loop through each primitive property of entity
    foreach (EdmProperty edmProperty in entity.Properties.Where(p => 
              p.TypeUsage.EdmType is PrimitiveType && p.DeclaringType == entity))
    {
#>
    /// <summary>
    /// <#=GetFriendlyName(code.Escape(edmProperty))#>
    /// </summary>        
<#    
        // Write display name data annotation    
        WriteDisplayName(edmProperty);

        // Write required field data annotation
        WriteRequiredAttribute(edmProperty);

        // Write string length annotation
        WriteStringLengthAttribute(edmProperty);
#>
    <#=Accessibility.ForProperty(edmProperty)#> <#=code.Escape(edmProperty.TypeUsage)#> 
      <#=code.Escape(edmProperty)#> { <#=Accessibility.ForGetter(edmProperty)#>get; 
      <#=Accessibility.ForSetter(edmProperty)#>set; }

<#        
    }
#>
}
<#
    // End namespace
    EndNamespace(namespaceName);
    }
    else
    {
        // Write with original file
        fileManager.StartNewFile(fileName);
        this.Write(OutputFile(fileName));
    }
}
fileManager.Process();

#>

<#+

// Write display name data annotation
void WriteDisplayName(EdmProperty edmProperty) {
    string displayName = edmProperty.Name;
    
    // Check for property name
    if (!string.IsNullOrEmpty(displayName)) 
    {
        // Generate user friendly name
        displayName = GetFriendlyName(edmProperty.Name);
        
        // Populate actual string to be written
        string displayAttribute = 
          string.Format("[DisplayName(\"{0}\")]", displayName);
#>
    <#=displayAttribute#>
<#+
    }
}

//add required attribute
void WriteRequiredAttribute(EdmProperty edmProperty) {
    
    // Check for required property
    if (!edmProperty.Nullable) 
    {
      WriteLine("{0}[Required(ErrorMessage = \"{1} is required\")]",
         CodeRegion.GetIndent(1),GetFriendlyName(edmProperty.Name));
    }
}

// Write max string length
void WriteStringLengthAttribute(EdmProperty edmProperty) { 
    
    // Object for retrieving additional information from property 
    Facet maxLengthfacet;
    
    // Try to get max length from property
    if (edmProperty.TypeUsage.Facets.TryGetValue("MaxLength", true, out maxLengthfacet)) 
    {
        // Max length for property
        double lengthAttribute;
        
        // Try to parse max length value
        if (double.TryParse(maxLengthfacet.Value.ToString(), out lengthAttribute)) 
        {
            // Generate actual string for attribute
            WriteLine("{0}[MaxLength({1}, ErrorMessage = \"{2} cannot " + 
              "be longer than {1} characters\")]",
              CodeRegion.GetIndent(1),lengthAttribute,GetFriendlyName(edmProperty.Name));
        }
    }
}

// Initialize header
void WriteHeader(EntityFrameworkTemplateFileManager fileManager, params string[] extraUsings)
{
    fileManager.StartHeader();
#>
using System; 
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
<#=String.Join(String.Empty, extraUsings.Select(u => "using " + u + 
                     ";" + Environment.NewLine).ToArray())#>
<#+ 
    fileManager.EndBlock();
}

// Add namespace
void BeginNamespace(string namespaceName, CodeGenerationTools code)
{
    // Generate region
    CodeRegion region = new CodeRegion(this);

    // Check for namespace value
    if (!String.IsNullOrEmpty(namespaceName))
    {
#>

namespace <#=code.EscapeNamespace(namespaceName)#>
{
<#+
        // Add indent
        PushIndent(CodeRegion.GetIndent(1));
    }
}

// End namespace
void EndNamespace(string namespaceName)
{
    if (!String.IsNullOrEmpty(namespaceName))
    {
        PopIndent();
#>
}
<#+
    }
}

#>

<#+

// Check for file existence
bool DoesFileExist(string filename)
{            
    return File.Exists(Path.Combine(GetCurrentDirectory(),filename));    
}

// Get current  folder directory
string GetCurrentDirectory()
{
    return System.IO.Path.GetDirectoryName(Host.TemplateFile);
}

// Get content of file name
string OutputFile(string filename)
{
    using(StreamReader sr = 
      new StreamReader(Path.Combine(GetCurrentDirectory(),filename)))
    {
        return sr.ReadToEnd();
    }
}

// Get friendly name for property names
string GetFriendlyName(string value)
{
return Regex.Replace(value,
            "([A-Z]+)", " $1",
            RegexOptions.Compiled).Trim();
}
#>

这将根据与 EDMX 关联的模型数量生成文件。所有这些类成员都将具有数据标注,例如 DisplayNameRequiredMaxLength,具体取决于模型。首次生成后,这些文件将不会被覆盖。因此,我们可以放心地根据需要修改数据标注,而不必担心丢失 TT 文件生成的类中的任何新添加的代码。

将生成的元数据类与实际模型关联

现在我们需要将元数据类与实际类关联。这可以通过将 MetaDataType 属性设置为我们的模型来完成。例如

[MetadataType(typeof(EmployeeTypeMetadata))]

EmployeeTypeMetadata 是来自上述 TT 文件的生成的元数据类的名称。

要设置这些属性,我们需要修改 EDMX 文件下的模型 TT 文件(例如 EmployeeModel.tt)。这将把元数据属性应用于 EDMX 下的所有模型。

 <#=codeStringGenerator.EntityClassOpening(entity)#>  

之上添加

[MetadataType(typeof(<#=code.Escape(entity)#>Metadata))] 

上面的代码会将元数据类设置为来自 EDMX 的实际模型。

你可能想知道为什么我们需要将元数据类与实际模型类分离。我们可以直接修改模型 TT 文件(例如 EmployeeModel.tt)来为所有类生成数据标注。但是,通过分离,我们将能够控制手动设置属性,而当 EDMX 更新时,对类的所有更改都会丢失。

注意:我没有尝试用复杂类型进行测试。

© . All rights reserved.