写在前面:

上文我们搭建好了属于自己的一个仓储模式的项目框架,项目中有两个手动创建的实体类,那么如果我们的项目需要用100个实体类,那么就需要手动在仓储层,业务逻辑层手动创建100个对象的仓储和业务逻辑吗?答案是显而易见的,应该自动化的来创建。今天,我们就来解决这一问题。

一、什么是T4模板?

T4,即4T开头的英文字母组合:Text Template Transformation Toolkit
T4文本模板,即一种自定义规则的代码生成器。根据业务模型可生成任何形式的文本文件或供程序调用的字符串。(模型以适合于应用程序域的形式包含信息,并且可以在应用程序的生存期更改)。
这里关于T4模板的语法就不做过多赘述了,有兴趣的小伙伴们可以自行百度学习!

二、创建T4模板项目

image.png

1、新建一个类库,分别建立仓储层,业务逻辑层的文件夹,等会儿我们将在各文件夹下创建对应该层的T4模板来实现自动更新。

2、首先在类库的根目录新建 ModelAuto.ttinclude 模板文件,用来控制我们的文件生成配置

ModelAuto.ttinclude

  1. <#@ assembly name="System.Core"#>
  2. <#@ assembly name="EnvDTE"#>
  3. <#@ import namespace="System.Collections.Generic"#>
  4. <#@ import namespace="System.IO"#>
  5. <#@ import namespace="System.Text"#>
  6. <#@ import namespace="Microsoft.VisualStudio.TextTemplating"#>
  7. <#+
  8. class Manager
  9. {
  10. public struct Block {
  11. public int Start, Length;
  12. public String Name,OutputPath;
  13. }
  14. public List<Block> blocks = new List<Block>();
  15. public Block currentBlock;
  16. public Block footerBlock = new Block();
  17. public Block headerBlock = new Block();
  18. public ITextTemplatingEngineHost host;
  19. public ManagementStrategy strategy;
  20. public StringBuilder template;
  21. public Manager(ITextTemplatingEngineHost host, StringBuilder template, bool commonHeader) {
  22. this.host = host;
  23. this.template = template;
  24. strategy = ManagementStrategy.Create(host);
  25. }
  26. public void StartBlock(String name,String outputPath) {
  27. currentBlock = new Block { Name = name, Start = template.Length ,OutputPath=outputPath};
  28. }
  29. public void StartFooter() {
  30. footerBlock.Start = template.Length;
  31. }
  32. public void EndFooter() {
  33. footerBlock.Length = template.Length - footerBlock.Start;
  34. }
  35. public void StartHeader() {
  36. headerBlock.Start = template.Length;
  37. }
  38. public void EndHeader() {
  39. headerBlock.Length = template.Length - headerBlock.Start;
  40. }
  41. public void EndBlock() {
  42. currentBlock.Length = template.Length - currentBlock.Start;
  43. blocks.Add(currentBlock);
  44. }
  45. public void Process(bool split) {
  46. String header = template.ToString(headerBlock.Start, headerBlock.Length);
  47. String footer = template.ToString(footerBlock.Start, footerBlock.Length);
  48. blocks.Reverse();
  49. foreach(Block block in blocks) {
  50. String fileName = Path.Combine(block.OutputPath, block.Name);
  51. if (split) {
  52. String content = header + template.ToString(block.Start, block.Length) + footer;
  53. strategy.CreateFile(fileName, content);
  54. template.Remove(block.Start, block.Length);
  55. } else {
  56. strategy.DeleteFile(fileName);
  57. }
  58. }
  59. }
  60. }
  61. class ManagementStrategy
  62. {
  63. internal static ManagementStrategy Create(ITextTemplatingEngineHost host) {
  64. return (host is IServiceProvider) ? new VSManagementStrategy(host) : new ManagementStrategy(host);
  65. }
  66. internal ManagementStrategy(ITextTemplatingEngineHost host) { }
  67. internal virtual void CreateFile(String fileName, String content) {
  68. File.WriteAllText(fileName, content);
  69. }
  70. internal virtual void DeleteFile(String fileName) {
  71. if (File.Exists(fileName))
  72. File.Delete(fileName);
  73. }
  74. }
  75. class VSManagementStrategy : ManagementStrategy
  76. {
  77. private EnvDTE.ProjectItem templateProjectItem;
  78. internal VSManagementStrategy(ITextTemplatingEngineHost host) : base(host) {
  79. IServiceProvider hostServiceProvider = (IServiceProvider)host;
  80. if (hostServiceProvider == null)
  81. throw new ArgumentNullException("Could not obtain hostServiceProvider");
  82. EnvDTE.DTE dte = (EnvDTE.DTE)hostServiceProvider.GetService(typeof(EnvDTE.DTE));
  83. if (dte == null)
  84. throw new ArgumentNullException("Could not obtain DTE from host");
  85. templateProjectItem = dte.Solution.FindProjectItem(host.TemplateFile);
  86. }
  87. internal override void CreateFile(String fileName, String content) {
  88. base.CreateFile(fileName, content);
  89. //((EventHandler)delegate { templateProjectItem.ProjectItems.AddFromFile(fileName); }).BeginInvoke(null, null, null, null);
  90. }
  91. internal override void DeleteFile(String fileName) {
  92. ((EventHandler)delegate { FindAndDeleteFile(fileName); }).BeginInvoke(null, null, null, null);
  93. }
  94. private void FindAndDeleteFile(String fileName) {
  95. foreach(EnvDTE.ProjectItem projectItem in templateProjectItem.ProjectItems) {
  96. if (projectItem.get_FileNames(0) == fileName) {
  97. projectItem.Delete();
  98. return;
  99. }
  100. }
  101. }
  102. }#>

