欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C# ORM学习笔记:T4入门及生成数据库实体类

程序员文章站 2022-04-14 16:09:28
一、什么是T4? 1.1、T4简介 T4,即4个T开头的英文字母组合:Text Template Transformation Toolkit,是微软官方在Visual Studio 2008开始使用的代码生成引擎。T4是由一些文本块和控制逻辑组成的混合模板,简单地说,T4可以根据模板生成您想要的文 ......

    一、什么是t4?

    1.1、t4简介

    t4,即4个t开头的英文字母组合:text template transformation toolkit,是微软官方在visual studio 2008开始使用的代码生成引擎。t4是由一些文本块和控制逻辑组成的混合模板,简单地说,t4可以根据模板生成您想要的文件,如类文件、文本文件、html等等。

    vs提供了一套基于t4引擎的代码生成执行环境,由以下程序集构成:

    microsoft.visualstudio.texttemplating.10.0.dll

    microsoft.visualstudio.texttemplating.interfaces.10.0.dll

    microsoft.visualstudio.texttemplating.modeling.10.0.dll

    microsoft.visualstudio.texttemplating.vshost.10.0.dll

    1.2、t4模板类型

    t4模板有两种类型:

    1)运行时模板

    在应用程序中执行运行时t4文本模板,以便生成文本字符串。

    若要创建运行时模板,请向您的项目中添加"运行时文本模板"文件。另外,您还可以添加纯文本文件并将其"自定义工具"属性设置为"texttemplatingfilepreprocessor"。

    2)设计时模板

    在vs中执行设计时t4文本模板,以便定义应用程序的部分源代码和其它资源。

    若要创建设计时模板,请向您的项目中添加"文本模板"文件。 另外,您还可以添加纯文本文件并将其"自定义工具"属性设置为"texttemplatingfilegenerator"。

    1.3、插件安装

    vs默认的编辑工具无高亮、无提示,错误不容易定位,建议安装tangible t4 editor插件进行编写t4代码。

    二、t4 hello world示例

    假设有一个控制台应用程序linkto.test.consolet4,现在要输出"hello world"字符串,program代码如下:

using system;
using system.collections.generic;
using system.linq;
using system.text;
using system.threading.tasks;

namespace linkto.test.consolet4
{
    class program
    {
        static void main(string[] args)
        {
            console.writeline("hello world");
            console.read();
        }
    }
}

    现在把program.cs文件删除掉,利用t4模板生成一个与上述代码相同的program.cs,操作方法如下:

    1)项目右键"添加"->"新建项"->"文本模板",将名称更改为program.tt。

C# ORM学习笔记:T4入门及生成数据库实体类

    2)program.tt的代码如下:

<#@ output extension=".cs" #>
using system;
using system.collections.generic;
using system.linq;
using system.text;
using system.threading.tasks;

<# 
    string classname = "program";
#>
namespace linkto.test.consolet4
{
    class <#=classname #>
    {
        static void main(string[] args)
        {
            console.writeline("hello world");
            console.read();
        }
    }
}

    3)点击保存,即可看到program.tt下生成了一个program.cs文件,代码与最初的hello world一样。

    三、t4 hello world示例扩展

    现在扩展一下hello world示例,在程序中增加两个类:

    1)hello类,输出"hello"。

    2)world类,输出"world"。

    代码如下:

C# ORM学习笔记:T4入门及生成数据库实体类C# ORM学习笔记:T4入门及生成数据库实体类
<#@ template debug="false" hostspecific="false" language="c#" #>
<#@ assembly name="system.core" #>
<#@ import namespace="system.linq" #>
<#@ import namespace="system.text" #>
<#@ import namespace="system.collections.generic" #>
<#@ output extension=".cs" #>
using system;
using system.collections.generic;
using system.linq;
using system.text;
using system.threading.tasks;
<# 
    string classname = "program";
#>
<# 
    list<string> classnames = new list<string>() {"hello","world"};
    list<string> callmethods = new list<string>();
#>

namespace linkto.test.consolet4
{
<#    
foreach (string classname in classnames)
{
    callmethods.add($"{classname}.show();");
#>
    class <#=classname #>
    {
        /// <summary>
        /// <#=classname #>类show()方法
        /// </summary>
        public static void show()
        {
            console.writeline("<#=classname #>");
        }
    }
    
<#
}
#>
    class <#=classname #>
    {
        static void main(string[] args)
        {
            console.writeline("hello world");

<#
        foreach (string callmethod in callmethods)
        {
#>
            //<#=callmethod #>方法调用
            <#=callmethod #>
<#
        }
#>

            console.read();
        }
    }
}

    生成文件如下:

