【问题标题】:T4 template to generate Code First MetaData buddy classes用于生成 Code First MetaData 伙伴类的 T4 模板
【发布时间】:2014-05-18 10:53:09
【问题描述】:

设置:

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

使用 Entity Framework -> Reverse Engineer Code First 上下文菜单项,我从数据库中获取 Code First 类、DbContext 类和映射类。

我的要求:

不过,我还想生成 MetaData 类,这样我就可以添加我自定义的 DisplayName 属性等。

元数据类将位于不同的目录 (MetaData) 中,这样它们就不会弄乱模型目录。

问题:

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

我的能力:

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

注意:

我不希望这些文件与逆向工程代码优先文件同时生成,因为我不想覆盖我的 MetaData 类。为了避免在运行模板时这样做,我会修改/编写模板,以便如果文件已经存在于 MetaData 目录中,则模板会跳过该实体并且不会创建新的元数据文件来覆盖现有的.

我看到了模型优先和数据库优先的东西,但没有看到代码优先。我想我可以先将其中一个修改为代码,只需将 EDMX 位替换为读取先前生成的文件,获取属性并将 DisplayName 属性添加到它们。

希望这有意义吗?

编辑(已删除):

我删除了第一个编辑,因为我已经取得了进展。请参阅下面的编辑 2。

编辑 2:

还删除了 EDIT 2,因为我已经解决了所有问题。请参阅下面的答案。

【问题讨论】:

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


    【解决方案1】:

    我已经能够使用血液、汗水、眼泪和 Tangible T4 的 TemplateFileManagerV2.1.ttinclude 及其 VisualStudioAutomationHelper.ttinclude 解决我的问题,尽管在以下帖子中通过 Tangible 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 帮助程序 .ttinclude,其中一个会留下残留错误。运行我的第二个文件会删除解决方案资源管理器中的错误和红色波浪线,我觉得这真的很让人分心。

    所以,我的文件的代码如下:

    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。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-18
      • 2011-07-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多