Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Metadata Class or Data Annotation from EDMX Files

0.00/5 (No votes)
8 Oct 2013 1  
Metadata class generation according to EDMX for view model or validation

Introduction

In Code First approach, all POCO classes are written manually with required data annotation but with database first approach, classes are generated based on EDMX file which already has a certain amount of information to populate data annotation attributes.

In this tutorial, we are going to look into how we can generate metadata classes based on EDMX file and associating generated classes with actual models. These could be used directly as view model or having validation on any layers.

T4 Template for Metadata Generation

Let's start with creating new T4 template file as DbModelMetadata.tt. I am putting up a lot of comments in code itself to make it self explanatory.

<#@ 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();
}
#>

This is going to generate files based upon number of models associated with EDMX. All those class members will be having data annotation like DisplayName, Required, MaxLength according to model. After first time generation, those files will not be overwritten. So, we can happily modify data annotation according to our need without worrying about loosing any newly added code under generated classes from TT file.

Associating Generated Metadata Classes to Actual Models

Now we need to associate metadata classes to actual classes. These could be done by setting MetaDataType attribute to our models. Example:

[MetadataType(typeof(EmployeeTypeMetadata))]

EmployeeTypeMetadata is the name of generated metadata class from the above TT file.

To set these attributes, we need to modify Model TT (Ex: EmployeeModel.tt) file found under EDMX file. This will apply metadata attribute to all models present under EDMX.

Just above:

 <#=codeStringGenerator.EntityClassOpening(entity)#>  

put:

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

The above code will set metadata classes to actual models from EDMX.

You might be thinking why we need to separate up metadata classes with actual model classes. We could have directly modified Model TT (Ex: EmployeeModel.tt) file to generate data annotation for all classes. But with separation, we will be having control on setting attributes manually whereas whenever EDMX gets updated, all changes to classes get lost.

Note: I have not tried to test it with complex types.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here