T4文本模板生成EasyModular各层代码

先不说T4如何生成代码,有人看到这到这标题肯定会问T4模板我知道啊,但这EasyModular又是啥东东呢。关于什么是EasyModular,点一下这个链接:EasyModular,里面藏着一些小确幸。

熟悉T4文本模板

T4是什么

T4模板全称是Text Template Transformation Toolkit,在 Visual Studio 中,T4 文本模板是文本块与可生成文本文件的控制逻辑的混合体,所生成的文件可以是任何类型的文本,例如网页、资源文件或任何语言的程序源代码。简单来说T4文本模板可以生成任何你想要的模板文件。

T4语法

T4语法和C#语法有点相似,主要分为三类

  • 指令:控制如何处理模板的元素
  • 文本块:直接复制到输出的内容
  • 控制块:将变量值插入的文本中并控制文本的有条件或重复部分的程序代码
指令
  • 模板指令
     <#@ template debug="false" hostspecific="false" language="C#" #>  
    
  • debug:是否启用调试,有效值true、false,默认为false。
  • hostspecific:有效值true、false,默认为false。如果将此特性的值设置为 true,则会将名为 Host 的属性添加到由文本模板生成的类中。 该属性是对转换引擎的宿主的引用,并声明为Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost。
  • language:输出语言,有效值C#、VB,默认为C#

还要很多其他的指令,这里就不详细铺开了,有兴趣的可以到官网查阅。

控制块

<# 标准控制块 #> 可以包含语句。
<#= 表达式控制块 #> 可以包含表达式。
<#+ 类特征控制块 #> 可以包含方法、字段和属性,就像一个类的内部 。

生成EasyModular各层代码

上面我们简单熟悉了一下T4文本模板,接下来我们看一下如何用T4文本模板生成生成EasyModular各层代码。大概的思路如下:

  1. 先熟悉EasyModular各层代码的结构,确定哪些需要动态生成代码块。
  2. 创建一个访问数据库的辅助模板类,因为我们需要访问数据库的表和字段的信息
  3. 创建一个文件输出的辅助模板类,因为我们需要输出多个文件
  4. 创建EasyModular每一层的T4文本模板

整个T4模板的代码架构如下:
20210411112430

数据库的辅助模板类


