【框架總結】利用T4模板批量生成代碼

很早就看過T4模板的介紹,是可以自定義規則來生成文件的,不過當時沒時間研究就跳過了,繼續使用動軟生成代碼。

現在終於抽時間學下T4模板,多虧大神們的無私分享,使我很快就用上T4模板了,灰常灰常感謝大大們~~

在這裏我也總結下使用情況,有說的不好的歡迎指出~~

 

T4文件後綴主要有:tt和ttinclude,tt文件是模板文件,每次保存VS都會提示是否執行代碼;ttinclude文件是tt的輔助文件,保存不會提示執行代碼。

需要生成代碼,一般都是映射數據庫了,當然也可以用來生成其他比較統一格式的文件,全看您的模板怎麼寫,這裏我是用來生成映射數據庫表的實體類。

(如果想讓T4模板代碼高亮,有提示,可以下載安裝T4模板插件

這是我的T4模板文件結構 

 

1.DBSchema.ttinclude

首先,我是找到一個DBSchema.ttinclude文件,此文件是用來訪問數據庫,從數據庫讀出表的信息,代碼如下,我根據我自己的情況改動了一些

(由於找資料時沒記住出處,忘記是copy哪個大神的了,大神請見諒^_^|||)

<#@ assembly name="System.Core" #>
<#@ assembly name="System.Data" #>
<#@ assembly name="System.xml" #>
<#@ assembly name="$(ProjectDir)\T4Manager\MySql.Data.dll" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ import namespace="MySql.Data.MySqlClient" #>
<#@ import namespace="System.Linq"#>

<#+
    public class DBSchemaFactory
    {
        public static IDBSchema GetDBSchema(string dbType)
        {
            IDBSchema dbSchema;
            string connectionString = String.Empty;
            switch (dbType)
            {
                case "SqlServer":
                    connectionString = "Data Source=.;Initial Catelog=dbName;Persist Security Info=True;User ID=sa;Password=sa;";
                    dbSchema = new SqlServerSchema(connectionString);
                    break;
                case "MySql":
                    connectionString = "Server=localhost;Port=3306;Database=dbName;Uid=root;Pwd=pwd;charset=utf8;";
                    dbSchema = new MySqlSchema(connectionString);
                    break;
                default:
                    throw new ArgumentException("The input argument of DatabaseType is invalid!");
            }
            return dbSchema;
        }

        public interface IDBSchema
        {
            List<Table> GetTables(string dbName);
        }

        public class SqlServerSchema : IDBSchema
        {
            public SqlConnection conn;

            public SqlServerSchema(string connString)
            {
                conn = new SqlConnection(connString);            
            }

            public List<Table> GetTables(string dbName)
            {
                List<Table> list = new List<Table>();
                try 
                {            
                    conn.Open();
                    var cmd = string.Format(@"SELECT tab.name AS TABLE_NAME, col.name AS COLUMN_NAME, 
                                                col.is_identity, per.value AS COLUMN_COMMENT, t.name AS DATA_TYPE 
                                                FROM {0}.sys.columns col INNER JOIN {0}.sys.tables tab 
                                                ON col.object_id = tab.object_id LEFT JOIN {0}.sys.extended_properties per 
                                                ON col.column_id = per.minor_id AND per.major_id = tab.object_id
                                                INNER JOIN {0}.SYS.types t ON col.user_type_id = t.user_type_id", dbName);
                    
                    SqlCommand command = new SqlCommand(cmd, conn);
                    using(SqlDataReader reader = command.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            string db = dbName,
                                table = reader["TABLE_NAME"].ToString(),
                                column = reader["COLUMN_NAME"].ToString(),
                                type = reader["DATA_TYPE"].ToString(),
                                comment = reader["COLUMN_COMMENT"].ToString(),
                                pk = reader["is_identity"].ToString();
                            Table entity = list.FirstOrDefault(x => x.TableName == table);
                            if (entity == null)
                            {
                                entity = new Table(table);
                                entity.Columns.Add(new Column
                                {
                                    Name = column,
                                    Type = GetCLRType(type),
                                    Comment = comment,
                                    IsPK = pk == "1" ? true : false
                                });
                                list.Add(entity);
                            }
                            else
                            {
                                entity.Columns.Add(new Column
                                {
                                    Name = column,
                                    Type = GetCLRType(type),
                                    Comment = comment,
                                    IsPK = pk == "1" ? true : false
                                });
                            }
                        }
                    }
                }
                finally
                {
                    conn.Close();
                }

                return list;
            }
        }

        public class MySqlSchema : IDBSchema
        {
            public MySqlConnection conn;

            public MySqlSchema(string connString)
            {
                conn = new MySqlConnection(connString);
            }

            public List<Table> GetTables(string dbName)
            {
                List<Table> list = new List<Table>();
                try 
                {            
                    conn.Open();
                    var cmd = string.Format(@"SELECT `information_schema`.`COLUMNS`.`TABLE_SCHEMA`
                                                    ,`information_schema`.`COLUMNS`.`TABLE_NAME`
                                                    ,`information_schema`.`COLUMNS`.`COLUMN_NAME`
                                                    ,`information_schema`.`COLUMNS`.`DATA_TYPE`
                                                    ,`information_schema`.`COLUMNS`.`COLUMN_COMMENT`
                                                    ,`information_schema`.`COLUMNS`.`COLUMN_KEY`
                                                FROM `information_schema`.`COLUMNS`
                                                WHERE `information_schema`.`COLUMNS`.`TABLE_SCHEMA` = '{0}'", dbName);
                    
                    MySqlCommand command = new MySqlCommand(cmd, conn);
                    using(MySqlDataReader reader = command.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            string db = reader["TABLE_SCHEMA"].ToString(),
                                table = reader["TABLE_NAME"].ToString(),
                                column = reader["COLUMN_NAME"].ToString(),
                                type = reader["DATA_TYPE"].ToString(),
                                comment = reader["COLUMN_COMMENT"].ToString(),
                                pk = reader["COLUMN_KEY"].ToString();
                            Table entity = list.FirstOrDefault(x => x.TableName == table);
                            if (entity == null)
                            {
                                entity = new Table(table);
                                entity.Columns.Add(new Column
                                {
                                    Name = column,
                                    Type = GetCLRType(type),
                                    Comment = comment,
                                    IsPK = pk == "PRI" ? true : false
                                });
                                list.Add(entity);
                            }
                            else
                            {
                                entity.Columns.Add(new Column
                                {
                                    Name = column,
                                    Type = GetCLRType(type),
                                    Comment = comment,
                                    IsPK = pk == "PRI" ? true : false
                                });
                            }
                        }
                    }
                }
                finally
                {
                    conn.Close();
                }

                return list;
            }
        }
        
        public static string GetCLRType(string dbType)
        {
            switch(dbType)
            {
                case "tinyint":
                case "smallint":
                case "mediumint":
                case "int":
                case "integer":
                    return "int?";
                case "double":
                    return "double?";
                case "float":
                    return "float?";
                case "decimal":
                case "numeric":
                case "real":
                    return "decimal?";
                case "bit":
                    return "bool?";
                case "date":
                case "time":
                case "year":
                case "datetime":
                case "timestamp":
                    return "DateTime?";
                case "tinyblob":
                case "blob":
                case "mediumblob":
                case "longblog":
                case "binary":
                case "varbinary":
                    return "byte[]";
                case "char":
                case "varchar":                    
                case "tinytext":
                case "text":
                case "mediumtext":
                case "longtext":
                    return "string";
                case "point":
                case "linestring":
                case "polygon":
                case "geometry":
                case "multipoint":
                case "multilinestring":
                case "multipolygon":
                case "geometrycollection":
                case "enum":
                case "set":
                default:
                    return dbType;
            }
        }
    }

    public class Table
    {
        public Table()
        {
            this.Columns = new List<Column>();
        }

        public Table(string name)
            : this()
        {
            this.TableName = name;
        }

        public string TableName { get; set; }
        public List<Column> Columns { get; set; }
    }

    public class Column
    {
        //字段名
        public string Name { get; set; }
        //類型
        public string Type { get; set; }
        //備註
        public string Comment { get; set; }
        //是否主鍵
        public bool IsPK { get; set; }
    }
#>
DBSchema.ttinclude

原先copy的只有針對SQL Server的,不過我需要MySql,所以上網找了個連接MySql的加上了。

-- <#@ assembly name="$(ProjectDir)\T4Manager\MySql.Data.dll" #>

   這裏的$(ProjectDir)當前項目所在目錄路徑,還可以用其他:

  • $(SolutionDir):當前項目所在解決方案目錄
  • $(ProjectDir):當前項目所在目錄
  • $(TargetPath):當前項目編譯輸出文件絕對路徑
  • $(TargetDir):當前項目編譯輸出目錄

-- public bool IsPK { get; set; }  //這個是我用來記錄該列是否主鍵,我生成實體類時需要用到,不需要可以去掉

   SQL Server是通過sys.columns.is_identity獲取,MySql是通過information_schema.COLUMNS.COLUMN_KEY獲取。

OK,其他沒怎麼改動過了。

 

2.MultDocument.ttinclude

數據庫連接有了,不過我根據大神的分享,生成實體類,發現每次只能生成一個表實體類,這完全不合理嘛,於是又上網找了下批量生成的資料。。。

很快就找到MultDocument.ttinclude文件了,據介紹,是外國一個大神分享的,這個文件沒什麼改的,我就直接用了一 一+

<#@ 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" #>

<#+ 
// T4 Template Block manager for handling multiple file outputs more easily.
// Copyright (c) Microsoft Corporation.  All rights reserved.
// This source code is made available under the terms of the Microsoft Public License (MS-PL)

// Manager class records the various blocks so it can split them u
class Manager
{
    public struct Block
    {
        public String Name;
        public int Start, Length;
    }

    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 String OutputPath { get; set; }

    public Manager(ITextTemplatingEngineHost host, StringBuilder template, bool commonHeader)
    {
        this.host = host;
        this.template = template;
        OutputPath = String.Empty;
        strategy = ManagementStrategy.Create(host);
    }

    public void StartBlock(String name)
    {
        currentBlock = new Block { Name = name, Start = template.Length };
    }

    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(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;
            }
        }
    }
}
#>
MultDocument.ttinclude

