【框架总结】利用T4模板批量生成代码

很早就看过T4模板的介绍,是可以自定义规则来生成文件的,不过当时没时间研究就跳过了,继续使用动软生成代码。

现在终于抽时间学下T4模板,多亏大神们的无私分享,使我很快就用上T4模板了,灰常灰常感谢大大们~~

在这里我也总结下使用情况,有说的不好的欢迎指出~~

T4文件后缀主要有:tt和ttinclude,tt文件是模板文件,每次保存VS都会提示是否执行代码;ttinclude文件是tt的辅助文件,保存不会提示执行代码。

需要生成代码,一般都是映射数据库了,当然也可以用来生成其他比较统一格式的文件,全看您的模板怎么写,这里我是用来生成映射数据库表的实体类。

(如果想让T4模板代码高亮,有提示,可以下载安装T4模板插件

这是我的T4模板文件结构 

1.DBSchema.ttinclude

首先,我是找到一个DBSchema.ttinclude文件,此文件是用来访问数据库,从数据库读出表的信息,代码如下,我根据我自己的情况改动了一些

扫描二维码关注公众号,回复: 2837220 查看本文章

(由于找资料时没记住出处,忘记是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来生成了。

猜你喜欢

转载自www.cnblogs.com/rorysix/p/9497175.html