<#@ include file="$(ProjectDir)/Model/TableModel.ttinclude"#> 
<#@ include file="$(ProjectDir)/Model/ColumnModel.ttinclude"#> 
<#+
    /// <summary>
    /// MySql数据库访问辅助类
    /// </summary>
    public class MySqlDbHelper
    {
        private string _dbName = "";
        public  string _ConnStr = "";

        public MySqlDbHelper(string projectDir)
        {
           var url = Path.Combine(projectDir, "config.json");
           using var streamReader = new StreamReader(url);
           var config = JsonConvert.DeserializeObject<ConfigModel>(streamReader.ReadToEnd());

           _ConnStr= config.ConnectionString;
           _dbName = config.DbName;
        }

        /// <summary>
        /// 获取表信息
        /// </summary>
        /// <param name="prefix">前缀</param>
        /// <param name="tbName">表名</param>
        /// <returns></returns>
        public List<TableModel> GetDbTables(string prefix = "", string tbName = "")
        {
           
            var strSql = $"SELECT table_name,table_comment FROM information_schema.TABLES WHERE table_schema = '{_dbName}'";

            if (!string.IsNullOrEmpty(prefix))
                strSql += $" and table_name like '{prefix}%'";

            if (!string.IsNullOrEmpty(tbName))
                strSql += $" and table_name = '{tbName}'";

            return SqlQuery<TableModel>(strSql);
        }

        /// <summary>
        /// 获取列信息
        /// </summary>
        /// <param name="tbName">表名</param>
        /// <returns></returns>
        public List<ColumnModel> GetDbColumns(string tbName)
        {
            var strSql = $"SELECT column_name, column_type, column_comment, data_type,is_nullable FROM information_schema.COLUMNS WHERE table_name = '{tbName}' AND table_schema = '{_dbName}' ORDER BY ordinal_position ASC";

            return SqlQuery<ColumnModel>(strSql);
        }

        /// <summary>
        /// 数据库类型转换为C#类型
        /// </summary>
        /// <param name="dbtype"></param>
        /// <param name="is_nullable"> 是否可空(YES或NO)</param>
        /// <returns></returns>
        public string ConvertToCsharpType(string dbtype, string is_nullable)
        {
            if (string.IsNullOrEmpty(dbtype))
                return dbtype;

            dbtype = dbtype.ToLower();
            var csharpType = "object";

            switch (dbtype)
            {
                case "bigint": csharpType = "long"; break;
                case "binary": csharpType = "byte[]"; break;
                case "bit": csharpType = "bool"; break;
                case "char": csharpType = "string"; break;
                case "date": csharpType = "DateTime"; break;
                case "datetime": csharpType = "DateTime"; break;
                case "datetime2": csharpType = "DateTime"; break;
                case "datetimeoffset": csharpType = "DateTimeOffset"; break;
                case "decimal": csharpType = "decimal"; break;
                case "float": csharpType = "double"; break;
                case "image": csharpType = "byte[]"; break;
                case "int": csharpType = "int"; break;
                case "money": csharpType = "decimal"; break;
                case "nchar": csharpType = "string"; break;
                case "ntext": csharpType = "string"; break;
                case "numeric": csharpType = "decimal"; break;
                case "nvarchar": csharpType = "string"; break;
                case "real": csharpType = "Single"; break;
                case "smalldatetime": csharpType = "DateTime"; break;
                case "smallint": csharpType = "short"; break;
                case "smallmoney": csharpType = "decimal"; break;
                case "sql_variant": csharpType = "object"; break;
                case "sysname": csharpType = "object"; break;
                case "text": csharpType = "string"; break;
                case "time": csharpType = "TimeSpan"; break;
                case "timestamp": csharpType = "byte[]"; break;
                case "tinyint": csharpType = "byte"; break;
                case "uniqueidentifier": csharpType = "Guid"; break;
                case "varbinary": csharpType = "byte[]"; break;
                case "varchar": csharpType = "string"; break;
                case "xml": csharpType = "string"; break;
                default: csharpType = "object"; break;
            }

            if(is_nullable=="YES"&&csharpType!="string"&&csharpType!="object")
            {
                csharpType=csharpType+"?";
            }

            return csharpType;
        }
        
        /// <summary>
        /// 查询
        /// </summary>
        /// <param name="sql"></param>
        /// <returns></returns>

        public List<T> SqlQuery<T>(string sql, params MySqlParameter[] paras) where T : new()
        {
            using (MySqlConnection con = new MySqlConnection(_ConnStr))
            {
                MySqlDataAdapter sqlda = new MySqlDataAdapter(sql, con);
                sqlda.SelectCommand.Parameters.AddRange(paras);
                DataTable dt = new DataTable();
                sqlda.Fill(dt);
                return DataTableToList<T>(dt);
            }
        }

        /// <summary>
        /// 利用反射将Datatable转换为List<T>对象
        /// </summary>
        /// <typeparam name="T">集合</typeparam>
        /// <param name="dt"> datatable对象</param>
        /// <returns></returns>
        public List<T> DataTableToList<T>(DataTable dt) where T : new()
        {
            //定义集合
            List<T> ts = new List<T>();

            //遍历dataTable中的数据行
            foreach (DataRow dr in dt.Rows)
            {
                T t = new T();
                //获得此模型的公共属性
                PropertyInfo[] propertys = t.GetType().GetProperties();

                //遍历该对象的所有属性
                foreach (PropertyInfo pi in propertys)
                {

                    string tempName = pi.Name;

                    if (!dt.Columns.Contains(tempName)) continue;   //检查datatable是否包含此列(列名==对象的属性名)    
                    object value = dr[tempName];      //取值
                    if (value == DBNull.Value) continue;  //如果非空,则赋给对象的属性
                    pi.SetValue(t, value, null);
                }
                //对象添加到泛型集合中
                ts.Add(t);
            }
            return ts;

        }
    }
#>

文件输出的辅助模板类

<#@ assembly name="System.Core"#>  
<#@ assembly name="EnvDTE"#>  
<#@ import namespace="System.Collections.Generic"#>  
<#@ import namespace="System.IO"#>  
<#@ import namespace="System.Text"#>  
<#@ import namespace="Microsoft.VisualStudio.TextTemplating"#>  
<#+  
class Manager  
{  
    public struct Block {  
        public int Start, Length;  
        public String Name,OutputPath;  
    }  
  
