A Better DataReader for C# 2.0

3/25/2009

在使用 ADO.NET 的 DataReader 來讀取欄位資料時,常常要寫很多判斷欄位值是否為 DBNull 的程式碼,例如:

SqlConnection cn = new SqlConnection("連線字串");
SqlCommand cmd = new SqlCommand("SELECT * FROM ...", cn);
SqlDataReader rdr = cmd.ExecuteReader();
while (rdr.Read())
{
if (rdr.IsDBNull(rdr.GetOrdinal("BIRTHDAY")))
{
Response.Write("");
}
else
{
Response.Write(Convert.ToDateTime(rdr["BIRTHDAY"], "yyyy-MM-dd"));
}
}
若不先判斷欄位值是否為 DBNull,程式執行時就會出現資料轉換失敗的 exception。如果能這樣寫就方便多了:
 SqlConnection cn = new SqlConnection("連線字串");
SqlCommand cmd = new SqlCommand("SELECT * FROM ...", cn);
BetterDataReader rdr = new BetterDataReader(cmd.ExecuteReader());
while (rdr.Read())
{
Response.Write(rdr.GetDateTimeStr("BIRTHDAY"));
}
這裡的 BetterDataReader 是修改自 Steve Michelotti 的 Nullable Data Readers,它本身雖然也實作了 IDataReader 介面,但大部分的實作方法都是直接呼叫外界傳入的 DataReader 物件的既有方法,同時再增加我們需要的方法。換句話說,BetterDataReader 只是一個簡單的 DataReader 轉換器(adapter)而已,它使用 wrapper(而非繼承)的方式來補強既有類別不足的地方,主要原因是既有的 DataReader 類別並不允許繼承。這是 C# 2.0 的解法,如果是 C# 3.0,就可以用 extension methods,這樣在撰寫程式時就更直覺了。

在剛才的範例程式中,GetDateTimeStr 方法會將你指定的日期欄位值轉換成字串傳回,若欄位值為 DBNull,則傳回空字串。這個方法便可以省掉每次判斷 DBNull 的瑣碎工作。

以下是 BetterDataReader 的部分原始碼:

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using Huanlin.Helper;

