在前一篇〈基於 .NET 平台的分層架構實戰(七)——資料存取層第一種實現:Access+SQL〉發布後,社群反饋熱烈(特別感謝「金色海洋」jyk 的建設性意見)。經歸納,初版程式碼存在三大問題:
Connection 物件未顯式關閉(雖執行期無功能性影響,但屬資源管理缺失)。DataReader 物件未關閉,易造成連線洩漏。針對前兩點,DataReader 關閉可在 finally 區塊處理;Connection 若在 DataReader 讀取期間關閉會導致讀取失敗,故暫保留由連線池管理。核心挑戰在於如何優雅地消除冗餘程式碼。
分析 DAL 操作,歸納為三類:
IList<T>。將三類操作的共同流程(開啟連線、建立命令、綁定參數、執行、資源釋放)抽離至 AccessDALHelper 三個泛型靜態方法,具體的「DataReader 轉實體」邏輯因實體型別不同而異,引入 Strategy 模式 解耦。
public interface IDataReaderToEntityStrategy<T>
{
T DataReaderToEntity(OleDbDataReader dataReader);
}
以 AdminInfo 為例,實作策略並採用 Singleton 模式:
public class AdminDataReaderToEntityStrategy : IDataReaderToEntityStrategy<AdminInfo>
{
private static AdminDataReaderToEntityStrategy singleInstance = null;
private AdminDataReaderToEntityStrategy() { }
public static AdminDataReaderToEntityStrategy GetInstance()
{
if (singleInstance == null) singleInstance = new AdminDataReaderToEntityStrategy();
return singleInstance;
}
public AdminInfo DataReaderToEntity(OleDbDataReader reader)
{
return new AdminInfo {
ID = (int)reader["ID"],
Name = (string)reader["Name"],
Password = (string)reader["Password"]
};
}
}
public sealed class AccessDALHelper
{
// 1. 非查詢操作
public static void ExecuteSQLNonQuery(string sql, OleDbParameter[] parameters) { ... }
// 2. 回傳 DataReader (供內部使用)
public static OleDbDataReader ExecuteSQLDataReader(string sql, OleDbParameter[] parameters) { ... }
// 3. 封裝非查詢邏輯
public static bool OperateNonData(string sql, OleDbParameter[] parameters)
{
try { ExecuteSQLNonQuery(sql, parameters); return true; }
catch { return false; }
}
// 4. 單一實體查詢
public static T OperateEntity<T>(string sql, OleDbParameter[] parameters, IDataReaderToEntityStrategy<T> strategy)
{
OleDbDataReader reader = ExecuteSQLDataReader(sql, parameters);
try {
if (!reader.HasRows) throw new Exception();
reader.Read();
return strategy.DataReaderToEntity(reader);
}
catch { return default(T); }
finally { reader.Close(); } // 關鍵:確保 DataReader 釋放
}
// 5. 實體集合查詢
public static IList<T> OperateEntityCollection<T>(string sql, OleDbParameter[] parameters, IDataReaderToEntityStrategy<T> strategy)
{
OleDbDataReader reader = ExecuteSQLDataReader(sql, parameters);
try {
if (!reader.HasRows) throw new Exception();
var list = new List<T>();
while (reader.Read()) list.Add(strategy.DataReaderToEntity(reader));
return list;
}
catch { return default(IList<T>); }
finally { reader.Close(); }
}
}
引入策略後,AdminDAL 方法縮減為單行委派呼叫:
public class AdminDAL : IAdminDAL
{
public bool Insert(AdminInfo admin)
=> AccessDALHelper.OperateNonData("INSERT INTO [TAdmin]([Name],[Password]) VALUES(@name,@password)",
new[] { new OleDbParameter("name", admin.Name), new OleDbParameter("password", admin.Password) });
public AdminInfo GetByID(int id)
=> AccessDALHelper.OperateEntity<AdminInfo>("SELECT * FROM [TAdmin] WHERE [ID]=@id",
new[] { new OleDbParameter("id", id) }, AdminDataReaderToEntityStrategy.GetInstance());
public IList<AdminInfo> GetAll()
=> AccessDALHelper.OperateEntityCollection<AdminInfo>("SELECT * FROM [TAdmin]", null, AdminDataReaderToEntityStrategy.GetInstance());
// Delete, Update, GetByNameAndPassword, GetByName 同理...
}
finally 區塊強制關閉 DataReader,杜絕連線洩漏。AccessDALHelper。此重構展示了如何在輕量級專案中套用經典設計模式,以最小代價達到專業級的程式碼品質。