1. 程式人生 > >.NET和SQL Server中“空值”辨析 (DBNull與Null的區別)

.NET和SQL Server中“空值”辨析 (DBNull與Null的區別)

對錶進行插入操作,如datetime和int型別的兩個欄位,都允許為null,
用“sqlcmd.Parameters.Add(“@t12”,tb12.Text)”引數繫結時。

datetime型別時,tb12.Text為空,插入成功,不報錯,檢視該值,卻為1900-01-01;int型別時,用同樣語句,tb12.Text為空,插入成功,不報錯,檢視該值,卻為0;

sqlcmd.Parameters.Add(new  SqlParameter("@t12",SqlDbType.DateTime));

sqlcmd.Parameters["@t12"].Value=tb12.Text;

執行上述語句時,tb12.Text為空插入則報錯。

sqlcmd.Parameters.Add(new SqlParameter("@t12",SqlDbType.DateTime));
if (tb12.Text.Length>0)
{
sqlcmd.Parameters["@t12"].Value=tb12.Text
}
else
{
sqlcmd.Parameters["@t12"].Value = System.DBNull.Value;

}

MSDN:

DBNull 類表示一個不存在的值

例如,在資料庫的表中,某一行的某列中可能不包含任何資料。即,該列被視為根本不存在,而不只是沒有值。

一個表示不存在的列的 DBNull 物件。

此外,COM 互操作使用 DBNull 類來區分 VT_NULL 變數(指示不存在的值)和 VT_EMPTY 變數(指示未指定的值)。

DBNull 型別是一個單獨的類,這意味著只有一個 DBNull 物件存在。

DBNull..::.Value 成員表示唯一的 DBNull 物件。

DBNull..::.Value 可用於將不存在的值顯式分配給資料庫欄位,但大多數 ADO.NET 資料提供程式在欄位沒有有效值時會自動分配 DBNull 值。

您可以通過將從資料庫欄位檢索到的值傳遞給 DBNull.Value.Equals 方法,確定該欄位值是否為 DBNull 值。然而,有些語言和資料庫物件提供一些方法,可以更容易地確定資料庫欄位值是否為 DBNull..::.Value。

這些方法包括 Visual Basic 的 IsDBNull 函式、Convert..::.IsDBNull 方法、DataTableReader..::.IsDBNull 方法和 IDataRecord..::.IsDBNull 方法。

請勿將面向物件的程式語言中的 nullNothingnullptrnull 引用(在 Visual Basic 中為 Nothing) 概念與 DBNull 物件混淆。

在面向物件的程式語言中,nullNothingnullptrnull 引用(在 Visual Basic 中為 Nothing) 表示不存在對某個物件的引用。

DBNull 則表示未初始化的變數或不存在的資料庫列。

實際上就是資料庫中的Null。

資料庫中如果要插入null值,如:像有些日期欄位,如果使用者沒有選擇日期,我們希望他保持NULL狀態。

下面的示例呼叫 DBNull.Value.Equals 方法,來確定聯絡人資料庫中的資料庫欄位是否具有有效值。

如果具有有效值,欄位值將被追加到在標籤中輸出的字串中。

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->private void OutputLabels(DataTable dt)
{
   string label; 

   // Iterate rows of table
   foreach (DataRow row in dt.Rows)
   {
      int labelLen;
      label = String.Empty;
      label += AddFieldValue(label, row, "Title");
      label += AddFieldValue(label, row, "FirstName");
      label += AddFieldValue(label, row, "MiddleInitial");
      label += AddFieldValue(label, row, "LastName");
      label += AddFieldValue(label, row, "Suffix");
      label += "\n";
      label += AddFieldValue(label, row, "Address1");
      label += AddFieldValue(label, row, "AptNo");
      label += "\n";
      labelLen = label.Length;
      label += AddFieldValue(label, row, "Address2");
      if (label.Length != labelLen)
         label += "\n";
      label += AddFieldValue(label, row, "City");
      label += AddFieldValue(label, row, "State");
      label += AddFieldValue(label, row, "Zip");
      Console.WriteLine(label);
      Console.WriteLine();
   }
}