    public List<Block> blocks = new List<Block>();  
    public Block currentBlock;  
    public Block footerBlock = new Block();  
    public Block headerBlock = new Block();  
    public ITextTemplatingEngineHost host;  
    public ManagementStrategy strategy;  
    public StringBuilder template;  
    public Manager(ITextTemplatingEngineHost host, StringBuilder template, bool commonHeader) {  
        this.host = host;  
        this.template = template;  
        strategy = ManagementStrategy.Create(host);  
    }  
    public void StartBlock(String name,String outputPath) {  
        currentBlock = new Block { Name = name, Start = template.Length ,OutputPath=outputPath};  
    }  
  
    public void StartFooter() {  
        footerBlock.Start = template.Length;  
    }  
  
    public void EndFooter() {  
        footerBlock.Length = template.Length - footerBlock.Start;  
    }  
  
    public void StartHeader() {  
        headerBlock.Start = template.Length;  
    }  
  
    public void EndHeader() {  
        headerBlock.Length = template.Length - headerBlock.Start;  
    }      
  
    public void EndBlock() {  
        currentBlock.Length = template.Length - currentBlock.Start;  
        blocks.Add(currentBlock);  
    }  
    public void Process(bool split) {  
        String header = template.ToString(headerBlock.Start, headerBlock.Length);  
        String footer = template.ToString(footerBlock.Start, footerBlock.Length);  
        blocks.Reverse();  
        foreach(Block block in blocks) {  
            String fileName = Path.Combine(block.OutputPath, block.Name);  
            if (split) {  
                String content = header + template.ToString(block.Start, block.Length) + footer;  
                strategy.CreateFile(fileName, content);  
                template.Remove(block.Start, block.Length);  
            } else {  
                strategy.DeleteFile(fileName);  
            }  
        }  
    }  
}  
class ManagementStrategy  
{  
    internal static ManagementStrategy Create(ITextTemplatingEngineHost host) {  
        return (host is IServiceProvider) ? new VSManagementStrategy(host) : new ManagementStrategy(host);  
    }  
  
    internal ManagementStrategy(ITextTemplatingEngineHost host) { }  
  
    internal virtual void CreateFile(String fileName, String content) {  
        File.WriteAllText(fileName, content);  
    }  
  
    internal virtual void DeleteFile(String fileName) {  
        if (File.Exists(fileName))  
            File.Delete(fileName);  
    }  
}  
  
class VSManagementStrategy : ManagementStrategy  
{  
    private EnvDTE.ProjectItem templateProjectItem;  
  
    internal VSManagementStrategy(ITextTemplatingEngineHost host) : base(host) {  
        IServiceProvider hostServiceProvider = (IServiceProvider)host;  
        if (hostServiceProvider == null)  
            throw new ArgumentNullException("Could not obtain hostServiceProvider");  
  
        EnvDTE.DTE dte = (EnvDTE.DTE)hostServiceProvider.GetService(typeof(EnvDTE.DTE));  
        if (dte == null)  
            throw new ArgumentNullException("Could not obtain DTE from host");  
  
        templateProjectItem = dte.Solution.FindProjectItem(host.TemplateFile);  
    }  
    internal override void CreateFile(String fileName, String content) {  
        base.CreateFile(fileName, content);  
        //((EventHandler)delegate { templateProjectItem.ProjectItems.AddFromFile(fileName); }).BeginInvoke(null, null, null, null);  
    }  
    internal override void DeleteFile(String fileName) {  
        ((EventHandler)delegate { FindAndDeleteFile(fileName); }).BeginInvoke(null, null, null, null);  
    }  
    private void FindAndDeleteFile(String fileName) {  
        foreach(EnvDTE.ProjectItem projectItem in templateProjectItem.ProjectItems) {  
            if (projectItem.get_FileNames(0) == fileName) {  
                projectItem.Delete();  
                return;  
            }  
        }  
    }  
}#>

EasyModular.Domain模板类

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Data"#>

<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.IO" #> 
<#@ import namespace="Newtonsoft.Json" #> 
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Reflection" #> 
<#@ import namespace="MySql.Data.MySqlClient" #>

<#@ assembly name="$(ProjectDir)\_Library\MySql.Data.dll" #>
<#@ assembly name="$(ProjectDir)\_Library\Newtonsoft.Json.dll" #>

<#@ include file="$(ProjectDir)/Model/ConfigModel.ttinclude"#> 
<#@ include file="$(ProjectDir)/Helper/OutputHelper.ttinclude"#> 
<#@ include file="$(ProjectDir)/Helper/MySqlDbHelper.ttinclude"#> 