各位看官,直接copy存爲ttinclude就可以了。

 

3.CommonAttr.ttinclude

準備工作差不多了,不過在寫tt模板之前,我先說下我抽出來的公用參數/方法。

<#+
    public class Attributes
    {
        // 文件版權信息
        public static string Copyright = DateTime.Now.Year + " RoryLiu All Rights Reserved";
        public static Version Version = Environment.Version;
        public static string Author = "auto generated by T4";
    
        public static string DbType = "MySql";//數據庫類型
        public static string DbName = "dbName";//數據庫名
        public static string ProName = "Namespace";//命名空間
        public static bool IsGo = true;//是否執行
        public static string TableNames = "*";//全部用"*",部分表用",表名,表名,..."
    }
    
    //得到屬性的Pascial風格名稱,比如my_table => MyTable
    public string GetPropertyPascialName(string source)
    {
        string[] s = source.Split('_');
        for(int i = 0; i < s.Length; i++)
        {
            string s1 = s[i].Substring(0, 1).ToUpper();
            string s2 = s[i].Substring(1);
            s[i] = String.Concat(s1,s2);
        }
        return String.Concat(s);    
    }
#>
CommonAttr.ttinclude

將這些參數抽出來主要是爲了方便生成Model、DAL、BLL等模板,不一定要用這個文件。

 

