程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 使用T4模板生成MySql數據庫實體類,t4mysql

使用T4模板生成MySql數據庫實體類,t4mysql

編輯:C#入門知識

使用T4模板生成MySql數據庫實體類,t4mysql


  注:本文系作者原創,但可隨意轉載。

  現在呆的公司使用的數據庫幾乎都是MySQL。編程方式DatabaseFirst。即先寫數據庫設計,表設計按照規范好的文檔寫進EXCEL裡,然後用公司的宏,生成建表腳本和實體類文件。

  之前就見識過T4模板生成SQL實體類文件,但還沒自己實踐過,這次正好實現一下生成MySQL的實體類。

 

目標類文件結構大致如下:

1 //----------------------------------------------------------------------- 2 // <copyright file=" UserProfile2.cs" company="xxx Enterprises"> 3 // * Copyright (C) 2015 xxx Enterprises All Rights Reserved 4 // * version : 4.0.30319.18444 5 // * author : auto generated by T4 6 // * FileName: UserProfile2.cs 7 // * history : Created by T4 11/24/2015 18:05:30 8 // </copyright> 9 //----------------------------------------------------------------------- 10 using System; 11 12 namespace Console4Test 13 { 14 /// <summary> 15 /// UserProfile2 Entity Model 16 /// </summary> 17 [Serializable] 18 public class UserProfile2 19 { 20 /// <summary> 21 /// 主鍵ID 22 /// </summary> 23 public string ID { get; set; } 24 25 /// <summary> 26 /// 姓名 27 /// </summary> 28 public string Name { get; set; } 29 30 /// <summary> 31 /// 年齡 32 /// </summary> 33 public int Age { get; set; } 34 35 /// <summary> 36 /// 性別 37 /// </summary> 38 public int Gender { get; set; } 39 } 40 } UserProfile2

  主要思路其實就兩步:

    1)讀取數據庫表結構信息。(視個人情況,讀取到的信息夠用即可。)  

    2)根據讀取到的表結構信息,為每個表生成實體類文件。

 

  在實現第一步時,參考了一些SQL的文章。很多是需要多次執行SQL,感覺有點兒浪費。看了下MySQL的系統庫information_schema,裡面有張COLUMNS表,表裡有TABLE_SCHEMA(即數據庫名), TABLE_NAME(表名),  COLUMN_NAME(列名),  DATA_TYPE(數據類型), COLUMN_COMMENT(列說明)等字段,已能滿足基本需求,因此讀庫時,只進行一次查詢即可。

  下面列出Helper的代碼,只有2個方法,一是負責讀取數據庫表結構,二是把MySql數據庫類型與C#數據類型匹配,這裡我們建表時不允許為NULL,所以也不存在匹配可空類型,比較簡單。可能有的匹配的不對,我沒有全部試驗過,一些特殊類型比如set, enum等直接返回類型字符串,不做處理,讓編譯報錯即可。