private string AddFieldValue(string label, DataRow row, 
                             string fieldName) 
{                                
   if (! DBNull.Value.Equals(row[fieldName])) 
      return (string) row[fieldName] + " ";
   else
      return String.Empty;
}

DBNull在DotNet是單獨的一個型別 System.DBNull 。它只有一個值 DBNull.Value 。

DBNull 直接繼承 Object ,所以 DBNull 不是 string , 不是 int , 也不是 DateTime 。。。

但是為什麼 DBNull 可以表示資料庫中的字串,數字,或日期呢?

原因是DotNet儲存這些資料的類(DataRow等)都是以 object 的形式來儲存資料的。

對於 DataRow , 它的 row[column] 返回的值永遠不為 null , 要麼就是具體的為column 的型別的值 ,要麼就是 DBNull 。

所以 row[column].ToString() 這個寫法永遠不會在ToString那裡發生NullReferenceException。

DBNull 實現了 IConvertible 。 但是,除了 ToString 是正常的外,其他的ToXXX都會丟擲不能轉換的錯誤。

如:

這裡寫圖片描述


Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--> protected void Page_Load(object sender, EventArgs e)
    {
        DataTable dt = new DataTable();
        dt.Columns.Add(new DataColumn("name",typeof(string)));
        DataRow dr=dt.NewRow();
        dr["name"] = DBNull.Value;
        dt.Rows.Add(dr);
        Response.Write(dt.Rows[0][0].ToString());//可以正常輸出
        Response.Write(dt.Rows[0][0]);//可以正常輸出
    }

在 IDbCommand(OleDbCommand,SqlCommand…) 的ExecuteScalar的返回值中,情況可以這樣分析:

select 1 這樣返回的object1
select null 這樣返回的是DBNull.Value
select isnull(null,1) 返回的是 1
select top 0 id from table1 這樣返回的值是null  -->一行資料也沒有
select isnull(id,0) from table1 where 1=0 返回的值是null -->一行資料也沒有

這裡 ExecuteScalar 的規則就是,返回第一列,第一行的資料。如果第一列第一行不為空,那麼ExecuteScalar就直接對應的DotNet的值。如果有第一行,但是第一列為空,那麼返回的是 DBNull 。如果一行都沒有,那麼ExecuteScalar就返回null

規則就是這樣的。這裡容易犯的一個錯誤是,把ExecuteScalar返回DBNull與null的情況混淆,例如:

string username=cmd.ExecuteScalar().ToString();  //沒有任何一行資料時

除非你認為cmd執行後,肯定至少有一行資料,否則這裡就會出錯。

又或者 select id from usertable where [email protected] 這樣的sql語句,如果找不到記錄,那麼ExecuteScalar則會返回null,

所以千萬不要

int userid=Convert.ToInt32(cmd.ExecuteScalar());

或者你會這樣寫 SQL 語句:

select isnull(id,0) from usertable where [email protected]name 

但是 int userid=Convert.ToInt32(cmd.ExecuteScalar()); 依然會出錯,因為上面的語句不成立時,仍然是不返回任何行。

下面來看一些示例:

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--> using (SqlConnection conn = new SqlConnection(connStr))
        {
            conn.Open();
            SqlCommand comm = new SqlCommand("select id from Tab_Article where 1=0", conn);
            if (comm.ExecuteScalar() == null)
            {
                Response.Write("返回的是null");//output:返回的是null
            }
        }

        using (SqlConnection conn = new SqlConnection(connStr))
        {
            conn.Open();
            //這裡假設第一行第一列為null
            SqlCommand comm = new SqlCommand("select null", conn);
            if (Convert.IsDBNull(comm.ExecuteScalar()))
            {
                Response.Write("返回的是DBNull"); //output:返回的是DBNull
            }
        }

        using (SqlConnection conn = new SqlConnection(connStr))
        {
            conn.Open();
            SqlCommand comm = new SqlCommand("select * from Tab_Article where 1=0", conn);
            SqlDataReader read = comm.ExecuteReader();
            if (read.Read())
            {
                Response.Write("有資料");
            }
            else
            {
                Response.Write("無資料");//output:無資料
            }
        }

        using (SqlConnection conn = new SqlConnection(connStr))
        {
            DataTable dt = new DataTable();
            conn.Open();
            SqlCommand comm = new SqlCommand("select * from Tab_Article where 1=0", conn);
            SqlDataAdapter sda = new SqlDataAdapter(comm);
            sda.Fill(dt);
            if (dt.Rows.Count==0)
            {
                Response.Write("無資料");//output:無資料
            }
        }

        //所以在進行ExecuteScalar處理的時候一定要判斷返回的值是否為空
        using (SqlConnection conn = new SqlConnection(connStr))
        {
            conn.Open();
            SqlCommand comm = new SqlCommand("select id from Tab_Article where 1=0", conn);
            object o=comm.ExecuteScalar();
            if (o!=null)
            {
                //...
            }
            else if(Convet.isDBNull(o))
            {
                //判斷是否為DBNull,做相關處理,因為如果返回無任何行返回的是null而不是DBNull,使用ToString();會報NullReferencesException異常.
            }else{
         //... 
       }
        }

