SqlCommand的ExecuteScaler的使用時機

SqlCommand的ExecuteScaler的使用時機

前一陣子有同事問我,SqlCommand的ExecuteScaler方法是做什麼用的?為什麼要用ExecuteScaler呢?怎麼不用ExecuteNonQuery就好了。是的,當然也沒有錯。不過ExecuteScaler還是有它的使用時機與一點點的價值存在的,因為ExecuteScaler早在9年前的.NET Framework 1.0的ADO.NET中就已經有這個方法了,到現在也依然存在。而當同事提出個疑問時,我當時的回答是這樣的,通常會使用ExecuteScaler方法的目的不外乎下面三種狀況:

  1. 只要傳回單一值,如SQL中常用的COUNT(*)、SUM(*) 、AVG(*)等聚合函數 或傳回IDENTITY值等。
  2. 精簡程式碼,因為我只要單一值傳回,所以我不要寫一大串的程式碼。
  3. 由於只需要傳回一個單一值需要有較好的效能,我不要再Fill 一個DataSet或是DataTable,這時ExecuteScaler就派上用場。

比如在第一種中,間我想要取回在Northwind資料庫中某位Customer的Orders數量,程式可能會這樣寫,同常可能寫一個共用的Scaler方法。

   1:  using System;
   2:  using System.Data;
   3:  using System.Data.SqlClient;
   4:  using System.Collections.Generic;
   5:  using System.Linq;
   6:  using System.Web;
   7:  using System.Configuration;
   8:   
   9:  /// <summary>
  10:  /// DAL 的摘要描述
  11:  /// </summary>
  12:  public class DAL
  13:  {
  14:      private void PrepareCommand(
  15:          ref SqlCommand cmd, 
  16:          SqlConnection conn, 
  17:          string cmdText, 
  18:          SqlParameter[] cmdParms)
  19:      {
  20:          if (conn.State != ConnectionState.Open)
  21:              conn.Open();
  22:          cmd.Connection = conn;
  23:          cmd.CommandText = cmdText;
  24:          cmd.CommandType = CommandType.Text;
  25:          if (cmdParms != null)
  26:          {
  27:              foreach (SqlParameter parm in cmdParms)
  28:                  cmd.Parameters.Add(parm);
  29:          }
  30:      }
  31:   
  32:      public object GetScaler(string SqlStatement, SqlParameter [] parames)
  33:      {
  34:          using (SqlConnection cnn = new SqlConnection(ConfigurationManager.ConnectionStrings["MSSQL"].ConnectionString))
  35:          {
  36:              SqlCommand cmd = new SqlCommand(SqlStatement, cnn);
  37:              try
  38:              {
  39:                  PrepareCommand(ref cmd, cnn, SqlStatement, parames);
  40:                  return cmd.ExecuteScalar();
  41:              }
  42:              catch (Exception ex)
  43:              {
  44:                  throw ex;
  45:              }
  46:              finally
  47:              {
  48:                  if (cnn.State != ConnectionState.Closed)
  49:                      cnn.Close();
  50:                  cnn.Dispose();
  51:              }
  52:          }
  53:      }
  54:   
  55:      public DAL()
  56:      {
  57:          //
  58:          // TODO: 在此加入建構函式的程式碼
  59:          //
  60:      }
  61:  }

 

如上程式非常的單存,其實就是一行cmd.ExecuteScalar 即取回我們要的結果。另外商業邏輯Orders.cs 中的SQL 敘述如下:

   1:  public class Orders
   2:  {
   3:      /// <summary>
   4:      /// 
   5:      /// </summary>
   6:      /// <param name="CustomerID"></param>
   7:      /// <returns></returns>
   8:      public string GetOrdersCountByCustomerID(string CustomerID)
   9:      {
  10:          string SqlStatement = @"select COUNT(*) AS COUNT_COL 
  11:  from Orders s 
  12:  where s.CustomerID=@CustomerID";
  13:          SqlParameter[] parameter = new SqlParameter[] { 
  14:              new SqlParameter("@CustomerID", SqlDbType.VarChar) 
  15:          };
  16:          parameter[0].Value = CustomerID;
  17:          return new DAL().GetScaler(SqlStatement, parameter).ToString();
  18:      }
  19:   
  20:      public Orders()
  21:      {
  22:          //
  23:          // TODO: 在此加入建構函式的程式碼
  24:          //
  25:      }
  26:  }

 

如上商業邏輯的程式,SQL敘述很單存的計算COUNT(*),所以一定會有一個傳回值,即使目前沒有任何資料,也會傳回0,但並不表示所有敘述都會有值傳回來。

因此需要注意的地方,如果你的SQL傳回的資料有可能是空值的時候,也就是說您查詢的SQL並不是COUNT 等聚合函數時,你是使用SUM、AVG 等,接回來的就不見得會有資料。此時接回的 object 不要就直接ToString() 以免直接出現 null reference的錯誤。

比如SQL 是為了抓身分證字號,可是這樣的敘述是有可能是沒有結果的空值 :


SELECT TOP 1 E.CHT_NAME FROM EMPLOYESS E WHERE E.AID=@AID

 

計算AVG 也是有可能得到NULL,因為筆數為0,這時計算不出結果時也是NULL,如下:


select Avg(s.Freight) AS AVG_COL from Orders s where s.CustomerID='任意錯誤的客戶編號'

 

而此時在.NET 的 Runtime下 executeScaler 回來會是null值。可是卻直接ToString() 回傳結果(如下紅色部分)。就會發生null reference,這是使用時須要注意的地方。

   1:      public string GetScaler(string SqlStatement, SqlParameter [] parames)
   2:      {
   3:          using (SqlConnection cnn = new SqlConnection(ConfigurationManager.ConnectionStrings["MSSQL"].ConnectionString))
   4:          {
   5:              SqlCommand cmd = new SqlCommand(SqlStatement, cnn);
   6:              try
   7:              {
   8:                  PrepareCommand(ref cmd, cnn, SqlStatement, parames);
   9:                  object o = cmd.ExecuteScalar();
  10:                  return o.ToString();
  11:              }
  12:              catch (Exception ex)
  13:              {
  14:                  throw ex;
  15:              }
  16:              finally
  17:              {
  18:                  if (cnn.State != ConnectionState.Closed)
  19:                      cnn.Close();
  20:                  cnn.Dispose();
  21:              }
  22:          }
  23:      }

 

這也是筆者接下之前系統時,在DAL中時經常看見的狀況。提供給各位參考。


 

簽名:

學習是一趟奇妙的旅程

這當中,有辛苦、有心酸、也有成果。有時也會有瓶頸。要能夠繼續勇往直前就必須保有一顆最熱誠的心。

軟體開發之路(FB 社團)https://www.facebook.com/groups/361804473860062/

Gelis 程式設計訓練營(粉絲團)https://www.facebook.com/gelis.dev.learning/


 

如果文章對您有用,幫我點一下讚,或是點一下『我要推薦,這會讓我更有動力的為各位讀者撰寫下一篇文章。

非常謝謝各位的支持與愛護,小弟在此位各位說聲謝謝!!! ^_^