namespace Huanlin.Data
{
/// <summary>
/// 此類別是 DataReader 物件的簡單包裝,主要在解決欄位值為 DBNull 的問題,並增加一些方便的取值方法。
/// </summary>
public class BetterDataReader : IDataReader
{
#region Private Fields

IDataReader reader;

/// <summary>
/// Delegate to be used for anonymous method delegate inference
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="name"></param>
/// <returns></returns>
private delegate T Conversion<T>(int ordinal);

#endregion

#region Private Methods

/// <summary>
/// This generic method will be call by every interface method in the class.
/// The generic method will offer significantly less code, with type-safety.
/// Additionally, the methods can you delegate inference to pass the
/// appropriate delegate to be executed in this method.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="ordinal">Column index.</param>
/// <param name="convert">Delegate to invoke if the value is not DBNull</param>
/// <returns></returns>
private Nullable<T> GetNullable<T>(int ordinal, Conversion<T> convert) where T : struct
{
Nullable<T> nullable;
if (reader.IsDBNull(ordinal))
{
nullable = null;
}
else
{
nullable = convert(ordinal);
}
return nullable;
}

#endregion

#region Constructors

/// <summary>
/// 建構函式。
/// </summary>
/// <param name="dataReader"></param>
public BetterDataReader(IDataReader dataReader)
{
reader = dataReader;
}

#endregion

#region IDataReader Members
// (略)
#endregion

#region IDisposable Members
// (略)
#endregion


#region IDataRecord Members

public DateTime GetDateTime(int i)
{
return reader.GetDateTime(i);
}

public DateTime GetDateTime(string name)
{
return this.reader.GetDateTime(reader.GetOrdinal(name));
}

/// <summary>
/// 取得可為 NULL 的 DateTime 物件。
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public Nullable<DateTime> GetNullableDateTime(int index)
{
return GetNullable<DateTime>(index, GetDateTime);
}

/// <summary>
/// 取得可為 NULL 的 DateTime 物件。
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public Nullable<DateTime> GetNullableDateTime(string name)
{
return GetNullableDateTime(reader.GetOrdinal(name));
}
/// <summary>
/// 傳回格式化的日期時間字串。若欄位值為 NULL,則傳回空字串。
/// </summary>
/// <param name="name">欄位名稱。</param>
/// <param name="format">格式化字串。</param>
/// <returns>日期時間字串。</returns>
public string GetDateTimeStr(string name, string format)
{
Nullable<DateTime> dt = GetNullableDateTime(name);
if (dt.HasValue)
{
return dt.Value.ToString(format);
}
return "";
}

/// <summary>
/// 傳回格式化的日期時間字串。
/// </summary>
/// <param name="name">欄位名稱。</param>
/// <returns>日期時間字串。</returns>
public string GetDateTimeStr(string name)
{
return GetDateTimeStr(name, DateTimeHelper.DateTimeFormat);
}

/// <summary>
/// 傳回格式化的日期字串。若欄位值為 NULL,則傳回空字串。
/// </summary>
/// <param name="name">欄位名稱。</param>
/// <returns>日期時間字串。</returns>
public string GetDateStr(string name)
{
return GetDateTimeStr(name, DateTimeHelper.DateFormat);
}

public int GetInt32(int i)
{
if (reader.IsDBNull(i))
return 0;
return reader.GetInt32(i);
}

public int GetInt32(string name)
{
return this.GetInt32(reader.GetOrdinal(name));
}

/// <summary>
/// 若欄位值為 NULL,則傳回預設值。
/// </summary>
/// <param name="name">欄位名稱。</param>
/// <param name="defaultValue">預設值。</param>
/// <returns>欄位值。</returns>
public int GetInt32(string name, int defaultValue)
{
int index = reader.GetOrdinal(name);
if (reader.IsDBNull(index))
{
return defaultValue;
}
return reader.GetInt32(index);
}

public string GetString(int i)
{
if (reader.IsDBNull(i))
return "";
return reader.GetString(i);
}

public string GetString(string name)
{
return this.GetString(reader.GetOrdinal(name));
}

/// <summary>
/// 若欄位值為 NULL 或空字串(包含 Tab、換行字元),則傳回預設值。
/// </summary>
/// <param name="name">欄位名稱。</param>
/// <param name="defaultValue">預設值。</param>
/// <returns>欄位值。</returns>
public string GetString(string name, string defaultValue)
{
string value = this.GetString(name);
if (StrHelper.IsEmpty(value))
{
return defaultValue;
}
return value;
}
#endregion
}
}

林以亮<翻譯的理論與實踐>

3/22/2009
在《林以亮論翻譯》的第一篇<翻譯的理論與實踐>中,他把林語堂的翻譯三原則略加修正為:

一個翻譯者所應有的條件應該是:(一)對原作的把握;(二)對本國文字的操縱能力;(三)經驗加上豐富的想像力。 (p.10)

《物件導向分析設計與應用》未付印的譯序

3/13/2009
物件導向分析設計與應用》沒有譯序,而這篇,應可算是未付印的譯序吧。
在翻譯過程中就開始寫譯序,似乎成了我的習慣。每當有一些想法時就寫一點、改一點,等到整本書譯完,譯序也差不多完成了。但這次我並沒有把譯序交給出版社,一方面,本書作者已是大師級人物,讀者可能沒興趣再看一些錦上添花的推薦文,或翻譯甘苦談之類的碎碎念。另一方面,貼在部落格上比較方便修改,隨時反映新的想法。

一點提醒

雖然作者的名氣響亮,但每個人的需求和口味不同,建議您先看看試讀章節(包含目錄、序、和第一章),考慮一下這本書是不是「你的菜」。比如說,有些人可能會覺得這本書的參考文獻太「豐富」、學術味道太濃、UML 語法不夠完整詳盡、案例跟自己碰到的專案類型相差太遠(而無法直接依樣畫葫蘆)、譯筆太差、當枕頭太硬......等等。