簡化寫法:

對於IDbDataParameter(OleDDbParameter,SqlParameter..)的Value,如果為null,則代表該引數沒有指定,或者是代表DEFAULT。(如果資料庫中設定了預設值,就可以使用null),如果為DBNull.Value,則代表SQL中的NULL

所以,如果你要呼叫儲存過程,裡面有引數 @val nvarchar(20)=”AABB” ,

那麼cmd.Parameters[“@val”].Value=null 代表使用這個預設的 “AABB”–>這裡為null,則代表該引數沒有指定,會使用儲存過程的預設值:@val nvarchar(20)=”AABB”
而cmd.Parameters[“@val”].Value=DBNull.Value 代表使用NULL來傳給 @val

你可以用Convert.IsDBNull來判斷一個值是否DBNull。注意Convert.IsDBNull(null)是false。

備註:以上的SQL語句全是指SQLSERVER2000的。其他的資料庫是否為同樣的行為,我不確定。

在我的一個程式裡遇到這樣一個問題
在資料庫中的某個欄位型別為 datetime
頁面上對應該欄位的為一個text文字輸入框,意思是輸入時間。

string strId =txtId.Text.Trim();
string strName=txtName.Text.Trim();
string strPwd=txtPwd.Text.Trim();
string strExpiry=txtTime.Text.Trim(); //時間

System.Data.SqlClient.SqlParameter []parmCustomers = new SqlParameter[3];
parmCustomers[0] = new SqlParameter( "@C_Id", strId );
parmCustomers[1] = new SqlParameter( "@Name", strName );
parmCustomers[2] = new SqlParameter( "@Pwd", strPwd );  
parmCustomers[3] = new SqlParameter("@Date",strExpiry);  //如果現文本里沒有輸入時間

SqlServerDatabase obj = new SqlServerDatabase();
if ( obj.RunProc( "proc_AddUser", parmCustomers ) == 1 )  // 新增成功
{
    Response.Write("<script type='text/javascript'>alert('Add Success!')</script>");
}

上段程式當然可以新增成功,
問題是當txtTime.Text什麼都沒輸入的時候,資料庫中的這個欄位仍然會儲存 1900-01-01 00:00:00.000

於是我就在parmCustomers[3] = new SqlParameter(“@Date”, ” ” )寫入空字串 或是 null ,可問題插入後資料庫裡還是顯示1900-01-01

以下是解決辦法:

於是加了判斷:

//注資料庫裡時間欄位要設定永許為空

string strExpiry=this.txtTime.Text.Trim();

System.Data.SqlClient.SqlParameter []parmCustomers = new SqlParameter[3];
parmCustomers[0] = new SqlParameter( "@C_Id", strId );
parmCustomers[1] = new SqlParameter( "@Name", strName );
parmCustomers[2] = new SqlParameter( "@Pwd", strPwd );  