C# ORM学习笔记:T4入门及生成数据库实体类C# ORM学习笔记:T4入门及生成数据库实体类
using system;
using system.collections.generic;
using system.linq;
using system.text;
using system.threading.tasks;

namespace linkto.test.consolet4
{
    class hello
    {
        /// <summary>
        /// hello类show()方法
        /// </summary>
        public static void show()
        {
            console.writeline("hello");
        }
    }
    
    class world
    {
        /// <summary>
        /// world类show()方法
        /// </summary>
        public static void show()
        {
            console.writeline("world");
        }
    }
    
    class program
    {
        static void main(string[] args)
        {
            console.writeline("hello world");

            //hello.show();方法调用
            hello.show();
            //world.show();方法调用
            world.show();

            console.read();
        }
    }
}

    四、t4模板的基本结构

    代码块可分为两种:文本及程序脚本。

    4.1、文本:就是需要生成的文本

    4.2、程序脚本:内部执行,最终生成想要的文本。t4中<# #>中的部分,都属于程序脚本内容。

    为了方便理解,使用"块"(block)来细分语法。块是构成t4模板的基本单元,可以分成5类:指令块(directive block)、文本块(text block)、代码语句块(statement block)、表达式块(expression block)、类特性块(class feature block)。

    4.2.1、指令块(directive block)

    和asp.net页面的指令一样,它们出现在文件头,通过<#@ … #>表示。其中<#@ template … #>指令是必须的,用于定义模板的基本属性,比如编程语言、基于的文化、是否支持调式等。

    指令通常是模板文件或包含的文件中的第一个元素。不应将它们放置在代码块<#...#>内,也不应放置在类功能块<#+...#>之后。

    t4 模板指令

    <#@ template [language="c#"] [hostspecific="true"] [debug="true"] [inherits="templatebaseclass"] [culture="code"] [compileroptions="options"] #>

    t4 参数指令

    <#@ parameter type="full.typename" name="parametername" #>

    t4 输出指令

    <#@ output extension=".filenameextension" [encoding="encoding"] #>

    t4 程序集指令

    <#@ assembly name="[assembly strong name|assembly file name]" #>

    $(solutiondir):当前项目所在解决方案目录

    $(projectdir):当前项目所在目录

    $(targetpath):当前项目编译输出文件绝对路径

    $(targetdir):当前项目编译输出目录

    t4 导入指令

    <#@ import namespace="namespace" #>

    t4 包含指令

    <#@ include file="filepath" #>

    4.2.2、文本块(text block)

    文本块就是直接原样输出的静态文本,不需要添加任何的标签。

    4.2.3、代码语句块(statement block)

    代码语句块通过<# statement #>的形式表示,中间是一段通过相应编程语言编写的程序调用,我们可以通过代码语句快控制文本转化的流程。

    4.2.4、表达式块(expression block)

    表达式块以<#=expression #>的形式表示,通过它可以动态地解析字符串表达式内嵌到输出的文本中。

    4.2.5、类特性块(class feature block)

    如果文本转化需要一些比较复杂的逻辑,代码可能需要写在一个单独的辅助方法中,甚至是定义一些单独的类。类特性块的表现形式为<#+ featurecode #>。

    五、t4模板生成数据库实体类

    5.1、添加一个t4code文件夹,并在下面新建两个文本模板,需要注意的是,这里不使用默认的.tt扩展名,而是.ttinclude,将它们作为包含文件使用。