1 <#@ assembly name="System.Core"#> 2 <#@ assembly name="System.Data"#> 3 <#@ assembly name="$(ProjectDir)\PublicDll\MySql.Data.dll" #> 4 <#@ import namespace="System" #> 5 <#@ import namespace="System.Data" #> 6 <#@ import namespace="System.Collections.Generic" #> 7 <#@ import namespace="System.Linq" #> 8 <#@ import namespace="MySql.Data.MySqlClient" #> 9 <#+ 10 public class EntityHelper 11 { 12 public static List<Entity> GetEntities(string connectionString, List<string> databases) 13 { 14 var list = new List<Entity>(); 15 var conn = new MySqlConnection(connectionString); 16 try 17 { 18 conn.Open(); 19 var dbs = string.Join("','", databases.ToArray()); 20 var cmd = string.Format(@"SELECT `information_schema`.`COLUMNS`.`TABLE_SCHEMA` 21 ,`information_schema`.`COLUMNS`.`TABLE_NAME` 22 ,`information_schema`.`COLUMNS`.`COLUMN_NAME` 23 ,`information_schema`.`COLUMNS`.`DATA_TYPE` 24 ,`information_schema`.`COLUMNS`.`COLUMN_COMMENT` 25 FROM `information_schema`.`COLUMNS` 26 WHERE `information_schema`.`COLUMNS`.`TABLE_SCHEMA` IN ('{0}') ", dbs); 27 using (var reader = MySqlHelper.ExecuteReader(conn, cmd)) 28 { 29 while (reader.Read()) 30 { 31 var db = reader["TABLE_SCHEMA"].ToString(); 32 var table = reader["TABLE_NAME"].ToString(); 33 var column = reader["COLUMN_NAME"].ToString(); 34 var type = reader["DATA_TYPE"].ToString(); 35 var comment = reader["COLUMN_COMMENT"].ToString(); 36 var entity = list.FirstOrDefault(x => x.EntityName == table); 37 if(entity == null) 38 { 39 entity = new Entity(table); 40 entity.Fields.Add(new Field 41 { 42 Name = column, 43 Type = GetCLRType(type), 44 Comment = comment 45 }); 46 47 list.Add(entity); 48 } 49 else 50 { 51 entity.Fields.Add(new Field 52 { 53 Name = column, 54 Type = GetCLRType(type), 55 Comment = comment 56 }); 57 } 58 } 59 } 60 } 61 finally 62 { 63 conn.Close(); 64 } 65 66 return list; 67 } 68 69 public static string GetCLRType(string dbType) 70 { 71 switch(dbType) 72 { 73 case "tinyint": 74 case "smallint": 75 case "mediumint": 76 case "int": 77 case "integer": 78 return "int"; 79 case "double": 80 return "double"; 81 case "float": 82 return "float"; 83 case "decimal": 84 return "decimal"; 85 case "numeric": 86 case "real": 87 return "decimal"; 88 case "bit": 89 return "bool"; 90 case "date": 91 case "time": 92 case "year": 93 case "datetime": 94 case "timestamp": 95 return "DateTime"; 96 case "tinyblob": 97 case "blob": 98 case "mediumblob": 99 case "longblog": 100 case "binary": 101 case "varbinary": 102 return "byte[]"; 103 case "char": 104 case "varchar": 105 case "tinytext": 106 case "text": 107 case "mediumtext": 108 case "longtext": 109 return "string"; 110 case "point": 111 case "linestring": 112 case "polygon": 113 case "geometry": 114 case "multipoint": 115 case "multilinestring": 116 case "multipolygon": 117 case "geometrycollection": 118 case "enum": 119 case "set": 120 default: 121 return dbType; 122 } 123 } 124 } 125 126 public class Entity 127 { 128 public Entity() 129 { 130 this.Fields = new List<Field>(); 131 } 132 133 public Entity(string name) 134 : this() 135 { 136 this.EntityName = name; 137 } 138 139 public string EntityName { get;set; } 140 public List<Field> Fields { get;set; } 141 } 142 143 public class Field 144 { 145 public string Name { get;set; } 146 public string Type { get;set; } 147 public string Comment { get;set; } 148 } 149 #> EntityHelper

  這裡需要注意的大概有三點:

    1)我通過NuGet引用的MySQL.Data.dll直接引用報錯找不到文件,我把它拷貝到PublicDLL\文件夾下進行引用。

    2)此文件為模板執行時引用的文件,不需直接執行,因此將其後綴名改為.ttinclude。

    3)MySQL在Windows下安裝後默認表名等大小寫不敏感。比如UserProfile表,讀出來就是userprofile,這樣生成的類名就是userprofile。因此需要對MySQL進行配置使其對大小寫敏感。很簡單可自行百度。

  

  第一步實現後,我搗鼓了兩下後發現執行模板只能生成一個文件,看的示例也比較簡單,沒有說生成多個文件的。後來搜索了一下,引用一個老外寫的Helper類就可以了,這個方法應該比較流行吧,看了下比較簡單,試了下也可以就沒看別的方法。

  附上他的博客地址:http://damieng.com/blog/2009/11/06/multiple-outputs-from-t4-made-easy-revisited

  下面附上他的Helper類代碼:

  