if(strExpiry.ToString()=="")
{
    parmCustomers[3] = new SqlParameter("@Date",DBNull.Value);//如果文字框的時間為空的話就吧 strExpiry 改為 DBNull.Value 就OK了
}
else
{
    parmCustomers[3] = new SqlParameter("@Date",strExpiry);//有值時
}
SqlServerDatabase obj = new SqlServerDatabase();
if ( obj.RunProc( "proc_AddUser", parmCustomers ) == 1 )  // 新增成功
{
    Response.Write("<script type='text/javascript'>alert('Add Success!')</script>");
}

如果是Sql語句直接插入的話 
insert into AddUser (name,pwd)values(’test’,’123’)
date欄位 就不要寫入到插入的Sql語句裡 這樣資料庫裡的值就為空了。。。

(1)NULL

null 關鍵字是表示不引用任何物件的空引用的文字值。

null 是引用型別變數的預設值。

那麼也只有引用型的變數可以為NULL如果 int i=null,的話,是不可以的,因為Int是值型別的。

(2)DBNULL

DBNull在DotNet是單獨的一個型別,該類只能存在唯一的例項,DBNULL.Value,DBNull唯一作用是 可以表示資料庫中的字串,數字,或日期,為什麼可以表示原因是DotNet儲存這些資料的類(DataRow等)都是以 object 的形式來儲存資料的。

對於 DataRow , 它的 row[column] 返回的值永遠不為 null , 要麼就是具體的為column 的型別的值 , 要麼就是 DBNull 。

所以 row[column].ToString() 這個寫法永遠不會在ToString那裡發生NullReferenceException。

DBNull 實現了 IConvertible 。

但是,除了 ToString 是正常的外,其他的ToXXX都會丟擲不能轉換的錯誤。

示例:


Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--> if (ds.Tables[0].Rows[0]["click"].ToString() =="")
                {
                    //true
                }

                if (ds.Tables[0].Rows[0]["click"].ToString() == string.Empty)
                {
                    //true
                }


                if (ds.Tables[0].Rows[0]["click"].ToString() == null)
                {
                   //false
                }

                if (ds.Tables[0].Rows[0]["click"]==null)
                {
                    //false
                }

                if (ds.Tables[0].Rows[0]["click"].Equals(DBNull.Value))
                {
                    //true
                }

在處理資料庫的時候的一個技巧:


Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->  if (ds.Tables[0].Rows.Count > 0)    {
      if (ds.Tables[0].Rows[0]["id"].ToString() != "")
       {
           model.id = int.Parse(ds.Tables[0].Rows[0]["id"].ToString());
       }
}

//以上方式這裡判斷一下查詢出來是否存在行,如果不判斷的話就需要判斷返回值是否為空了,因為查詢沒有任何行的情況下返回的是Null而不是DBNull,下面這種方式要每行都新增這種判斷顯然不是好方法.

如:

if(ds.Tables[0].Rows[0]["id"]!=null && ds.Tables[0].Rows[0]["id"].ToString()!=""){
         model.id = int.Parse(ds.Tables[0].Rows[0]["id"].ToString());
}

//或者
if(ds.Tables[0].Rows[0]["id"]!=null && ! Convert.IsDBNull(ds.Tables[0].Rows[0]["id"]))
         model.id = int.Parse(ds.Tables[0].Rows[0]["id"].ToString());
}

