[Entity Framework] 利用T4快速建立Entity Data Model之MetaData類別

利用T4快速建立Entity Data Model之MetaData類別

 

前言簡述

 

在使用Entity Framework之Database First模式進行開發時,會透過現有DB資料表來建立出實體資料模型(EDMX),當需要在模型屬性(Property)上註記標籤(Attribute)時,由於從Visual Studio中更新EDMX時會將模型中的異動覆蓋掉,所以這時就需要另外建立一個Metadata類別,好讓Model中的Property與Metadata分離,當DB中資料的結構有所異動的時候,更新Edmx時就不會影響到我們所定義的Metadata。此篇文章將偏重Metadata的快速建立,並且體驗T4 Template所帶來的便捷性。

 

 

實作說明

 

使用前請先安裝T4編輯器 (tangible T4 Editor)

讓原本黑白T4編輯畫面迎向彩色的世界(擁有Intellisense便捷性)

 

image

image

 

在專案中新增ADO.NET實體資料模型進行測試

image

 

產生出EDMX檔案

image

 

於Models中新增文字範本(T4 Template)

image

image

 

文字範本內容如下

主要就是指定EDMX檔案位置,然後產出各Model的Partial Class,並建立相對應的Matedata Inner Class。

<#@ 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 = @"D:\YourDdxmLocation\DbModel.edmx"; 

// 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)#> class
/// </summary>
[MetadataType(typeof(<#=code.Escape(entity) + suffix#>))]
<#= Accessibility.ForType(entity)#> <#=code.SpaceAfter(code.AbstractOption(entity))#> partial class <#=code.Escape(entity)#>
{

	/// <summary>
	/// <#=code.Escape(entity)#> Metadata class
	/// </summary>
	<#= Accessibility.ForType(entity)#>  <#=code.SpaceAfter(code.AbstractOption(entity))#> 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))
		{
	#>
	<#= CodeRegion.GetIndent(1) #>
		/// <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; }

	<#        
	   }
	#>
	<#= CodeRegion.GetIndent(1) #>
	}
}

<#
    // 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
		WriteLine("{0}[DisplayName(\"{1}\")]", CodeRegion.GetIndent(1), displayName);
    }
}

//add required attribute
void WriteRequiredAttribute(EdmProperty edmProperty) {
    
    // Check for required property
    if (!edmProperty.Nullable) 
    {
      WriteLine("{0}[Required(ErrorMessage = \"{1} is required\")]",
         CodeRegion.GetIndent(2),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(2),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路徑位置

image

 

存檔後所有Model的MetaData類別都被建立出來了

image

 

產出的資料就如預期般地呈現

using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;


namespace T4EdmxMetadataModelWeb.Models
{

    /// <summary>
    /// User class
    /// </summary>
    [MetadataType(typeof(UserMetadata))]
    public partial class User
    {

        /// <summary>
        /// User Metadata class
        /// </summary>
        public class UserMetadata
        {

            /// <summary>
            /// User Id
            /// </summary>        
            [DisplayName("User Id")]
            [Required(ErrorMessage = "User Id is required")]
            [MaxLength(50, ErrorMessage = "User Id cannot be longer than 50 characters")]
            public string UserId { get; set; }


            /// <summary>
            /// Email
            /// </summary>        
            [DisplayName("Email")]
            [Required(ErrorMessage = "Email is required")]
            [MaxLength(250, ErrorMessage = "Email cannot be longer than 250 characters")]
            public string Email { get; set; }


            /// <summary>
            /// Password
            /// </summary>        
            [DisplayName("Password")]
            [Required(ErrorMessage = "Password is required")]
            [MaxLength(80, ErrorMessage = "Password cannot be longer than 80 characters")]
            public string Password { get; set; }


            /// <summary>
            /// Name
            /// </summary>        
            [DisplayName("Name")]
            [Required(ErrorMessage = "Name is required")]
            [MaxLength(10, ErrorMessage = "Name cannot be longer than 10 characters")]
            public string Name { get; set; }


            /// <summary>
            /// Register On
            /// </summary>        
            [DisplayName("Register On")]
            [Required(ErrorMessage = "Register On is required")]
            public System.DateTime RegisterOn { get; set; }


            /// <summary>
            /// Is Enable
            /// </summary>        
            [DisplayName("Is Enable")]
            [Required(ErrorMessage = "Is Enable is required")]
            public bool IsEnable { get; set; }


        }
    }

}

 

 

參考資訊

 

http://www.codeproject.com/Articles/665317/Metadata-Class-or-Data-Annotation-from-EDMX-Files

http://limitedcode.blogspot.tw/2014/07/aspnet-mvc-model-metadata.html


希望此篇文章可以幫助到需要的人

若內容有誤或有其他建議請不吝留言給筆者喔 !