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