//所以我們在做資料庫操作的時候會以以下示例方法來取資料庫中的資料,也就是判斷一下if (ds.Tables[0].Rows.Count > 0){//...}的方式:
  public Holiday.DAL.Model.TouristTrack GetModel(int id)
        {

            StringBuilder strSql = new StringBuilder();
            strSql.Append("select  top 0 id,routeName,routeCharacteristic,routeIntroductions,costDetail,participate,click,routeCategory,dineMenu,weather,isEnable,addPerson,addDate,competitiveProducts,luxury,onVacation,characteristic,hotRecommend,referencesPrice,specialPreference,imgShow,imgName,imgUrl,newProduct,overflow,season from Tab_TouristTrack ");
            strSql.Append(" where [email protected] ");
            SqlParameter[] parameters = {
                    new SqlParameter("@id", SqlDbType.Int,4)};
            parameters[0].Value = id;

            Holiday.DAL.Model.TouristTrack model = new Holiday.DAL.Model.TouristTrack();
            DataSet ds = SQLLinkDatabase.Query(strSql.ToString(), parameters);
            if (ds.Tables[0].Rows.Count > 0)
            {
                if (ds.Tables[0].Rows[0]["id"].ToString() != "")
                {
                    model.id = int.Parse(ds.Tables[0].Rows[0]["id"].ToString());
                }
                model.routeName = ds.Tables[0].Rows[0]["routeName"].ToString();
                model.routeCharacteristic = ds.Tables[0].Rows[0]["routeCharacteristic"].ToString();
                model.routeIntroductions = ds.Tables[0].Rows[0]["routeIntroductions"].ToString();
                model.costDetail = ds.Tables[0].Rows[0]["costDetail"].ToString();
                model.participate = ds.Tables[0].Rows[0]["participate"].ToString();

                if (ds.Tables[0].Rows[0]["routeCategory"].ToString() != "")
                {
                    model.routeCategory = int.Parse(ds.Tables[0].Rows[0]["routeCategory"].ToString());
                }
                model.dineMenu = ds.Tables[0].Rows[0]["dineMenu"].ToString();
                model.weather = ds.Tables[0].Rows[0]["weather"].ToString();

                model.isEnable = ds.Tables[0].Rows[0]["isEnable"].ToString();

                model.Addperson = ds.Tables[0].Rows[0]["addPerson"].ToString();

                if (ds.Tables[0].Rows[0]["addDate"].ToString() != "")
                {
                    model.addDate = DateTime.Parse(ds.Tables[0].Rows[0]["addDate"].ToString());
                }
                model.Competitiveproducts = ds.Tables[0].Rows[0]["competitiveProducts"].ToString();
                model.luxury = ds.Tables[0].Rows[0]["luxury"].ToString();
                model.onVacation = ds.Tables[0].Rows[0]["onVacation"].ToString();
                model.Characteristic = ds.Tables[0].Rows[0]["characteristic"].ToString();
                model.hotRecommend = ds.Tables[0].Rows[0]["hotRecommend"].ToString();
                if (ds.Tables[0].Rows[0]["referencesPrice"].ToString() != "")
                {
                    model.ReferencesPrice = decimal.Parse(ds.Tables[0].Rows[0]["referencesPrice"].ToString());
                }
                model.SpecialPreference = ds.Tables[0].Rows[0]["specialPreference"].ToString();
                model.ImgShow = ds.Tables[0].Rows[0]["imgShow"].ToString();
                model.ImgName = ds.Tables[0].Rows[0]["imgName"].ToString();
                model.ImgUrl = ds.Tables[0].Rows[0]["imgUrl"].ToString();
                model.NewProduct = ds.Tables[0].Rows[0]["newProduct"].ToString();
                model.Overflow = ds.Tables[0].Rows[0]["overflow"].ToString();
                model.Season = ds.Tables[0].Rows[0]["season"].ToString();
                return model;
            }
            else
            {
                return null;
            }
        }

(3)”“和String.Empty

這兩個都是表示空字串,其中有一個重點是string str1=”” 和 string str2=null 的區別。

這樣定義後,str1是一個空字串,空字串是一個特殊的字串,只不過這個字串的值為空,在記憶體中是有準確的指向的。

string str2=null,這樣定義後,只是定義了一個string 類的引用,str2並沒有指向任何地方,在使用前如果不例項化的話,都將報錯。

(4)Convert.IsDBNull()

Convert.IsDBNull()返回有關指定物件是否為 DBNull 型別的指示,即是用來判斷物件是否為DBNULL的。

其返回值是True或Flase。

要判斷空值,有如下這些方法:

1、e.Row.DataItem!=Null

對於可以取e的,直接用obj和Null比較

2、drg[“column”]==System.DBNull.Value 或者 Convert.IsDBNull(string)

這種情況用於判斷資料庫中的資料是否為空

3、string.IsNullOrEmpty(string)

判斷字串是否為空的萬能方法

取資料時怕資料庫的資料為空,還可以這樣寫:

 if ((dr.Get("MessageID") != null) && !object.Equals(dr.Get("MessageID"), DBNull.Value))
  {
         wen1.MessageID = int.Parse(dr.Get("MessageID"));
  }

相關推薦

no