再來看一次取得員工資料的範例程式碼。
public DataSet GetEmployeeData(string empAccount) { IDataAccessCommand cmd = DataAccessCommandFactory.GetDataAccessCommand(DBs.MainDB); spEmployee_GetData cmdInfo = new spEmployee_GetData() { EmpAccount = empAccount }; DataSet ds = cmd.ExecuteDataset(cmdInfo); dbErrMsg = cmd.GetErrMsg(); return ds; } public class spEmployee_GetData : IDataAccessCommandInfo { // DataAccessCommand 會使用欄位變數當做 SqlParameter 的產生來源(使用名稱、值);屬性不包含在其中。 // 輸出參數請加上屬性 [OutputPara] public string EmpAccount; public CommandType GetCommandType() { return CommandType.StoredProcedure; } public string GetCommandText() { return "dbo.spEmployee_GetData"; } }
Line 3 的 cmd 取得的物件是 new DataAccessCommand(DBs.MainDB);
Line 9 的 cmd.ExecuteDataset(cmdInfo); 請看下方 DataAccessCommand.ExecuteDataset 活動圖。
DataAccessCommand.ExecuteDataset 活動圖 |
1.從指令資訊物件取得指令碼與參數資料並且暫存:
取得指令類型、指令碼以及使用 Reflection 取得物件的所有參數名稱與參數值的程式碼片段如下,
CommandType cmdType = cmdInfo.GetCommandType(); string cmdText = cmdInfo.GetCommandText(); //動態取得物件的公用欄位 List<SqlParaInfo> paraInfos = GenerateSqlParaInfos(cmdInfo); protected List<SqlParaInfo> GenerateSqlParaInfos(IDataAccessCommandInfo cmdInfo) { //動態取得物件的公用欄位 FieldInfo[] fields = cmdInfo.GetType().GetFields(); List<SqlParaInfo> paraInfos = new List<SqlParaInfo>(); if (fields.Length > 0) hasParameters = true; foreach (FieldInfo field in fields) { object[] outputAttrs = field.GetCustomAttributes(typeof(OutputParaAttribute), false); object fieldValue = null; fieldValue = field.GetValue(cmdInfo); bool isOutputPara = false; if (outputAttrs.Length > 0) { isOutputPara = true; hasOutputParameters = true; } paraInfos.Add(new SqlParaInfo() { Name = field.Name, Value = fieldValue, IsOutput = isOutputPara, ParaType = field.FieldType, ParaFieldInfo = field }); } return paraInfos; }
Line 1, 2: 這裡使用 IDataAccessCommandInfo 定義的 GetCommandType(), GetCommandText() 取得指令類型與指令碼。
Line 4: 從 GenerateSqlParaInfos(cmdInfo) 取回暫存的自訂類別參數清單。
Line 9: 利用 Reflection,從 cmdInfo.GetType().GetFields() 取回指令資訊物件裡的所有公用欄位資訊 FieldInfo。
Line 15~37: 從每一個 FieldInfo 抓出欄位名稱、欄位值以及欄位型別等資料,暫存至 SqlParaInfo 類別的參數清單。
Line 17, 23: 查詢這個欄位是否有寫上自訂屬性 [Output],有的話代表它是輸出型的參數。
Line 20, 31, 34: field.GetValue(cmdInfo) 取回欄位的值。field.Name 取回欄位名稱。field.FieldType 取回欄位型別。
2.從 IDataAccessSource 取得連線物件並開啟連線:
使用 IDataAccessSource.CreateConnectionInstanceWithOpen() 取回一個 SqlConnection 物件,同時已使用 SqlConnection.Open() 連線至資料庫。
3.從暫存的參數資料清單轉出一份 SqlParameter 參數清單:
SqlParameter[] commandParameters = GenerateCommandParameters(paraInfos); protected SqlParameter[] GenerateCommandParameters(List<SqlParaInfo> paraInfos) { List<SqlParameter> sqlParas = new List<SqlParameter>(); foreach (SqlParaInfo paraInfo in paraInfos) { SqlParameter sqlPara = new SqlParameter("@" + paraInfo.Name, paraInfo.Value); if (paraInfo.IsOutput) sqlPara.Direction = ParameterDirection.Output; paraInfo.SqlPara = sqlPara; sqlParas.Add(sqlPara); } return sqlParas.ToArray(); }
Line 1: 將暫存的參數資料清單 paraInfos 轉出一份 SqlParameter 參數清單 commandParameters。
Line 9: 將參數名稱冠上 @ 前置符號做為 SqlParameter 要送去資料庫的參數名稱,例如 EmpAccount 就會以 @EmpAccount 傳送給資料庫。參數值從 paraInfo.Value 取得。
Line 11, 12: 指定是否為輸出型的參數。
Line 14: 把產生的 SqlParameter 物件也記錄在暫存參數清單中,之後要用來取回指令執行後的輸出型參數值。
4.用 log4net 記錄要執行的指令碼與參數資訊:
LogSql(cmdText, commandParameters); protected void LogSql(string commandText, params SqlParameter[] commandParameters) { if (Logger.IsDebugEnabled) { try { string separator = ", "; StringBuilder sbParams = new StringBuilder(500); foreach (SqlParameter tempParam in commandParameters) { string tempValue = null; if (tempParam.Value == null) { tempValue = "(null)"; } else if (Convert.IsDBNull(tempParam.Value)) { tempValue = "(DBNull)"; } else { tempValue = tempParam.Value.ToString(); } switch (tempParam.Direction) { case ParameterDirection.Output: case ParameterDirection.InputOutput: sbParams.Append("output "); break; } sbParams.AppendFormat("{0}={1}", tempParam.ParameterName, tempValue); sbParams.Append(separator); } int separatorLen = separator.Length; if (sbParams.Length >= separatorLen) { sbParams.Remove(sbParams.Length - separatorLen, separatorLen); } Logger.DebugFormat("sql: {0}; params: {1};", commandText, sbParams.ToString()); } catch (Exception ex) { Logger.Error("params SqlParameter", ex); } } }
Line 1: 記錄要執行的指令碼與參數資訊。
Line 5: 當 log4net 的 <level> 設定值為 DEBUG 時才會記錄。
儲存的 sql log 內容如下,
sql: dbo.spEmployee_UpdateLoginInfo; params: @EmpAccount=admin, @ThisLoginIP=127.0.0.1;
sql: dbo.spEmployee_GetData; params: @EmpAccount=admin;
if (hasParameters) { ds = SqlHelper.ExecuteDataset(conn, cmdType, cmdText, commandParameters); } else { ds = SqlHelper.ExecuteDataset(conn, cmdType, cmdText); }
SqlHelper 是微軟在 2004 年左右,放在 Microsoft.ApplicationBlocks.Data 裡面的一個好用的工具類別,它把 SqlCommand, SqlDataAdapter, SqlDataReader 常用的功能包裝成一系列的方法。若用來執行 SP 的話,還可以寫成 SqlHelper.ExecuteDataset(conn, "spName", pa1, pa2, pa3),它會自動幫忙把 pa1, pa2, pa3 的值依順序放置 SP 的對應參數裡,對於長期使用 SP 的我來說,實在是太方便了,也因此用到現在。
這裡使用的是 SqlHelper.ExecuteDataset()。
* Data Access Application Block for .NET v2 下載位置
* 這個版本是 2.0,印象中更舊的版本 (v1.x) 還沒支援可傳入 SqlTransaction 的方法,若有需要交易功能的人請多加注意。
6.將 SqlParameter output 參數值寫回指令資訊物件同名參數:
if (hasOutputParameters) { UpdateOutputParameterValuesOfSqlParaInfosFromSqlParameter(paraInfos, cmdInfo); } protected void UpdateOutputParameterValuesOfSqlParaInfosFromSqlParameter(List<SqlParaInfo> paraInfos, IDataAccessCommandInfo cmdInfo) { foreach (SqlParaInfo paraInfo in paraInfos) { if (paraInfo.IsOutput) { FieldInfo outputParaFieldInfo = paraInfo.ParaFieldInfo; outputParaFieldInfo.SetValue(cmdInfo, paraInfo.SqlPara.Value); } } }
Line 1, 3: 如果這次的參數中有輸出型參數的話才要把 SqlParameter 輸出型參數值寫回指令資訊物件同名參數。
Line 12, 13: paraInfo.SqlPara 為執行前暫存的 SqlParameter,paraInfo.ParaFieldInfo 為執行前暫存的 FieldInfo,也就是指令資訊物件的成員變數 Reflection 欄位資訊,這一行程式將 SqlParameter 的值寫回指令資訊物件的同名成員變數。
7.使用 IDataAccessSource.CloseConnection 關閉連線:
統一使用 IDataAccessSource.CloseConnection(conn); 關閉連線,減少 null 判斷的重覆程式碼。
public void CloseConnection(IDbConnection conn) { if (conn == null) return; conn.Close(); }
其他 DataAccessCommand 提供的執行指令方法還有這些
/// <summary> /// 執行指令 /// </summary> bool ExecuteNonQuery(IDataAccessCommandInfo cmdInfo); /// <summary> /// 執行指令並取回第一個欄位值 /// </summary> /// <param name="errCode">做為錯誤碼的值</param> T ExecuteScalar<T>(IDataAccessCommandInfo cmdInfo, T errCode); /// <summary> /// 執行指令並取回 DataReader /// </summary> IDataReader ExecuteReader(IDataAccessCommandInfo cmdInfo, out SqlConnection connOut);
它們的運作方式和本篇介紹的 ExecuteDataset() 大同小異,最大的差別是在使用 SqlHelper 呼叫不同的對應方法。
若需要 DataAccessCommand 原始碼請看 DataAccessCommand.cs
沒有留言:
張貼留言