3、还是在类库根目录下新建 DbHelper.ttinclude 模板,主要是数据库操作类

DbHelper.ttinclude

<#+
    public class config
    {
        public static readonly string ConnectionString = "这里写数据库连接字符串";
        public static readonly string DbDatabase = "";
        public static readonly string TableName = "";
    }
#>
<#+ 
    public class DbHelper
    {
        #region GetDbTables

         public static List<string> GetDbTablesNew(string connectionString, string database,string tables = null)
        { 
             if (!string.IsNullOrEmpty(tables))
            {
                tables = string.Format(" and obj.name in ('{0}')", tables.Replace(",", "','"));
            }
            string sql = string.Format(@"SELECT
                obj.name tablename
                from {0}.sys.objects obj 
                inner join {0}.dbo.sysindexes idx on obj.object_id=idx.id and idx.indid<=1
                INNER JOIN {0}.sys.schemas schem ON obj.schema_id=schem.schema_id
                left join {0}.sys.extended_properties g ON (obj.object_id = g.major_id AND g.minor_id = 0 AND g.name= 'MS_Description')
                where type='U' {1} 
                order by obj.name", database,tables);
            DataTable dt = GetDataTable(connectionString, sql);
            return dt.Rows.Cast<DataRow>().Select(row =>row.Field<string>("tablename")).ToList();
        }

        public static List<DbTable> GetDbTables(string connectionString, string database, string tables = null)
        {

            if (!string.IsNullOrEmpty(tables))
            {
                tables = string.Format(" and obj.name in ('{0}')", tables.Replace(",", "','"));
            }
            #region SQL
            string sql = string.Format(@"SELECT
                                    obj.name tablename,
                                    schem.name schemname,
                                    idx.rows,
                                    CAST
                                    (
                                        CASE 
                                            WHEN (SELECT COUNT(1) FROM sys.indexes WHERE object_id= obj.OBJECT_ID AND is_primary_key=1) >=1 THEN 1
                                            ELSE 0
                                        END 
                                    AS BIT) HasPrimaryKey                                         
                                    from {0}.sys.objects obj 
                                    inner join {0}.dbo.sysindexes idx on obj.object_id=idx.id and idx.indid<=1
                                    INNER JOIN {0}.sys.schemas schem ON obj.schema_id=schem.schema_id
                                    where type='U' {1}
                                    order by obj.name", database, tables);
            #endregion
            DataTable dt = GetDataTable(connectionString, sql);
            return dt.Rows.Cast<DataRow>().Select(row => new DbTable
            {
                TableName = row.Field<string>("tablename"),
                SchemaName = row.Field<string>("schemname"),
                Rows = row.Field<int>("rows"),
                HasPrimaryKey = row.Field<bool>("HasPrimaryKey")
            }).ToList();
        }
        #endregion

        #region GetDbColumns

        public static List<DbColumn> GetDbColumns(string connectionString, string database, string tableName, string schema = "dbo")
        {
            #region SQL
            string sql = string.Format(@"
                                    WITH indexCTE AS
                                    (
                                        SELECT 
                                        ic.column_id,
                                        ic.index_column_id,
                                        ic.object_id    
                                        FROM {0}.sys.indexes idx
                                        INNER JOIN {0}.sys.index_columns ic ON idx.index_id = ic.index_id AND idx.object_id = ic.object_id
                                        WHERE  idx.object_id =OBJECT_ID(@tableName) AND idx.is_primary_key=1
                                    )
                                    select
                                    colm.column_id ColumnID,
                                    CAST(CASE WHEN indexCTE.column_id IS NULL THEN 0 ELSE 1 END AS BIT) IsPrimaryKey,
                                    colm.name ColumnName,
                                    systype.name ColumnType,
                                    colm.is_identity IsIdentity,
                                    colm.is_nullable IsNullable,
                                    cast(colm.max_length as int) ByteLength,
                                    (
                                        case 
                                            when systype.name='nvarchar' and colm.max_length>0 then colm.max_length/2 
                                            when systype.name='nchar' and colm.max_length>0 then colm.max_length/2
                                            when systype.name='ntext' and colm.max_length>0 then colm.max_length/2 
                                            else colm.max_length
                                        end
                                    ) CharLength,
                                    cast(colm.precision as int) Precision,
                                    cast(colm.scale as int) Scale,
                                    prop.value Remark
                                    from {0}.sys.columns colm
                                    inner join {0}.sys.types systype on colm.system_type_id=systype.system_type_id and colm.user_type_id=systype.user_type_id
                                    left join {0}.sys.extended_properties prop on colm.object_id=prop.major_id and colm.column_id=prop.minor_id
                                    LEFT JOIN indexCTE ON colm.column_id=indexCTE.column_id AND colm.object_id=indexCTE.object_id                                        
                                    where colm.object_id=OBJECT_ID(@tableName)
                                    order by colm.column_id", database);
            #endregion
            SqlParameter param = new SqlParameter("@tableName", SqlDbType.NVarChar, 100) { Value = string.Format("{0}.{1}.{2}", database, schema, tableName) };
            DataTable dt = GetDataTable(connectionString, sql, param);
            return dt.Rows.Cast<DataRow>().Select(row => new DbColumn()
            {
                ColumnID = row.Field<int>("ColumnID"),
                IsPrimaryKey = row.Field<bool>("IsPrimaryKey"),
                ColumnName = row.Field<string>("ColumnName"),
                ColumnType = row.Field<string>("ColumnType"),
                IsIdentity = row.Field<bool>("IsIdentity"),
                IsNullable = row.Field<bool>("IsNullable"),
                ByteLength = row.Field<int>("ByteLength"),
                CharLength = row.Field<int>("CharLength"),
                Precision=row.Field<int>("Precision"),
                Scale = row.Field<int>("Scale"),
                Remark = row["Remark"].ToString()
            }).ToList();
        }

        #endregion

        #region GetDataTable

        public static DataTable GetDataTable(string connectionString, string commandText, params SqlParameter[] parms)
        {
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                SqlCommand command = connection.CreateCommand();
                command.CommandText = commandText;
                command.Parameters.AddRange(parms);
                SqlDataAdapter adapter = new SqlDataAdapter(command);

                DataTable dt = new DataTable();
                adapter.Fill(dt);

                return dt;
            }
        }

        #endregion

        #region GetPrimaryKey
        public static string GetPrimaryKey(List<DbColumn> dbColumns)
        {
            string primaryKey = string.Empty;
            if (dbColumns!=null&&dbColumns.Count>0)
            {
                foreach (var item in dbColumns)
                {
                    if (item.IsPrimaryKey==true)
                    {
                        primaryKey = item.ColumnName;
                    }
                }
            }
            return primaryKey;
        }
        #endregion
    }

    #region DbTable
    public sealed class DbTable
    {
        public string TableName { get; set; }
        public string SchemaName { get; set; }
        public int Rows { get; set; }

        public bool HasPrimaryKey { get; set; }
    }
    #endregion

    #region DbColumn

    public sealed class DbColumn
    {

        public int ColumnID { get; set; }


        public bool IsPrimaryKey { get; set; }


        public string ColumnName { get; set; }


        public string ColumnType { get; set; }


        public string CSharpType
        {
            get
            {
                return SqlServerDbTypeMap.MapCsharpType(ColumnType);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public Type CommonType
        {
            get
            {
                return SqlServerDbTypeMap.MapCommonType(ColumnType);
            }
        }

        public int ByteLength { get; set; }

        public int CharLength { get; set; }

        public int Precision{get;set;}
        public int Scale { get; set; }

        public bool IsIdentity { get; set; }

        public bool IsNullable { get; set; }

        public string Remark { get; set; }
    }
    #endregion

    #region SqlServerDbTypeMap

    public class SqlServerDbTypeMap
    {
        public static string MapCsharpType(string dbtype)
        {
            if (string.IsNullOrEmpty(dbtype)) return dbtype;
            dbtype = dbtype.ToLower();
            string 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;
            }
            return csharpType;
        }

        public static Type MapCommonType(string dbtype)
        {
            if (string.IsNullOrEmpty(dbtype)) return Type.Missing.GetType();
            dbtype = dbtype.ToLower();
            Type commonType = typeof(object);
            switch (dbtype)
            {
                case "bigint": commonType = typeof(long); break;
                case "binary": commonType = typeof(byte[]); break;
                case "bit": commonType = typeof(bool); break;
                case "char": commonType = typeof(string); break;
                case "date": commonType = typeof(DateTime); break;
                case "datetime": commonType = typeof(DateTime); break;
                case "datetime2": commonType = typeof(DateTime); break;
                case "datetimeoffset": commonType = typeof(DateTimeOffset); break;
                case "decimal": commonType = typeof(decimal); break;
                case "float": commonType = typeof(double); break;
                case "image": commonType = typeof(byte[]); break;
                case "int": commonType = typeof(int); break;
                case "money": commonType = typeof(decimal); break;
                case "nchar": commonType = typeof(string); break;
                case "ntext": commonType = typeof(string); break;
                case "numeric": commonType = typeof(decimal); break;
                case "nvarchar": commonType = typeof(string); break;
                case "real": commonType = typeof(Single); break;
                case "smalldatetime": commonType = typeof(DateTime); break;
                case "smallint": commonType = typeof(short); break;
                case "smallmoney": commonType = typeof(decimal); break;
                case "sql_variant": commonType = typeof(object); break;
                case "sysname": commonType = typeof(object); break;
                case "text": commonType = typeof(string); break;
                case "time": commonType = typeof(TimeSpan); break;
                case "timestamp": commonType = typeof(byte[]); break;
                case "tinyint": commonType = typeof(byte); break;
                case "uniqueidentifier": commonType = typeof(Guid); break;
                case "varbinary": commonType = typeof(byte[]); break;
                case "varchar": commonType = typeof(string); break;
                case "xml": commonType = typeof(string); break;
                default: commonType = typeof(object); break;
            }
            return commonType;
        }
    }
    #endregion
 #>

三、实现整个系统框架模板

1、创建IRespository层模板

Blog.Framework.IRepository.tt

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core.dll" #>
<#@ assembly name="System.Data.dll" #>
<#@ assembly name="System.Data.DataSetExtensions.dll" #>
<#@ assembly name="System.Xml.dll" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.IO" #>
<#@ include file="$(ProjectDir)DbHelper.ttinclude"  #>
<#@ include file="$(ProjectDir)ModelAuto.ttinclude"    #>
<# var manager = new Manager(Host, GenerationEnvironment, true); #>


<# 
    var OutputPath1 =Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(Host.TemplateFile+"..")+"..")+"..");
    OutputPath1=Path.Combine(OutputPath1,"Blog.IRepository","IRepositories_New");
    if (!Directory.Exists(OutputPath1))
    {
        Directory.CreateDirectory(OutputPath1);
    }  
#>


//--------------------------------------------------------------------
//     此代码由T4模板自动生成
//       生成时间 <#=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")#> 
//     对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------

<# 
    SqlConnection conn = new SqlConnection(config.ConnectionString); 
    conn.Open(); 
    System.Data.DataTable schema = conn.GetSchema("TABLES"); 

    foreach(System.Data.DataRow row in schema.Rows) 
    {  

        if(row["TABLE_NAME"].ToString()!="__EFMigrationsHistory"){
        manager.StartBlock("I"+row["TABLE_NAME"].ToString()+"Repository"+".cs",OutputPath1);//文件名
     #>
//----------<#=row["TABLE_NAME"].ToString()#>开始----------    
using Blog.IRepository.Base;
using Blog.Model.EntityModels;
namespace Blog.IRepository
{    
    /// <summary>
    /// I<#=row["TABLE_NAME"].ToString()#>Repository
    /// </summary>    
    public interface I<#=row["TABLE_NAME"].ToString()#>Repository : IBaseRepository<<#=row["TABLE_NAME"].ToString()#>>//类名
    {


    }
}  

//----------<#=row["TABLE_NAME"].ToString()#>结束----------

//----------结束----------
    <# 
        manager.EndBlock(); 
        }
    } 
    manager.Process(true);

    #>

注:因为项目使用的CodeFirst,所以在数据库中会存在一张迁移的记录表,那么如果不想让T4模板自动生成这张表的仓储层,我们需要排除,排除代码在上述代码中已经加上了
image.png

2、创建Repository层模板

Blog.Framework.Repository.tt

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core.dll" #>
<#@ assembly name="System.Data.dll" #>
<#@ assembly name="System.Data.DataSetExtensions.dll" #>
<#@ assembly name="System.Xml.dll" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.IO" #>
<#@ include file="$(ProjectDir)DbHelper.ttinclude"  #>
<#@ include file="$(ProjectDir)ModelAuto.ttinclude"    #>
<# var manager = new Manager(Host, GenerationEnvironment, true); #>


<# 
    var OutputPath1 =Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(Host.TemplateFile+"..")+"..")+"..");
    OutputPath1=Path.Combine(OutputPath1,"Blog.Repository","Repositories_New");
    if (!Directory.Exists(OutputPath1))
    {
        Directory.CreateDirectory(OutputPath1);
    }
#>


//--------------------------------------------------------------------
//     此代码由T4模板自动生成
//       生成时间 <#=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")#> 
//     对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------


<# 
    SqlConnection conn = new SqlConnection(config.ConnectionString); 
    conn.Open(); 
    System.Data.DataTable schema = conn.GetSchema("TABLES"); 

    foreach(System.Data.DataRow row in schema.Rows) 
    {  
        if(row["TABLE_NAME"].ToString()!="__EFMigrationsHistory"){

        manager.StartBlock(row["TABLE_NAME"].ToString()+"Repository"+".cs",OutputPath1);
     #>
    //----------<#=row["TABLE_NAME"].ToString()#>开始----------


using Blog.Model.EntityModels;
using Blog.Repository.Base;
using Blog.IRepository;
using Blog.IRepository.UnitOfWork;
using Blog.EntityFrameworkCore;

namespace Blog.Repository
{    
    /// <summary>
    /// <#=row["TABLE_NAME"].ToString()#>Repository
    /// </summary>    
    public class <#=row["TABLE_NAME"].ToString()#>Repository : BaseRepository<<#=row["TABLE_NAME"].ToString()#>>, I<#=row["TABLE_NAME"].ToString() #>Repository
    {
        public <#=row["TABLE_NAME"].ToString()#>Repository(EFDBContext myDbContext) : base(myDbContext)
        {
        }

    }
}

    //----------<#=row["TABLE_NAME"].ToString()#>结束----------


    //----------结束----------
    <# 
        manager.EndBlock(); 
        }
    }


    manager.Process(true);

    #>

