.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 方法,来确定联系人数据库中的数据库字段是否具有有效值。如果具有有效值,字段值将被追加到在标签中输出的字符串中。

 

代码
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都会抛出不能转换的错误。

如:

 

 

代码
 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 这样返回的object是 1
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 username=@name 这样的sql语句,如果找不到记录,那么ExecuteScalar则会返回null,所以千万不要

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

  

    或者你会这样写 SQL 语句:select isnull(id,0) from usertable where username=@name 

但是 int userid=Convert.ToInt32(cmd.ExecuteScalar()); 依然会出错,因为上面的语句不成立时,仍然是不返回任何行。

 

下面来看一些示例:

 

 

代码
 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都会抛出不能转换的错误。

示例:

 

代码
 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
                }

 

在处理数据库的时候的一个技巧:

 

代码
  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 id=@id ");
            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"));
  }

 

 

 

posted @ 2009-12-08 22:02  唔愛吃蘋果  阅读(14287)  评论(0编辑  收藏  举报