しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

Entity Framework のモデル用に T4 テンプレートを作る

Windows ランタイムアプリで EF6 を利用する場合、コードファーストにならざるを得ない・・・ということで EF6 で利用するための POCO エンティティを自動生成する T4 テンプレートを作ってみました

まずは T4 の本体部を Entity.ttinclude というファイル名で作成

<# if (!string.IsNullOrEmpty(this.Description)) { #>
    /// <summary>
    /// <#= this.Description #> のインタフェース
    /// </summary>
<# } #>
    public partial interface I<#= this.ClassName #>
    {
<# if (this.Properties != null) {
        foreach(var property in this.Properties) { #>
        <#= property.TypeName #> <#=property.PropertyName #> { get; set; }
<#      }
    }
#>
<# if (this.NavigationProperties != null) {
        foreach(var property in this.NavigationProperties) { #>
        <#= property.TypeName #> <#=property.PropertyName #> { get; set; }
<#      }
    }
#>    }

<# if (!string.IsNullOrEmpty(this.Description)) { #>
    /// <summary>
    /// <#= this.Description #>
    /// </summary>
<# } #>
<# if (this.Attribute != null) { #>
    <#= this.Attribute #>
<# } #>
    public partial class <#= this.ClassName #> : I<#= this.ClassName #>
    {
        #region マルチスレッド排他制御用

        /// <summary>
        /// 排他制御フラグ
        /// </summary>
        private static bool isSynchronize = false;

        /// <summary>
        /// 排他制御フラグ
        /// </summary>
        [XmlIgnore]
        public static bool IsSynchronize
        {
            get { return isSynchronize; }
            set { isSynchronize = value; }
        }

        /// <summary>
        /// 排他制御オブジェクト
        /// </summary>
        public static readonly Mutex LockObject = new Mutex();

        #endregion //マルチスレッド排他制御用

<# if (this.Properties != null) {
        foreach(var property in this.Properties) { #>
        #region <#= property.PropertyName #>:<#= property.DisplayName #> プロパティ
<# if (!string.IsNullOrEmpty(property.DisplayName)) { #>
        /// <summary>
        /// <#= property.DisplayName #>
        /// </summary>
<# } #>
        private <#= property.TypeName #> <#= property.FieldName #>; 

<# if (!string.IsNullOrEmpty(property.DisplayName)) { #>
        /// <summary>
        /// <#= property.DisplayName #> の取得および設定
        /// </summary>
<# } #>
<# if (property.Attribute != null) { foreach(var attribute in property.Attribute) {#>
        <#= attribute #>
<# } } #>
        public <#= property.TypeName #> <#=property.PropertyName #>
        {
            get { return this.<#= property.FieldName #>; }
            set 
            {
                try
                {
                    if (isSynchronize)
                    {
                        LockObject.WaitOne();
                    }

                    this.<#= property.FieldName #> = value;
                }
                finally
                {
                    if (isSynchronize)
                    {
                        LockObject.ReleaseMutex();
                    }
                }
            }
        }
        #endregion //<#= property.PropertyName #>:<#= property.DisplayName #> プロパティ

<#      }
    } #>
<# if (this.NavigationProperties != null) {
        foreach(var property in this.NavigationProperties) { #>
        #region <#= property.PropertyName #>:<#= property.DisplayName #> プロパティ
<# if (!string.IsNullOrEmpty(property.DisplayName)) { #>
        /// <summary>
        /// <#= property.DisplayName #>
        /// </summary>
<# } #>
        private <#= property.TypeName #> <#= property.FieldName #>; 

<# if (!string.IsNullOrEmpty(property.DisplayName)) { #>
        /// <summary>
        /// <#= property.DisplayName #> の取得および設定
        /// </summary>
<# } #>
<# if (property.Attribute != null) { foreach(var attribute in property.Attribute) {#>
        <#= attribute #>
<# } } #>
        public <# if (property.IsLasyLoading) { #>virtual <# } #><#= property.TypeName #> <#=property.PropertyName #>
        {
            get { return this.<#= property.FieldName #>; }
            set 
            {
                try
                {
                    if (isSynchronize)
                    {
                        LockObject.WaitOne();
                    }

                    this.<#= property.FieldName #> = value;
                }
                finally
                {
                    if (isSynchronize)
                    {
                        LockObject.ReleaseMutex();
                    }
                }
            }
        }
        #endregion //<#= property.PropertyName #>:<#= property.DisplayName #> プロパティ

<#      }
    } #>    }
<#+
    public class Property
    {
        public string FieldName { get; set; }
        public string PropertyName { get; set; }
        public string TypeName { get; set; }
        public string DisplayName { get; set; }
        public string[] Attribute { get; set; }

        public Property(string name, string type, string displayName = "", params  string[] attribute)
        {
            var textInfo =  System.Globalization.CultureInfo.CurrentCulture.TextInfo;

            this.FieldName = textInfo.ToLower(name.FirstOrDefault()) + name.Substring(1);
            this.PropertyName =  textInfo.ToUpper(name.FirstOrDefault()) + name.Substring(1);
            this.TypeName = type;
            this.DisplayName = displayName;
            this.Attribute = attribute;
        } 
    }

    public class NavigationProperty
    {
        public string FieldName { get; set; }
        public string PropertyName { get; set; }
        public string TypeName { get; set; }
        public string DisplayName { get; set; }
        public string[] Attribute { get; set; }
        public bool IsMultiple { get; set; }
        public bool IsLasyLoading { get; set; }

        public NavigationProperty(string name, string type, bool multiple, bool lasyLoading, string displayName = "", params  string[] attribute)
        {
            var textInfo =  System.Globalization.CultureInfo.CurrentCulture.TextInfo;

            this.FieldName = textInfo.ToLower(name.FirstOrDefault()) + name.Substring(1);
            this.PropertyName =  textInfo.ToUpper(name.FirstOrDefault()) + name.Substring(1);

            if (multiple)
            {
               var newArray = new string[attribute.Length + 1];
               newArray[attribute.Length] = string.Format("[XmlIncludeAttribute(typeof(List<{0}>))]", type);
               Array.Copy(attribute, newArray, attribute.Length);
               attribute = newArray;
               type = string.Format("ICollection<{0}>", type);
            }
            this.TypeName = type;
            this.DisplayName = displayName;
            this.Attribute = attribute;
            this.IsMultiple = multiple;
            this.IsLasyLoading = lasyLoading;
        }
    }

    public string ClassName { get; set; }
    public Property[] Properties { get; set; }
    public NavigationProperty[] NavigationProperties { get; set; }
    public string Description { get; set; }
    public string Attribute { get; set; }

    public void Generate(string className, Property[] properties, NavigationProperty[] navigations, string description = "", string attribute = "")
    {
        this.ClassName = className;
        this.Properties = properties;
        this.NavigationProperties = navigations;
        this.Description = description;
        this.Attribute = attribute;
    }
#>

この .ttinclude ファイルを利用してモデル生成ファイルを書きます

<#@ template debug="true" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".generated.cs" #>
//<auto-generated>
#region License
//-----------------------------------------------------------------------
// <copyright>
//     Copyright matatabi-ux 2014.
// </copyright>
//-----------------------------------------------------------------------
#endregion

namespace EverydayCats.Models
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Xml.Serialization;
    using Newtonsoft.Json;

<# 
   Generate("Animal", new Property[] {
       new Property("id", "int", "ID", "[XmlAttribute(\"id\")]", "[JsonProperty(\"id\")]"),
   }, new NavigationProperty[] {
       new NavigationProperty("cats", "Cat", true, true, "猫情報", "[XmlElement(\"cat\")]", "[JsonProperty(\"cats\")]"),
   }, "動物情報", "[XmlRoot(\"animal\")]");
#>
<#@ include file="../Templates/Entity.ttinclude" #>
}

Property と NavigationProperty を分けているのがミソで、NavigationProperty の第三引数の bool は複数繰り返し項目かどうか、第四引数の bool は遅延読み込みさせるかどうかのフラグです

//<auto-generated>
#region License
//-----------------------------------------------------------------------
// <copyright>
//     Copyright matatabi-ux 2014.
// </copyright>
//-----------------------------------------------------------------------
#endregion

namespace EverydayCats.Models
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Xml.Serialization;
    using Newtonsoft.Json;

    /// <summary>
    /// 動物情報 のインタフェース
    /// </summary>
    public partial interface IAnimal
    {
        int Id { get; set; }
        ICollection<Cat> Cats { get; set; }
    }

    /// <summary>
    /// 動物情報
    /// </summary>
    [XmlRoot("animal")]
    public partial class Animal : IAnimal
    {
        #region マルチスレッド排他制御用

        /// <summary>
        /// 排他制御フラグ
        /// </summary>
        private static bool isSynchronize = false;

        /// <summary>
        /// 排他制御フラグ
        /// </summary>
        [XmlIgnore]
        public static bool IsSynchronize
        {
            get { return isSynchronize; }
            set { isSynchronize = value; }
        }

        /// <summary>
        /// 排他制御オブジェクト
        /// </summary>
        public static readonly Mutex LockObject = new Mutex();

        #endregion //マルチスレッド排他制御用

        #region Id:ID プロパティ
        /// <summary>
        /// ID
        /// </summary>
        private int id; 

        /// <summary>
        /// ID の取得および設定
        /// </summary>
        [XmlAttribute("id")]
        [JsonProperty("id")]
        public int Id
        {
            get { return this.id; }
            set 
            {
                try
                {
                    if (isSynchronize)
                    {
                        LockObject.WaitOne();
                    }

                    this.id = value;
                }
                finally
                {
                    if (isSynchronize)
                    {
                        LockObject.ReleaseMutex();
                    }
                }
            }
        }
        #endregion //Id:ID プロパティ

        #region Cats:猫情報 プロパティ
        /// <summary>
        /// 猫情報
        /// </summary>
        private ICollection<Cat> cats; 

        /// <summary>
        /// 猫情報 の取得および設定
        /// </summary>
        [XmlElement("cat")]
        [JsonProperty("cats")]
        [XmlIncludeAttribute(typeof(List<Cat>))]
        public virtual ICollection<Cat> Cats
        {
            get { return this.cats; }
            set 
            {
                try
                {
                    if (isSynchronize)
                    {
                        LockObject.WaitOne();
                    }

                    this.cats = value;
                }
                finally
                {
                    if (isSynchronize)
                    {
                        LockObject.ReleaseMutex();
                    }
                }
            }
        }
        #endregion //Cats:猫情報 プロパティ

    }

}

生成結果はこんな感じで複数繰り返しで遅延読み込みありの NavigationProperty は virtual ICollection になります

コードファーストの方がいろいろ細かい設定はできるんだろうけど、モデルファーストも使いたかった・・・