3、创建IServices层模板

Blog.Framework.IServices.tt

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core.dll" #>
<#@ assembly name="System.Data.dll" #>
<#@ assembly name="System.Data.DataSetExtensions.dll" #>
<#@ assembly name="System.Xml.dll" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.IO" #>  
<#@ include file="$(ProjectDir)DbHelper.ttinclude"  #>
<#@ include file="$(ProjectDir)ModelAuto.ttinclude"    #>
<# var manager = new Manager(Host, GenerationEnvironment, true); #>


<# 
    var OutputPath1 =Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(Host.TemplateFile+"..")+"..")+"..");
    OutputPath1=Path.Combine(OutputPath1,"Blog.IServices","IServices_New");
    if (!Directory.Exists(OutputPath1))
    {
        Directory.CreateDirectory(OutputPath1);
    }
#>



//--------------------------------------------------------------------
//     此代码由T4模板自动生成
//       生成时间 <#=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")#> 
//     对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
<# 
    var tableName=config.TableName;
 #>
<# 
if(tableName!="" && tableName!="__EFMigrationsHistory"){
    #>  

using Blog.IServices.Base;     
using Blog.Model.EntityModels;

namespace Blog.IServices
{    
    /// <summary>
    /// I<#=tableName#>Services
    /// </summary>    
    public interface I<#=tableName#>Services :IBaseServices<<#=tableName#>>
    {


    }
}

<# 
    } else{ 

    #>

<# 
    SqlConnection conn = new SqlConnection(config.ConnectionString); 
    conn.Open(); 
    System.Data.DataTable schema = conn.GetSchema("TABLES"); 

    foreach(System.Data.DataRow row in schema.Rows) 
    {  
        if(row["TABLE_NAME"].ToString()!="__EFMigrationsHistory"){
        manager.StartBlock("I"+row["TABLE_NAME"].ToString()+"Services"+".cs",OutputPath1);
     #>
    //----------<#=row["TABLE_NAME"].ToString()#>开始----------

using Blog.IServices.Base;
using Blog.Model.EntityModels;

namespace Blog.IServices
{    
    /// <summary>
    /// <#=row["TABLE_NAME"].ToString()#>Services
    /// </summary>    
    public interface I<#=row["TABLE_NAME"].ToString()#>Services :IBaseServices<<#=row["TABLE_NAME"].ToString()#>>
    {


    }
}

    //----------<#=row["TABLE_NAME"].ToString()#>结束----------
    <# 
        manager.EndBlock(); 
        } 
    }

        manager.Process(true);
    }
    #>

