[.NET]Entity Generator by T4 with Table Description and Column Description

  • 3590
  • 0
  • 2012-12-28

[.NET]Entity Generator by T4 with Table Description and Column Description

前言

在之前的文章:[.NET]RowMapper with Entity Generated by T4 and Mapping Nested Entity 中,有針對原本的 RowMapper 與使用 T4 來當做 Entity Generator 進行改善。

最近筆者開發系統時,又碰到了一個讓人覺得繁瑣的動作,也就是在設計 Entity 時,需要針對 class 與 property 補上 summary 說明。(也就是 C# document )

在面對過去前人所設計的欄位時,往往欄位命名不容易透過直覺就能了解, 就像下圖這樣的情況,實在不容易理解:

image相同的

而部門內其實也有開發一個內部網頁,只需輸入 DB 與要查詢的 table 名稱,即可查到對應的欄位說明。如下圖所示:

ERP table query

由於一些欄位命名實在很像,加上這個複製貼上的動作實在很囉嗦兼容易出錯,正所謂「科技始終來自於人性」,內部網頁做的到,那 T4 也做的到,為什麼不乾脆在自動產生 Entity 的時候,就把 Column 的描述,直接產生在 class 與 property 的 summary 中呢?

所以,針對原本的 T4 ,又進行了一次改版。

 

Table Description 與 Column Description 相關的 SQL Statement

想要讓 T4 自動產生 table/column description 到 class/property 的 summary 中,那麼要先知道該怎麼查詢出 table description 與 column description 。

要查詢出 column 的相關資訊,請參考下面這段 SQL statement :

			SELECT  	c.name AS [column],    		
    					cd.value AS [column_desc],    		
    					c.isnullable AS [isNullable]   		    		
			FROM    	sysobjects t WITH(nolock)
			INNER JOIN  syscolumns c WITH(nolock)
				ON		c.id = t.id
			LEFT OUTER JOIN sys.extended_properties cd WITH(nolock)
				ON		cd.major_id = c.id
				AND		cd.minor_id = c.colid
				AND		cd.name = 'MS_Description'
			WHERE t.type = 'u'
			and t.name='@tableName'
			ORDER BY    t.name, c.colorder;

查詢出來的結果,如下圖所示:

column description

而要查詢 table description 則透過下面這段 SQL statement :

			SELECT	top 1 
					t.name AS [table_name],
					td.value AS [table_desc]
			FROM    	sysobjects t WITH(nolock)
			INNER JOIN sys.extended_properties td WITH(nolock)
				ON		td.major_id = t.id
				AND 	td.minor_id = 0
				AND		td.name = 'MS_Description'
			WHERE t.type = 'u'
			and t.name='@tableName';

查詢出來的結果,如下圖所示:

image

 

T4 內容

查到 table description 與 column description 的資料後,要放進去 T4 也就不是什麼難事了。改版後的 T4 如下:

<#@ template language="C#" debug="True" hostspecific="True" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Data.DataSetExtensions" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Data" #>
<#@ assembly name="System.xml" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Linq" #>

using System;

namespace YourNamespace 
{        
		<#  //修改connection string
			//todo, 請修改為你的connectionstring
			string connectionString = "你的connectionstring";
			SqlConnection conn = new SqlConnection(connectionString); 
			conn.Open(); 
			
			//todo, 請修改為你的tableName
			var tableName = "你的table名稱";

			//如果需要database中全部table,則使用conn.GetSchema("Tables")即可
			string[] restrictions = new string[4];
			restrictions[1] = "dbo";
			//修改table名稱
			restrictions[2] = tableName;
			DataTable schema = conn.GetSchema("Tables", restrictions);
			
			string selectQuery = @"
			SELECT top 1 * from  @tableName WITH(nolock);

			SELECT  	c.name AS [column],    		
    					cd.value AS [column_desc],    		
    					c.isnullable AS [isNullable]   		    		
			FROM    	sysobjects t WITH(nolock)
			INNER JOIN  syscolumns c WITH(nolock)
				ON		c.id = t.id
			LEFT OUTER JOIN sys.extended_properties cd WITH(nolock)
				ON		cd.major_id = c.id
				AND		cd.minor_id = c.colid
				AND		cd.name = 'MS_Description'
			WHERE t.type = 'u'
			and t.name='@tableName'
			ORDER BY    t.name, c.colorder;

			SELECT	top 1 
					t.name AS [table_name],
					td.value AS [table_desc]
			FROM    	sysobjects t WITH(nolock)
			INNER JOIN sys.extended_properties td WITH(nolock)
				ON		td.major_id = t.id
				AND 	td.minor_id = 0
				AND		td.name = 'MS_Description'
			WHERE t.type = 'u'
			and t.name='@tableName';"; 

			SqlCommand command = new SqlCommand(selectQuery,conn); 
			SqlDataAdapter ad = new SqlDataAdapter(command); 
			System.Data.DataSet ds = new DataSet(); 						

			foreach(System.Data.DataRow row in schema.Rows) 
			{ 				
				command.CommandText = selectQuery.Replace("@tableName",row["TABLE_NAME"].ToString()); 
				ad.Fill(ds);

				var isExistData = ds.Tables[2].Rows.Count > 0 ;
				var tableDescription = isExistData ? ds.Tables[2].Rows[0]["table_desc"].ToString() : "";				
			#>             						
			/// <summary>			
			/// <#= tableDescription #>
			/// mapping table name: <#= row["TABLE_NAME"].ToString() #>
			/// </summary>
			public class <#= row["TABLE_NAME"].ToString() #>                            
			{
				<#                 										
					foreach (DataColumn dc in ds.Tables[0].Columns)
					{					
						var columnDefinition = ds.Tables[1].AsEnumerable().Where(x => x["column"].ToString() == dc.ColumnName).FirstOrDefault();						
						var columnDescription = columnDefinition["column_desc"].ToString();
						var isAllowNull = columnDefinition["isNullable"].ToString() == "1";
				#>
/// <summary>
				/// <#= columnDescription #>
				/// </summary>
				[DBColumnMapping("<#= dc.ColumnName #>")]                        
				<# if(isAllowNull && dc.DataType.Name != "String"){ #>
public Nullable<<#= dc.DataType.Name #>> <#= dc.ColumnName #>  { get; set; }                                        
				<#     }                
				else
				{ #>
public <#= dc.DataType.Name #> <#= dc.ColumnName #>  { get; set; }
				<#}
				#>                            	
				<#    }                 #>                                
			}                            
<#    
			}  
			conn.Close();			
#>
}

說明如下:

  1. 由於要在 T4 中使用 LINQ 語法,所以要讓 T4 參考 LINQ 相關的 DLL 以及 using 對應的 namespace。在 T4 中,要參考某一顆 DLL ,要透過 assembly 來加入參考,要 using namespace ,則是透過 import 。
  2. 這三段 SQL 以及查詢相關的 description 其實可以 tuning 的更有效率,不過這邊就留給讀者們自行改善囉,因為 T4 執行的次數不多,基本上不會對 DB 造成太多負擔。

 

這次有改善先前 T4 設計幾個問題:

  1. 先前查詢是直接使用 Select * From @tableName ,其實只是要 DataTable 的結構,根本不需要撈出所有資料,所以這一版加上 top 1 ,避免 table 資料過多時,花費過多不必要的資源。
  2. 先前 Query table 時沒有加上 with(Nolock) ,這一版把 nolock 加上去了。
  3. 最要命的是這一點,先前 connection open 之後,沒有呼叫 Close() ,這一版也補上去了。

最後產出的 Entity ,就會類似下圖(這邊 product table 沒有設定 table description ):

image

 

結論

就像筆者從之前到現在,一直提到的一句話:「每一次的複製貼上,都是扼殺自己成長的機會」,原本只是用來說明 DRY 原則,在設計物件內容時,一定要盡量避免相同的意義擁有重複的程式碼。

相同的,在一般開發流程中,太多重複性質的手動作業,只會消耗 developer 的 flow 能量,善用工具,了解工具,就可以客製化一些簡單的程式碼產生器,來幫助自己把時間花在更美好的事物上。

希望這一篇文章可以讓大家更了解 T4 可以幫助我們做哪些事,也希望這個 T4 範本可以協助更多 developer 擺脫弱型別的資料結構。即使不使用 ORM ,仍然可以使用強型別來當作溝通媒介。

 

補充

在 SQL Server 中,要設定 table description 與 column description ,要記得使用較高權限的帳號才能修改。

Column description 的設定,請在 table 上點選 [design] ,接著選取某一個欄位,在 Column Properties 中,編輯 [Description] 即可。請見下圖:

image

Table description 則在同一個畫面,開啟 Properties 視窗,裡面放的其實是 table 的 properties,一樣編輯 [Description] 即可。如下圖所示:

image


blog 與課程更新內容,請前往新站位置:http://tdd.best/