4.ModelTemp.tt

準備工作都做完了,終於到模板了,各位看官等急了吧,先給大家看下生成實體類的模板:(ModelTemp.tt其實也是上網找的,然後自己再修改( ‵▽′)ψ)

<#@ template  debug="true" hostSpecific="true" language="C#" #>
<#@ include file="CodeTemplates/MultDocument.ttinclude" #>
<#@ include file="CodeTemplates/DBSchema.ttinclude" #>
<#@ include file="CodeTemplates/CommonAttr.ttinclude" #>
<#@ output extension=".cs" #>
<# 
    //不生成則退出
    if (!Attributes.IsGo) return "";
    //命名空間
    var nsName = Attributes.ProName + ".Models";
    //實例化生成模板
    var manager = new Manager(Host, GenerationEnvironment, true) { OutputPath = Path.GetDirectoryName(Host.TemplateFile)};
    //獲取連接的數據庫
    var dbSchema = DBSchemaFactory.GetDBSchema(Attributes.DbType);
    //獲取數據庫下的所有表
    var entities = dbSchema.GetTables(Attributes.DbName);
    //實體名
    var entityName = "";

    foreach(Table entity in entities)
    {
        //只生成指定的表實體
        if (!Attributes.TableNames.Equals("*") && !Attributes.TableNames.Contains("," + entity.TableName + ","))
            continue;
        //生成實體名
        entityName = GetPropertyPascialName(entity.TableName);
        manager.StartBlock(entityName + ".cs");
        Column pkCol = entity.Columns.Find(c => c.IsPK);
        foreach (var col in entity.Columns)
        {
            if (col.IsPK)
            {
                pkCol = col;
                break;
            }
        }
#>
//-----------------------------------------------------------------------
// <copyright file="<#= entity.TableName #>.cs">
// * Copyright (C) <#= Attributes.Copyright #>
// * version : <#= Attributes.Version #>
// * author  : <#= Attributes.Author #>
// * FileName: <#= entityName #>.cs
// * history : Created by T4 <#= DateTime.Now #> 
// </copyright>
//-----------------------------------------------------------------------
using System;
using System.ComponentModel.DataAnnotations;

namespace <#= nsName #>
{
    /// <summary>
    /// <#= entityName #> Entity Model
    /// </summary>
    [Serializable]
    [Table("<#= entity.TableName #>")]
    public partial class <#= entityName #>
    {
        public <#= entityName #>() { }
<# 
        for (int i = 0; i < entity.Columns.Count; i++)
        {
#>
        
        /// <summary>
        /// <#= entity.Columns[i].Comment #>
        /// </summary>
<#
        if (pkCol.Name == entity.Columns[i].Name)
        {
#>
        [Key]
<#
        }
#>
        public <#= entity.Columns[i].Type #> <#= entity.Columns[i].Name #> { get; set; }
<# 
        }
#>
    }
}
<# 
        manager.EndBlock();
    }

    manager.Process(true);