4、创建Services层模板

Blog.Framework.Services.tt

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core.dll" #>
<#@ assembly name="System.Data.dll" #>
<#@ assembly name="System.Data.DataSetExtensions.dll" #>
<#@ assembly name="System.Xml.dll" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.IO" #>
<#@ include file="$(ProjectDir)DbHelper.ttinclude"  #>
<#@ include file="$(ProjectDir)ModelAuto.ttinclude"    #>
<# var manager = new Manager(Host, GenerationEnvironment, true); #>


<# 
    var OutputPath1 =Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(Host.TemplateFile+"..")+"..")+"..");
    OutputPath1=Path.Combine(OutputPath1,"Blog.Services","Services_New");
    if (!Directory.Exists(OutputPath1))
    {
        Directory.CreateDirectory(OutputPath1);
    }
#>



//--------------------------------------------------------------------
//     此代码由T4模板自动生成
//       生成时间 <#=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")#> 
//     对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
<# 
    var tableName=config.TableName;
 #>
<# 
if(tableName!="" && tableName!="__EFMigrationsHistory"){
    #>  


using System;
using System.Threading.Tasks; 
using Blog.IRepository;
using Blog.IRepository.Base;
using Blog.IRepository.UnitOfWork;
using Blog.IServices;
using Blog.Model.EntityModels;
using Blog.Services.Base;
namespace Blog.Services
{    
    /// <summary>
    /// <#=tableName#>Services
    /// </summary>    
    public class <#=tableName#>Services : BaseServices<<#=tableName#>>, I<#=tableName#>Services
    {

        public <#=tableName#>Services(IUnitOfWork unitOfWork, IBaseRepository<<#=tableName#>> dal):base(unitOfWork, dal)
        {

        }

    }
}

<# 
    } else{ 

    #>

<# 
    SqlConnection conn = new SqlConnection(config.ConnectionString); 
    conn.Open(); 
    System.Data.DataTable schema = conn.GetSchema("TABLES"); 

    foreach(System.Data.DataRow row in schema.Rows) 
    {  
        if(row["TABLE_NAME"].ToString()!="__EFMigrationsHistory"){
        manager.StartBlock(row["TABLE_NAME"].ToString()+"Services"+".cs",OutputPath1);
     #>
    //----------<#=row["TABLE_NAME"].ToString()#>开始----------
using System;
using System.Threading.Tasks; 
using Blog.IRepository;
using Blog.IRepository.UnitOfWork;
using Blog.IRepository.Base;
using Blog.IServices;
using Blog.Model.EntityModels;
using Blog.Services.Base;
namespace Blog.Services
{    
    /// <summary>
    /// <#=row["TABLE_NAME"].ToString()#>Services
    /// </summary>    
    public class <#=row["TABLE_NAME"].ToString()#>Services : BaseServices<<#=row["TABLE_NAME"].ToString()#>>, I<#=row["TABLE_NAME"].ToString() #>Services
    {
        public <#=row["TABLE_NAME"].ToString() #>Services(IUnitOfWork unitOfWork, IBaseRepository<<#=row["TABLE_NAME"].ToString()#>> dal):base(unitOfWork, dal)
        {

        }

    }
}  

    //----------<#=row["TABLE_NAME"].ToString()#>结束----------
    <# 
        manager.EndBlock(); 
        } 
    }

        manager.Process(true);
    }
    #>