1 <#@ assembly name="System.Core"#> 2 <#@ assembly name="System.Data.Linq"#> 3 <#@ assembly name="EnvDTE"#> 4 <#@ assembly name="System.Xml"#> 5 <#@ assembly name="System.Xml.Linq"#> 6 <#@ import namespace="System"#> 7 <#@ import namespace="System.CodeDom"#> 8 <#@ import namespace="System.CodeDom.Compiler"#> 9 <#@ import namespace="System.Collections.Generic"#> 10 <#@ import namespace="System.Data.Linq"#> 11 <#@ import namespace="System.Data.Linq.Mapping"#> 12 <#@ import namespace="System.IO"#> 13 <#@ import namespace="System.Linq"#> 14 <#@ import namespace="System.Reflection"#> 15 <#@ import namespace="System.Text"#> 16 <#@ import namespace="System.Xml.Linq"#> 17 <#@ import namespace="Microsoft.VisualStudio.TextTemplating"#> 18 <#+ 19   20 // Manager class records the various blocks so it can split them up 21 class Manager { 22     private class Block { 23         public String Name; 24         public int Start, Length; 25     } 26   27     private Block currentBlock; 28     private List<Block> files = new List<Block>(); 29     private Block footer = new Block(); 30     private Block header = new Block(); 31     private ITextTemplatingEngineHost host; 32     private StringBuilder template; 33     protected List<String> generatedFileNames = new List<String>(); 34   35     public static Manager Create(ITextTemplatingEngineHost host, StringBuilder template) { 36         return (host is IServiceProvider) ? new VSManager(host, template) : new Manager(host, template); 37     } 38   39     public void StartNewFile(String name) { 40         if (name == null) 41             throw new ArgumentNullException("name"); 42         CurrentBlock = new Block { Name = name }; 43     } 44   45     public void StartFooter() { 46         CurrentBlock = footer; 47     } 48   49     public void StartHeader() { 50         CurrentBlock = header; 51     } 52   53     public void EndBlock() { 54         if (CurrentBlock == null) 55             return; 56         CurrentBlock.Length = template.Length - CurrentBlock.Start; 57         if (CurrentBlock != header && CurrentBlock != footer) 58             files.Add(CurrentBlock); 59         currentBlock = null; 60     } 61   62     public virtual void Process(bool split) { 63         if (split) { 64             EndBlock(); 65             String headerText = template.ToString(header.Start, header.Length); 66             String footerText = template.ToString(footer.Start, footer.Length); 67             String outputPath = Path.GetDirectoryName(host.TemplateFile); 68             files.Reverse(); 69             foreach(Block block in files) { 70                 String fileName = Path.Combine(outputPath, block.Name); 71                 String content = headerText + template.ToString(block.Start, block.Length) + footerText; 72                 generatedFileNames.Add(fileName); 73                 CreateFile(fileName, content); 74                 template.Remove(block.Start, block.Length); 75             } 76         } 77     } 78   79     protected virtual void CreateFile(String fileName, String content) { 80         if (IsFileContentDifferent(fileName, content)) 81             File.WriteAllText(fileName, content); 82     } 83   84     public virtual String GetCustomToolNamespace(String fileName) { 85         return null; 86     } 87   88     public virtual String DefaultProjectNamespace { 89         get { return null; } 90     } 91   92     protected bool IsFileContentDifferent(String fileName, String newContent) { 93         return !(File.Exists(fileName) && File.ReadAllText(fileName) == newContent); 94     } 95   96     private Manager(ITextTemplatingEngineHost host, StringBuilder template) { 97         this.host = host; 98         this.template = template; 99     } 100   101     private Block CurrentBlock { 102         get { return currentBlock; } 103         set { 104             if (CurrentBlock != null) 105                 EndBlock(); 106             if (value != null) 107                 value.Start = template.Length; 108             currentBlock = value; 109         } 110     } 111   112     private class VSManager: Manager { 113         private EnvDTE.ProjectItem templateProjectItem; 114         private EnvDTE.DTE dte; 115         private Action<String> checkOutAction; 116         private Action<IEnumerable<String>> projectSyncAction; 117   118         public override String DefaultProjectNamespace { 119             get { 120                 return templateProjectItem.ContainingProject.Properties.Item("DefaultNamespace").Value.ToString(); 121             } 122         } 123   124         public override String GetCustomToolNamespace(string fileName) { 125             return dte.Solution.FindProjectItem(fileName).Properties.Item("CustomToolNamespace").Value.ToString(); 126         } 127   128         public override void Process(bool split) { 129             if (templateProjectItem.ProjectItems == null) 130                 return; 131             base.Process(split); 132             projectSyncAction.EndInvoke(projectSyncAction.BeginInvoke(generatedFileNames, null, null)); 133         } 134   135         protected override void CreateFile(String fileName, String content) { 136             if (IsFileContentDifferent(fileName, content)) { 137                 CheckoutFileIfRequired(fileName); 138                 File.WriteAllText(fileName, content); 139             } 140         } 141   142         internal VSManager(ITextTemplatingEngineHost host, StringBuilder template) 143             : base(host, template) { 144             var hostServiceProvider = (IServiceProvider) host; 145             if (hostServiceProvider == null) 146                 throw new ArgumentNullException("Could not obtain IServiceProvider"); 147             dte = (EnvDTE.DTE) hostServiceProvider.GetService(typeof(EnvDTE.DTE)); 148             if (dte == null) 149                 throw new ArgumentNullException("Could not obtain DTE from host"); 150             templateProjectItem = dte.Solution.FindProjectItem(host.TemplateFile); 151             checkOutAction = (String fileName) => dte.SourceControl.CheckOutItem(fileName); 152             projectSyncAction = (IEnumerable<String> keepFileNames) => ProjectSync(templateProjectItem, keepFileNames); 153         } 154   155         private static void ProjectSync(EnvDTE.ProjectItem templateProjectItem, IEnumerable<String> keepFileNames) { 156             var keepFileNameSet = new HashSet<String>(keepFileNames); 157             var projectFiles = new Dictionary<String, EnvDTE.ProjectItem>(); 158             var originalFilePrefix = Path.GetFileNameWithoutExtension(templateProjectItem.get_FileNames(0)) + "."; 159             foreach(EnvDTE.ProjectItem projectItem in templateProjectItem.ProjectItems) 160                 projectFiles.Add(projectItem.get_FileNames(0), projectItem); 161   162             // Remove unused items from the project 163             foreach(var pair in projectFiles) 164                 if (!keepFileNames.Contains(pair.Key) && !(Path.GetFileNameWithoutExtension(pair.Key) + ".").StartsWith(originalFilePrefix)) 165                     pair.Value.Delete(); 166   167             // Add missing files to the project 168             foreach(String fileName in keepFileNameSet) 169                 if (!projectFiles.ContainsKey(fileName)) 170                     templateProjectItem.ProjectItems.AddFromFile(fileName); 171         } 172   173         private void CheckoutFileIfRequired(String fileName) { 174             var sc = dte.SourceControl; 175             if (sc != null && sc.IsItemUnderSCC(fileName) && !sc.IsItemCheckedOut(fileName)) 176                 checkOutAction.EndInvoke(checkOutAction.BeginInvoke(fileName, null, null)); 177         } 178     } 179 } #> T4Manager

  同樣把這個Helper類的後綴名改為.ttinclude

  需要注意的是這個文件引用了EnvDTE,看了下好像是操作VS用的,寫VS插件什麼的應該會用到吧。可直接從.net框架引用。但後來我把這個引用移除了好像也沒什麼影響。

 

