혼동하기 쉬운 C# null 과 DBNull

[제목] 혼동하기 쉬운 C# null 과 DBNull

데이타베이스 특히 SQL Server의 데이타를 읽어 와서 이를 핸들링할 때, DB 테이블의 NULL 필드에 대한 처리에서 흔히 혼동하는 경우가 있다.

예를 들어, 아래 코드는 올바른 코드인가? 이 코드의 의도는 변수 rs가 DataReader를 리턴받아 첫번째 컬럼이 NULL인지 체크하는 것이다.

IDataReader rs = GetReader();
rs.Read();
if (string.IsNullOrEmpty(rs[0].ToString())
{
	Console.WriteLine("Field is NULL");
}
else 
{
    //...
}

언뜻 살펴보면, 위의 코드는 rs[0]가 null 인지 체크하지 않고 있다. 만약 이것이 null이라면 물론 ToString() 메서드를 사용할 수 없다. 만약 rs[0]이 항상 null 이 될 수 없다면 ToString()을 적용할 수 있다. 하지만 여기서 IsNullOrEmpty() 메서드를 써서 ToString()의 결과값이 null인 경우도 불필요하게(?) 체크하고 있다. 결론적으로 말하면, 위의 코드는 제대로 동작한다. 굳이 IsNullOrEmpty() 메서드를 사용할 필요가 있었느냐의 문제가 있겠지만...

한가지 중요한 사실은 데이타베이스의 필드값이 NULL일 때, ADO.NET 에서 해당 필드를 null 로 리턴하지 않는다는 것이다. ADO.NET은 이 경우 System.DBNull의 객체를 리턴한다. C#의 null은 타당하지 않은 레퍼런스를 의미한다. 즉, null이 할당된 변수로부터 어떤 메서드를 Invoke할 수 없다. 하지만 System.DBNull은 어떤 변수(DB 필드)의 값이 NULL이라는 것을 나타내는 .NET 클래스이고, NULL 필드는 이 클래스의 객체로서 표현된다. System.DBNull 객체에서 .ToString()을 호출하면 empty string을 리턴하게 된다.

이러한 사항들을 고려하며 아래의 코드를 살펴보자.

class Program
{
    static void Main(string[] args)
    {
        IDataReader rs = new Program().GetReader();
        rs.Read();
        if (rs[0] == null) // 항상 false
        {
            // 아래 문장은 어떤 경우에도 실행 안됨
            Console.WriteLine("notes is NULL");
        }
        else
        {
            // System.DBNull
            Type fieldType = rs[0].GetType();
            Console.WriteLine("Field Type=" + fieldType);

            string value = rs[0].ToString();
            Console.WriteLine("Field Value=" + value);
        }

        // rs[0]는 null일 수 없으므로 ToString() 항상 사용 가능
        if (rs[0].ToString() == "")
        {
            Console.WriteLine("Field is NULL");
        }

        rs.Close();
    }

    public IDataReader GetReader()
    {
        string strConn = "Data Source=.;Initial Catalog=pubs;Integrated Security=SSPI;";
        SqlConnection conn = new SqlConnection(strConn);
        conn.Open();
        SqlCommand cmd = new SqlCommand();
        cmd.Connection = conn;
		// NULL 필드가 리턴된다고 가정
        cmd.CommandText = "SELECT notes FROM titles WHERE title_id='MC3026'";
        SqlDataReader rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
        return rdr;
    }
}

코드를 자세히 보면, 라인7 의 null 체크는 항상 실패할 것이므로 Line 10은 언제나 실행되지 않음을 알 수 있다. 라인23 의 ToString() 적용은 항상 가능하고 필드값이 NULL인 경우 이 문장은 true가 된다.

Null과 DBNull의 문제는 DB를 많이 사용하지 않으면 가끔 혼동할 수 있는 문제이므로 기본 개념을 명확히 잡아두는 것이 좋다.

마지막으로 위의 코드에서 DataReader를 GetReader() 메서드에서 리턴했을 때, DB Connection을 끊을 수 없는데
(끊으면 rs를 읽기 못함) rs가 Close() 되면 Connection을 함께 끊으라는 명령을 내리기 위해
ExecuteReader (CommandBehavior.CloseConnection)를 사용하였다
(참조: http://www.csharpstudy.com/Data/SQL-datareader.aspx )



본 웹사이트는 광고를 포함하고 있습니다. 광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.