T4模板生成Code First MetaData伙伴类

时间:2014-05-18 10:53:09

标签: ef-code-first t4 entity-framework-6

设定:

我从头开始构建了很多MVC应用程序,但是它使用了现有的数据库。

使用实体框架 - >逆向工程代码第一个上下文菜单项,我从数据库中获取Code First类,DbContext类和映射类。

我的要求:

但是,我还想生成MetaData类,以便我可以添加自定义的DisplayName属性等。

MetaData类将位于不同的目录(MetaData)中,因此它们不会使Models目录混乱。

问题:

有人知道这样做的T4模板吗?如果我是第一个有此要求的人,那就太奇怪了......

我的能力:

我是T4的新手,但是任何从给定目录获取文件的模板,在循环中读取每个文件,修改它(理想情况下,向属性添加属性!),然后写入新文件一个不同的目录就可以了,从那里开始,我可以弄清楚如何为我的特定目的做这件事。

注意:

我不希望与Reverse Engineered Code First文件同时生成文件,因为我不想覆盖我的MetaData类。为了避免在运行模板时执行此操作,我将修改/编写模板,以便如果文件已存在于MetaData目录中,模板将跳过该实体,并且不会创建新的MetaData文件来覆盖现有的。

我见过Model First和Database First的东西,但不是代码优先。我想我可以先调整其中一个代码,只需用先前生成的文件中的读取替换EDMX位,获取属性并向其添加DisplayName属性。

希望这有道理吗?

编辑(删除):

我删除了第一个编辑,因为我取得了进展。见下面的编辑2.

编辑2:

我已经解决了所有问题,也删除了编辑2。请参阅下面的答案。

1 个答案:

答案 0 :(得分:2)

我已经能够使用血液,汗水,眼泪和有形T4的TemplateFileManagerV2.1.ttinclude以及他们的VisualStudioAutomationHelper.ttinclude来解决我的问题,尽管有以下帖子中有形T4支持建议的修改:

Tangible T4 support advice for editing their Visual Studio Automation Helper to allow creating files that are not wrapped in a .txt4 file

由于我没有有形T4专业版,所以有点痛苦。嘿嘿,我不是在寻找口中的礼物马。

唯一突出的问题是我无法检测源文件中的属性是否为虚拟属性,因此我也在我的伙伴元数据类中获取了导航属性,这是我不想要的。我将活着去争取那一天。

另外,我可以创建文件,但它们不包含在项目中。包含它们的代码很简单,但我无法在同一个文件中使用它,因此必须将其拆分为单独的文件,如下所示:

T4_1_GenerateCodeFirstBuddies.tt T4_2_GenerateCodeFirstBuddies.tt

这种分离有一个附带好处,因为T4_1_GenerateCodeFirstBuddies.tt使用两个有形T4助手.ttincludes,其中一个留下了残留错误。运行我的第二个文件会删除解决方案资源管理器中的错误和红色波浪线,我发现这真的让人分心。

因此,我的文件代码如下:

T4_1_GenerateCodeFirstBuddies.tt

<#@ template debug="true" hostSpecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ Assembly Name="System.Core" #>
<#@ import namespace="System" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Diagnostics" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #> 
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="EnvDTE" #>
<#@ include file="VisualStudioAutomationHelper.ttinclude" #>
<#@ include file="TemplateFileManagerV2.1.ttinclude" #><#
    var modelFileDirectory = this.Host.ResolvePath("Models");
    var metaDataFilesDirectory = this.Host.ResolvePath("MetaData"); 
    var nspace = "";
    var manager = TemplateFileManager.Create(this);
    foreach(var file in System.IO.Directory.GetFiles(modelFileDirectory, "*.cs"))
    {
        var projectItem = this.VisualStudioHelper.FindProjectItem(file);
        foreach(EnvDTE.CodeClass classInFile in this.VisualStudioHelper.CodeModel.GetAllCodeElementsOfType(projectItem.FileCodeModel.CodeElements, EnvDTE.vsCMElement.vsCMElementClass, false))
        {
            var name = classInFile.Name;
            if(nspace == "") nspace = classInFile.Namespace.Name;
            // Danger: Beware if a table name includes the string "Context" or "AspNet"!!
            // These files are removed because they are either the DbContext, or the sysdiagram file, or else the AspNet.Identity tables
            if(name != "sysdiagram" && name.IndexOf("Context") == -1 && name.IndexOf("AspNet") == -1)
            {                               
                if(!FileExists(metaDataFilesDirectory, classInFile.Name + "MetaData.cs")) 
                {
                    manager.StartNewFile(name +"MetaData.cs", "", "MetaData"); #>
using System;
using System.Collections.Generic;
using System.ComponentModel;
//using System.ComponentModel.DataAnnotations;
//using Wingspan.Web.Mvc.Extensions;
using Wingspan.Web.Mvc.Crud;

namespace <#= nspace #>
{
    public class <#= name + "MetaData" #>
    {
        <# foreach (CodeElement mem in classInFile.Members)
        { 
            if (mem.Kind == vsCMElement.vsCMElementProperty) // && "[condition to show that mem is not marked as virtual]") 
            {
                PushIndent("        ");
                WriteLineDisplayName(mem); 
                WriteLineProperty(mem);
                WriteLine("");
                PopIndent();
            }
        } #>    
    }

    public partial class <#= name #> : IInjectItemSL
    {
        public ItemSL ItemSL
        {
            get
            {
                return new ItemSL
                {
                    ItemId = <#= name #>Id, ItemText = Name
                };
            } 
        }   
    }
}<#
                } 
            }
        }       
    }
    manager.Process();  
#>
<#+
// Check for file existence
bool FileExists(string directory, string filename)
{            
    return File.Exists(Path.Combine(directory, filename));    
}

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

string GetRootDirectory()
{
    return this.Host.ResolvePath("");
}

// Get content of file name
string xOutputFile(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();
}

void WriteLineProperty(CodeElement ce)
{
    var access = ((CodeProperty) ce).Access == vsCMAccess.vsCMAccessPublic ? "public" : "";
    WriteLine(access + " " + (((CodeProperty) ce).Type).AsFullName + " " + ce.Name + " { get; set; }");
}

void WriteLineDisplayName(CodeElement ce) 
{
    var name = ce.Name;
    if (!string.IsNullOrEmpty(name)) 
    {
        name = GetFriendlyName(name);
        WriteLine(string.Format("[DisplayName(\"{0}\")]", name));
    }
}
#>

T4_2_GenerateCodeFirstBuddies.tt:

<#@ template debug="true" hostSpecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ Assembly Name="System.Core" #>
<#@ import namespace="System" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Diagnostics" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #> 
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="EnvDTE" #>
<#@ include file="VisualStudioAutomationHelper.ttinclude" #>
<#@ include file="TemplateFileManagerV2.1.ttinclude" #><#

    var metaDataFilesDirectory = this.Host.ResolvePath("MetaData"); 

    var metaDataFiles = System.IO.Directory.GetFiles(metaDataFilesDirectory, "*.cs");
    var project = VisualStudioHelper.CurrentProject;
    var projectItems = project.ProjectItems;
    foreach( var f in metaDataFiles)
    {
        projectItems.AddFromFile(f);
    }

#>

生成的输出文件对我来说足够好了,看起来像是:

using System;
using System.Collections.Generic;
using System.ComponentModel;
//using System.ComponentModel.DataAnnotations;
//using Wingspan.Web.Mvc.Extensions;
using Wingspan.Web.Mvc.Crud;

namespace BuddyClassGenerator.Models
{
    public class ChemicalMetaData
    {
        [DisplayName("Chemical Id")]
        public System.Guid ChemicalId { get; set; }

        [DisplayName("Active Ingredient")]
        public System.String ActiveIngredient { get; set; }

        [DisplayName("Type")]
        public System.String Type { get; set; }

        [DisplayName("LERAP")]
        public System.String LERAP { get; set; }

        [DisplayName("Hazard Classification")]
        public System.String HazardClassification { get; set; }

        [DisplayName("MAPP")]
        public System.Int32 MAPP { get; set; }

        [DisplayName("Hygiene Practice")]
        public System.String HygienePractice { get; set; }

        [DisplayName("Medical Advice")]
        public System.String MedicalAdvice { get; set; }

        [DisplayName("Label")]
        public  System.String Label { get; set; }

        [DisplayName("PPE")]
        public System.String PPE { get; set; }

        [DisplayName("Warnings")]
        public System.String Warnings { get; set; }

        [DisplayName("Products")]
        public System.Collections.Generic.ICollection<BuddyClassGenerator.Models.Product> Products { get; set; }

    }

    public partial class Chemical : IInjectItemSL
    {
        public ItemSL ItemSL
        {
            get
            {
                return new ItemSL
                {
                    ItemId = ChemicalId, ItemText = Name
                };
            } 
        }   
    }

毫无疑问,请注意我已将两个类放在同一个文件中。可能不是最佳实践,但它可以节省我在文件夹中的时间和视觉混乱,所以这是我的特权。

待办事项列表:1,不包括好友类中的导航属性; 2,从属性类型中删除命名空间名称。

我希望这对某人有所帮助,但请记住,为了让它发挥作用,你需要上面详述的有形T4 ttincludes。