最後貼上,我們用來執行的模板

1 <#@ template debug="false" hostspecific="true" language="C#" #> 2 <#@ include file="Manager.ttinclude" #> 3 <#@ include file="EntityHelper.ttinclude" #> 4 <# 5 // 是否是WCF服務模型 6 bool serviceModel = false; 7 8 // 數據庫連接 9 var connectionString = @"server=127.0.0.1;uid=root;pwd=12345678;charset=utf8;"; 10 11 // 需要解析的數據庫 12 var database = new List<string> { "chatroom" }; 13 14 // 文件版權信息 15 var copyright = DateTime.Now.Year + " xxxx Enterprises All Rights Reserved"; 16 var version = Environment.Version; 17 var author = "auto generated by T4"; 18 19 var manager = Manager.Create(Host, GenerationEnvironment); 20 var entities = EntityHelper.GetEntities(connectionString, database); 21 22 foreach(Entity entity in entities) 23 { 24 manager.StartNewFile(entity.EntityName + ".cs"); 25 #> 26 //----------------------------------------------------------------------- 27 // <copyright file=" <#= entity.EntityName #>.cs" company="xxxx Enterprises"> 28 // * Copyright (C) <#= copyright #> 29 // * version : <#= version #> 30 // * author : <#= author #> 31 // * FileName: <#= entity.EntityName #>.cs 32 // * history : Created by T4 <#= DateTime.Now #> 33 // </copyright> 34 //----------------------------------------------------------------------- 35 using System; 36 <# if(serviceModel) 37 { 38 #> 39 using System.Runtime.Serialization; 40 <# 41 } 42 #> 43 44 namespace Console4Test 45 { 46 /// <summary> 47 /// <#= entity.EntityName #> Entity Model 48 /// </summary> 49 [Serializable] 50 <# if(serviceModel) 51 { 52 #> 53 [DataContract] 54 <# 55 } 56 #> 57 public class <#= entity.EntityName #> 58 { 59 <# 60 for(int i = 0; i < entity.Fields.Count; i++) 61 { 62 if(i ==0){ 63 #> /// <summary> 64 /// <#= entity.Fields[i].Comment #> 65 /// </summary> 66 <# if(serviceModel) 67 { 68 #> 69 [DataMember] 70 <# 71 } 72 #> 73 public <#= entity.Fields[i].Type #> <#= entity.Fields[i].Name #> { get; set; } 74 <# 75 } 76 else{ 77 #> 78 /// <summary> 79 /// <#= entity.Fields[i].Comment #> 80 /// </summary> 81 <# if(serviceModel) 82 { 83 #> 84 [DataMember] 85 <# 86 } 87 #> 88 public <#= entity.Fields[i].Type #> <#= entity.Fields[i].Name #> { get; set; } 89 <# } 90 } 91 #> 92 } 93 } 94 <# 95 manager.EndBlock(); 96 } 97 98 manager.Process(true); 99 #> TextTemplate

 

  至此,已基本實現。在需要執行的模板裡按下Ctrl+S,它就會執行一遍。

  裡面有些寫死的東西,可以調整到配置文件或其他地方。比如是否是WCF模型,如果是的話會自動加上[DataMember]等屬性。具體格式等可自行擴展。

  

 

 

 

  

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved