• C# ORM学习笔记: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。

        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"。

        代码如下:

    <#@ 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();
            }
        }
    }
    Program.tt

        生成文件如下:

    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();
            }
        }
    }
    Program.cs

        四、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,将它们作为包含文件使用。

    <#@ assembly name="System.Core" #>
    <#@ assembly name="System.Data" #>
    <#@ assembly name="$(ProjectDir)LibMySql.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

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

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

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

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

    <#@ 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);
    #>
    MultModelAuto.tt

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

        参考自:

        https://www.cnblogs.com/dataadapter/p/3844394.html

        https://www.cnblogs.com/yank/archive/2012/02/14/2342287.html

        附:MSDN T4文档

  • 相关阅读:
    Linux文件/proc/net/tcp分析
    为什么系统调用会消耗较多资源
    为什么Linux默认页大小是4KB
    为什么Linux需要虚拟内存
    Make 命令教程
    关于同步的一点思考-下
    关于同步的一点思考-上
    Linux下的进程控制块(PCB)
    汇编语言基础:寄存器和系统调用
    内核栈与thread_info结构详解
  • 原文地址:https://www.cnblogs.com/atomy/p/12698732.html
Copyright © 2020-2023  润新知