#>
ModelTemp.tt

頭部有三個<#@ include #>,相信大家都知道是神馬了,沒錯,就是引用前面準備的三個ttinclude文件。

-- Attributes.IsGo,其實這個不用也沒什麼影響,我是因爲不想每次保存都彈出提示是否執行才加的,我設置成不再提示,然後用IsGo來判斷是否執行

-- 實體名我沒用數據庫表名,而是通過GetPropertyPascialName()將表名變成Pascial風格,真正的表名我是寫在自定義特性Table裏

-- 循環所有表時,我是通過Attributes.TableNames來確定哪些表要生成文件,這樣後期有改動某個表時不需要全部生成

-- 爲了給主鍵屬性加上特性[Key],我是通過IsPK來判斷

模板基本就改了這些。

 

5.Table.cs

Table類是自定義特性,繼承了System.Attribute,代碼如下:

    /// <summary>
    /// 映射數據庫表對象
    /// </summary>
    [AttributeUsageAttribute(AttributeTargets.Class, Inherited = false, AllowMultiple = false), Serializable]
    public class Table : Attribute
    {
        /// <summary>
        /// 表名稱
        /// </summary>
        public string TableName;
        public Table(string tblName)
        {
            TableName = tblName;
        }
    }
Table.cs

我是直接放在根目錄下,當然如果有多個自定義特性時,還是建議建個文件夾。

 

好了,現在只要把CommonAttr裏的IsGo改爲true,DBSchema裏的connectionString改成您本地的mysql就可以保存ModelTemp來生成了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章