<# var manager = new Manager(Host, GenerationEnvironment, true); #>  

<# 
    var solutionDir = Host.ResolveAssemblyReference("$(SolutionDir)"); //当解决方案目录
    var projectDir = Host.ResolveAssemblyReference("$(ProjectDir)"); //当前项目目录

    var configUrl = Path.Combine(projectDir, "config.json");
    using var streamReader = new StreamReader(configUrl);
    var config = JsonConvert.DeserializeObject<ConfigModel>(streamReader.ReadToEnd());

    var projectName=$"{config.Module}.Domain";
    var projectPath=Path.Combine(solutionDir,projectName);
    if (!Directory.Exists(projectPath))  
    {  
        Directory.CreateDirectory(projectPath);  
    } 
  
    var _db =new MySqlDbHelper(projectDir);

    var tables=_db.GetDbTables(config.Prefix,config.TableName);

    foreach(var table in tables)
    {
       var tableName = table.table_name.Substring(config.Prefix.Length - 1).Replace("_", "");;

       var domainDir=Path.Combine(projectPath,tableName); 
       if(!Directory.Exists(domainDir))
       {
          Directory.CreateDirectory(domainDir); 
       }

//===========================1.生成实体===========================================
       manager.StartBlock(tableName+"Entity.cs",domainDir);
       #>

using EasyModular.SqlSugar;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Text;

namespace <#=projectName#>
{
    /// <summary>
    /// <#=table.table_comment.Replace("表","")#>
    /// </summary>
    [SugarTable("<#=table.table_name#>")]
    public partial class <#=tableName#>Entity : EntityBaseForEnterprise
    {
     <#
     var columns=_db.GetDbColumns(table.table_name);
     foreach(var col in columns)
     {
      if (config.ExcludeCols.Contains(col.column_name))
                continue;
        var colType=_db.ConvertToCsharpType(col.data_type,col.is_nullable);
    #>
    /// <summary>
        /// <#=col.column_comment#>
        /// </summary>
        public <#=colType#> <#=col.column_name#> { get; set; }

    <#
    }
    #>
}
}
     <#
      manager.EndBlock();  

//===========================2.生成模型===========================================
      var modelDir=Path.Combine(domainDir,"Models"); 
      if(!Directory.Exists(modelDir))
       {
          Directory.CreateDirectory(modelDir); 
       }
       manager.StartBlock(tableName+"QueryModel.cs",modelDir);
     #>

using EasyModular.Utils;
using System;
using System.Collections.Generic;
using System.Text;

namespace <#=projectName#>
{
    /// <summary>
    /// <#=table.table_comment#>查询模型
    /// </summary>
    public class <#=tableName#>QueryModel : QueryPagingModel
    {
           <#
            foreach(var col in columns)
            {
              if (config.ExcludeCols.Contains(col.column_name))
                continue;
               var colType=_db.ConvertToCsharpType(col.data_type,col.is_nullable);
           #>
            /// <summary>
            /// <#=col.column_comment#>
            /// </summary>
            public <#=colType#> <#=col.column_name#> { get; set; }
           <#}
           #>
}
}
    <#
      manager.EndBlock();  

//===========================3.生成仓储接口===========================================
     manager.StartBlock("I"+tableName+"Repository.cs",domainDir);
     #> 
using EasyModular.SqlSugar;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

namespace <#=projectName#>
{
    public interface I<#=tableName#>Repository : IRepository<<#=tableName#>Entity>
    {
        Task<IList<<#=tableName#>Entity>> Query(<#=tableName#>QueryModel model, Guid enterpriseId);
    }
}
 <#
  manager.EndBlock();  
   }
     manager.Process(true);  
#>  

配置文件

{
  "Module": "EDCP.Admin", //模块
  "DbName": "EDCP", //数据库名称
  "Prefix": "Sys_", //表前缀
  "TableName": "", //表名
  "ConnectionString": "server=XX;user id=XX; password=XXX; database=XXX;", //数据库连接串
  "ExcludeCols": "EnterpriseId,Creater,CreaterName,CreatedTime,Modifier,ModifierName,ModifiedTime,IsDel,Deleter,DeleterName,DeletedTime" //排除的字段
}

后续扩展

  1. 目前仅支持MySql的模板生成,后面需要兼容其他主流的数据库。
  2. 支持可视化的代码生成。
posted @ 2021-04-11 11:39  陈曦-LR  阅读(234)  评论(0)    收藏  举报