C# ORM学习笔记:T4入门及生成数据库实体类C# ORM学习笔记:T4入门及生成数据库实体类
<#@ assembly name="system.core" #>
<#@ assembly name="system.data" #>
<#@ assembly name="$(projectdir)\lib\mysql.data.dll" #>
<#@ import namespace="system.linq" #>
<#@ import namespace="system.text" #>
<#@ import namespace="system.collections.generic" #>
<#@ import namespace="system.data"#>
<#@ import namespace="system.data.sqlclient"#>
<#@ import namespace="mysql.data.mysqlclient"#>
<#+
    #region t4code
    /// <summary>
    /// 数据库架构接口
    /// </summary>
    public interface idbschema : idisposable
    {
        list<string> gettablelist();
        table gettablemetadata(string tablename);
    }

    /// <summary>
    /// 数据库架构工厂
    /// </summary>
    public class dbschemafactory
    {
        static readonly string databasetype = "sqlserver";
        public static idbschema getdbschema()
        {
            idbschema dbschema;
            switch (databasetype) 
            {
                case "sqlserver":
                    {
                        dbschema =new sqlserverschema();
                        break;
                    }
                case "mysql":
                    {
                        dbschema = new mysqlschema();
                        break;
                    }
                default: 
                    {
                        throw new argumentexception("the input argument of databasetype is invalid.");
                    }
            }
            return dbschema;
        }
    }

    /// <summary>
    /// sqlserver
    /// </summary>
    public class sqlserverschema : idbschema
    {
        public string connectionstring = "server=.;database=cfdev;uid=sa;pwd=********;";
        public sqlconnection conn;

        public sqlserverschema()
        {
            conn = new sqlconnection(connectionstring);
            conn.open();
        }

        public list<string> gettablelist()
        {
            datatable dt = conn.getschema("tables");
            list<string> list = new list<string>();
            foreach (datarow row in dt.rows)
            {
                list.add(row["table_name"].tostring());
            }
            return list;
        }
        
        public table gettablemetadata(string tablename)
        {
            string commandtext = string.format("select * from {0}", tablename); ;
            sqlcommand cmd = new sqlcommand(commandtext, conn);
            sqldataadapter da = new sqldataadapter(cmd);
            dataset ds = new dataset();
            da.fillschema(ds, schematype.mapped, tablename);
            table table = new table(ds.tables[0]);
            return table;
        }

        public void dispose()
        {
            if (conn != null)
            {
                conn.close();
            }
        }
    }

    /// <summary>
    /// mysql
    /// </summary>
    public class mysqlschema : idbschema
    {
        public string connectionstring = "server=localhost;port=3306;database=projectdata;uid=root;pwd=;";
        public mysqlconnection conn;

        public mysqlschema()
        {
            conn = new mysqlconnection(connectionstring);
            conn.open();
        }

        public list<string> gettablelist()
        {
            datatable dt = conn.getschema("tables");
            list<string> list = new list<string>();
            foreach (datarow row in dt.rows)
            {
                list.add(row["table_name"].tostring());
            }
            return list;
        }

        public table gettablemetadata(string tablename)
        {
            string commandtext = string.format("select * from {0}", tablename); ;
            mysqlcommand cmd = new mysqlcommand(commandtext, conn);
            mysqldataadapter da = new mysqldataadapter(cmd);
            dataset ds = new dataset();
            da.fillschema(ds, schematype.mapped, tablename);
            table table = new table(ds.tables[0]);
            return table;
        }

        public void dispose()
        {
            if (conn != null)
            {
                conn.close();
            }
        }
    }

    /// <summary>
    /// 数据表
    /// </summary>
    public class table
    {
        public list<column> pks;
        public list<column> columns;
        public string datatypes;

        public table(datatable dt)
        {
            pks = getpklist(dt);
            columns = getcolumnlist(dt);
            datatypes = getdatatypelist(symbol.normal);
        }

        public list<column> getpklist(datatable dt)
        {
            list<column> list = new list<column>();
            column column = null;
            if (dt.primarykey.length > 0)
            {
                list = new list<column>();
                foreach (datacolumn dc in dt.primarykey)
                {
                    column = new column(dc);
                    list.add(column);
                }
            }
            return list;
        }

        private list<column> getcolumnlist(datatable dt)
        {
            list<column> list = new list<column>();
            column column = null;
            foreach (datacolumn dc in dt.columns)
            {
                column = new column(dc);
                list.add(column);
            }
            return list;
        }

        private string getdatatypelist(symbol symbol)
        {
            list<string> list = new list<string>();
            foreach (column c in columns)
            {
                if (symbol == symbol.normal)
                    list.add(string.format("{0} {1}", c.datatype, c.uppercolumnname));
                else if (symbol == symbol.underline)
                    list.add(string.format("{0} _{1}", c.datatype, c.uppercolumnname));
            }
            return string.join(",", list.toarray());
        }
    }

    /// <summary>
    /// 数据列
    /// </summary>
    public class column
    {
        datacolumn columnbase;

        public column(datacolumn _columnbase)
        {
            columnbase = _columnbase;
        }

        public string columnname { get { return columnbase.columnname; } }

        public string datatype
        { 
            get 
            {
                string result = string.empty;
                if (columnbase.datatype.name == "guid")//for mysql,因为对于mysql如果是char(36),类型自动为guid。
                    result = "string";
                else if (columnbase.datatype.name == "string")
                    result = "string";
                else if (columnbase.datatype.name == "int32")
                    result = "int";
                else
                    result = columnbase.datatype.name;
                return result; 
            } 
        }

        public string maxlength { get { return columnbase.maxlength.tostring(); } }

        public bool allowdbnull { get { return columnbase.allowdbnull; } }

        public string uppercolumnname
        {
            get
            {
                return string.format("{0}{1}", columnname[0].tostring().toupper(), columnname.substring(1));
            }
        }

        public string lowercolumnname
        {
            get
            {
                return string.format("{0}{1}", columnname[0].tostring().tolower(), columnname.substring(1));
            }
        }
    }

    /// <summary>
    /// 帮助类
    /// </summary>
    public class generatorhelper
    {
        public static readonly string stringtype = "string";
        public static readonly string datetimetype = "datetime";
        public static string getquestionmarkbytype(string typename)
        {
            string result = typename;
            if (typename == datetimetype)
            {
                result += "?";
            }
            return result;
        }
    }

    /// <summary>
    /// 符号枚举
    /// </summary>
    public enum symbol
    {
        normal = 1,
        underline = 2
    }
    #endregion