其實光看厚度也知道,這本書不是兩三下就能消化完的,而且文字風格也絕對不會像 Head First 系列書籍那樣輕鬆詼諧。願意嘗試「啃」這本書的人,我想應該是對物件導向技術有相當的興趣與學習熱誠吧。那麼,我也樂意在這裡野人獻曝,提供一點個人的小小心得。

本書梗概

本書的架構主要分成三個部分,即概念篇、方法篇、和應用篇。以下簡單說一下各篇的內容概要。

開發過軟體專案的人應該都會同意,看似簡單的需求,最後都可能演變成複雜的大系統(希望不是大災難)。因此,為了解決許多複雜的軟體設計問題,開發人員必須化繁為簡,讓使用者能夠以簡馭繁;而達成此目標的一種有效策略,便是分而治之、各個擊破。那麼,分而治之的「分」指的是什麼呢?作者在書中便從「演算法分解」與「物件導向分解」兩種方法的比較作為切入點。瞭解物件導向分解的原理和優點之後,再來便是分類的哲學與物件模型的介紹。以上這些,大概就是第一篇所要闡述的重點。當然,分得好,還須合得妙;若只是將系統拆解成各自分散的個體,還是成不了一個完整運作的系統。至於怎麼合,又涉及了 UML 各種圖形以及模式(patterns)的搭配運用,是另一層次的議題了(本書的第三篇其實已經有運用一些 design patterns)。既名為概念篇,其內容自然是以物件導向的基本理論、觀念、原則為主。就這個部分來說,讀者可能容易覺得枯燥,或者學術味道比較濃。作者大概也有想到這點吧,所以在書中適時加入一些與主題有關的卡通插畫,讓嚴肅的主題增添一點趣味。

第二篇(「方法」篇)包含 UML 圖形表示法(第 5 章)、開發流程(第 6 章)、以及一些實務作法(第 7 章)。UML 表示法的部分比較像參考手冊,已熟悉 UML 2 的讀者大可先跳過這個部分,等到需要時再回頭查閱無妨。如果你是方法論專家,或者是開發塑模工具的設計師,可能會覺得書中的 UML 表示法不夠詳盡,因為這裡介紹的 UML 表示法主要是針對一般的 SA/SD/PG 來寫,而這些內容對於一般軟體系統的分析設計來說已經相當夠用了。一般比較常用的,大概也就是所謂的 UML 三劍客(使用案例圖、類別圖、循序圖)。如果需要塑模事務流程,自然得再加上活動圖--姑且稱它們為 UML 四君子吧 :)

第三篇(「應用」)全是物件導向分析設計(OOAD)的個案探討,分為五章,每一章都是一個不同的案例。

在學習一項技術時,如果有搭配範例說明,通常會比較有感覺,而如果範例正好跟自己碰到的問題類似,那更好,說不定還可以透過複製再修改的方式直接套用。因此,範例對個人的幫助有多大,就牽涉到範例的大小與其涉及的問題領域了。簡單如自動櫃員機(ATM)的案例,雖然容易理解,可是在實際開發 OOAD 專案時,可能會發現原來還會碰到那麼多大大小小的問題、那麼多設計決策要取捨,因而不知所措。此時你可能會希望找到更貼近自己手邊問題的範例,以及一些更明確的開發指引和建議,例如:剛開始進行一個新專案時,軟體的架構要怎麼規劃、子系統和套件應如何切割、使用案例描述該怎麼寫、如何抓類別等等。本書第三篇的用意即在透過實際的案例展示 OOAD 的開發流程,並提供一些實務面的分析設計技巧。

要提醒的是,這些案例的專案規模都不小,如衛星導航、鐵路交通管理、氣象監測等,若要把這些專案的設計理念和開發細節全交代清楚,恐怕每個個案都可以寫成一本書了。所以在看這些範例時,最好把焦點放在如何從這些去蕪存菁的個案探討當中體會作者想要表達的東西,同時思考作者為何要這麼設計(優缺點在哪裡,有沒有更好的設計方法),以及找到對我們實際開發有用的部分。