四、具体操作方法

通过上面几个步骤,我们就已经完成了创建的工作,接下来对上述代码应用需要手动更改的地方做一个简单的阐述。
image.png
输出路径需要自行根据项目接口进行修改,简单来说就是一层一层往上找,最终找到你需要生成的项目的目录下,命名空间需要与你需要生成的项目下的命名空间一致,输出文件夹名称可根据自己需要自行配置。
image.png
引用这部分的命名空间需要写正确,否则生成的内容会报错。
建议操作办法,首先将命名空间改正确,然后生成一份,点进去看类里面是否报错,如有报错,那么先将错误解决后,再将正确的代码复制到T4模板中,最后删除之前生成的文件夹,重新生成。
上述方法执行完成之后,我们每次对实体类做了新增,删除的操作,那么都需要进入T4模板的项目中,重新保存一下T4模板,模板就会重新生成新的文件夹啦,这样就实现了自动创建啦。

写在后面:

上面我们通过T4模板实现了半自动化项目搭建,这里埋个伏笔,我们这么多的实体类被创建了,如果每次在使用的时候都需要手动依赖注入进去的话,是否是太过麻烦了呢,有没有什么好点儿的办法来实现自动注入呢?后面我们将使用AutoFac来实现自动依赖注入,敬请期待…