#>

    dbschema.ttinclude主要实现了数据库工厂的功能。注:请将数据库连接字符串改成您自己的。

C# ORM学习笔记:T4入门及生成数据库实体类C# ORM学习笔记:T4入门及生成数据库实体类
<#@ assembly name="system.core" #>
<#@ assembly name="system.data" #>
<#@ assembly name="envdte" #>
<#@ import namespace="system.linq" #>
<#@ import namespace="system.text" #>
<#@ import namespace="system.collections.generic" #>
<#@ import namespace="system.data"#>
<#@ import namespace="system.io"#>
<#@ 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 up
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;
            }
        }
    }
}
#>

    multidocument.ttinclude主要实现了多文档的功能。

    5.2、添加一个multmodelauto.tt文本模板,代码如下:

C# ORM学习笔记:T4入门及生成数据库实体类C# ORM学习笔记:T4入门及生成数据库实体类
<#@ template debug="true" hostspecific="true" language="c#" #>
<#@ assembly name="system.core" #>
<#@ import namespace="system.linq" #>
<#@ import namespace="system.text" #>
<#@ import namespace="system.collections.generic" #>
<#@ output extension=".cs" #>
<#@ include file="t4code/dbschema.ttinclude"#>
<#@ include file="t4code/multidocument.ttinclude"#>
<# var manager = new manager(host, generationenvironment, true) { outputpath = path.getdirectoryname(host.templatefile)}; #>
<#
    //system.diagnostics.debugger.launch();//调试
    var dbschema = dbschemafactory.getdbschema();
    list<string> tablelist = dbschema.gettablelist();
    foreach(string tablename in tablelist)
    {
        manager.startblock(tablename+".cs");
        table table = dbschema.gettablemetadata(tablename);
#>
//-------------------------------------------------------------------------------
// 此代码由t4模板multmodelauto自动生成
// 生成时间 <#=datetime.now.tostring("yyyy-mm-dd hh:mm:ss")#>
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//-------------------------------------------------------------------------------

using system;
using system.collections.generic;
using system.text;

namespace project.model
{
    [serializable]
    public class <#=tablename#>
    {
        #region constructor
        public <#=tablename#>() { }

        public <#=tablename#>(<#=table.datatypes#>)
        {
<#
        foreach(column c in table.columns)
        {
#>
            this.<#=c.uppercolumnname#> = <#=c.uppercolumnname#>;
<#
        }
#>
        }
        #endregion

        #region attributes
<#
        foreach(column c in table.columns)
        {
#>
        public <#=generatorhelper.getquestionmarkbytype(c.datatype)#> <#=c.uppercolumnname#> {get; set;}
<#
        }
#>
        #endregion

        #region validator
        public list<string> errorlist = new list<string>();
        private bool validator()
        {    
            bool validatorresult = true;
<#
            foreach(column c in table.columns)
            {
                if (!c.allowdbnull)
                {
                    if(c.datatype == generatorhelper.stringtype)
                    {
#>
            if (string.isnullorempty(<#=c.uppercolumnname#>))
            {
                validatorresult = false;
                errorlist.add("the <#=c.uppercolumnname#> should not be empty.");
            }
<#
                    }

                    if(c.datatype == generatorhelper.datetimetype)
                    {
#>
            if (<#=c.uppercolumnname#> == null)
            {
                validatorresult = false;
                errorlist.add("the <#=c.uppercolumnname#> should not be empty.");
            }
<#
                    }
                }

            if (c.datatype == generatorhelper.stringtype)
            {
#>
            if (<#=c.uppercolumnname#> != null && <#=c.uppercolumnname#>.length > <#=c.maxlength#>)
            {
                validatorresult = false;
                errorlist.add("the length of <#=c.uppercolumnname#> should not be greater then <#=c.maxlength#>.");
            }
<#
            }
            }
#>
            return validatorresult;
        }    
        #endregion
    }
}
<#
        manager.endblock();
    }
    dbschema.dispose();
    manager.process(true);
#>

    代码保存后,可以看到此文件下面已按数据表生成了多个实体类文件:

C# ORM学习笔记:T4入门及生成数据库实体类

 

    参考自:

    

    

 

    附:msdn t4文档