大概就這樣吧,軟體設計的世界裡沒有絕對正確、一體適用的答案,希望這本書對您有幫助!

WCF 入門練習

3/13/2009
開發 WCF 應用程式基本上有三項工作:
  • 撰寫 WCF 服務,這包括定義服務介面,以及撰寫實作服務介面的類別。
  • 部署 WCF 服務。
  • 撰寫 WCF 用戶端程式。

Logging Application Block 概念圖

3/11/2009

補一張 EL Logging Application Block 的簡易概念圖:




MSDN 網站上也有一張比較詳細的類別圖可參考:Design of the Logging Application Block

Logging Application Block (三):撰寫自訂 Log 監聽器

3/10/2009
儘管 EL4 的 Logging Application Block(以下簡稱 LAB)已經提供很多種 trace listeners,但有時候還是無法完全符合我們的需要,例如上一篇提到的,Email Trace Listener 並未支援 SMTP 伺服器身份驗證的功能。此時就得自己寫一個 log 監聽器(sink)了。


Logging Application Block (二):透過 E-mail 寄送 log 訊息

3/10/2009
本文將示範如何在 ASP.NET 網站中透過 Logging Application Block 記錄 log 訊息,包括:
  • 使用 Filter 和 Severity 屬性的設定來篩選想要記錄的訊息。
  • 將 ASP.NET 網站未處理的 exception 輸出至 log。
  • 輸出的 log 訊息將透過 STMP 寄送至指定的 e-mail 信箱。

Logging Application Block (一):入門教學

3/09/2009
摘要:本文說明 Enterprise Library 的 Logging Application Block 的基本用法,其中包含兩個 step-by-step 練習,分別示範將 log 訊息輸出至 Windows 事件檢視器以及可循環使用的純文字 log 檔案。


BugTracker.NET 簡介

3/06/2009
原本個人常用的 issue tracking system 是 BugNET,因為安裝簡單、免費、功能也不差,像是:切分專案與子系統、問題的嚴重度、問題分類、統計圖表(稍嫌陽春)、E-mail 通知(這個真的很必要)等都有。這兩天又試了另一款工具:BugTracker.NET
同為 open source 軟體,我覺得二者的功能差不多,很難說哪一個一定比較好用。BugTracker.NET 還提供了自訂欄位,以及 E-mail 轉成 bug entry 的功能,此功能可以將 user 透過 e-mail 反映的問題轉入系統。

此外,BugTracker.NET 比較吸引我的部分,是它還能夠與 Subversion 整合。我還沒試這項功能,不過依官方文件的描述,它有提供 Subversion 的 commit hook,如此一來,每當程式設計師送交檔案時,如果有在 checkin comment 中填入 bug ID,那個 hook 程式就會去更新 BugTracker.NET 資料庫中對應的 bug entry。這樣的整合可以免去開發人員手動修改問題狀態的手續,讓整個問題處理的流程更順暢、更省力。

安裝步驟很簡單:
  1. 下載 BugTracker.NET(我用的是 v3.1.3),把壓縮包解開到某個資料夾,再進入 IIS 管理員建立一個虛擬目錄指向該資料夾,身分驗證方式設定為啟用匿名存取。
  2. 假設你建立的虛擬目錄名稱是 btnet,就用 IE 瀏覽網址: http://localhost/btnet/install.aspx 。這個網頁會告訴你怎麼安裝,如下圖所示:


此安裝頁面還有提供建立資料庫的功能,不過我是這麼做的:先自己手動建立資料庫(名稱隨你訂,例如:btnet_db),接著依畫面上的步驟 2~4 進行就完成安裝設定了。

當然,你的機器上必須有安裝 SQL Server(Express 版也行)、IIS、和 .NET Framework 2.0 或更新的版本。

安裝完成後,接下來的步驟基本上和 BugNET 差不多:建立專案、建立使用者帳戶、設定權限等等,這些動作都挺直覺,就不細說了。這裡有示範網站可以參考:http://ifdefined.com/btnet/bugs.aspx
技術提供:Blogger.