Partilhar via


Manipulando valores nulos

Baixar ADO.NET

Um valor nulo em um banco de dados relacional é usado quando o valor em uma coluna é desconhecido ou está ausente. Um valor nulo não é uma cadeia de caracteres vazia (para tipos de dados character ou datetime) nem um valor zero (para tipos de dados numéricos). A especificação ANSI SQL-92 declara que um valor nulo deve ser o mesmo para todos os tipos de dados, para que todos os nulos sejam manipulados consistentemente. O namespace System.Data.SqlTypes fornece semântica nula ao implementar a interface INullable. Cada um dos tipos de dados no System.Data.SqlTypes tem uma propriedade própria IsNull e um valor Null que pode ser atribuído a uma instância desse tipo de dados.

Observação

O .NET Framework versão 2.0 e o .NET Core versão 1.0 introduziram suporte para tipos que permitem valor nulo, o que possibilita aos programadores estender um tipo de valor para representar todos os valores do tipo subjacente. Esses tipos CLR que permitem valor nulo representam uma instância da estrutura Nullable. Essa funcionalidade é especialmente útil quando os tipos de valor são boxed e unboxed, fornecendo compatibilidade aprimorada com tipos de objeto. Os tipos CLR que permitem valor nulo não se destinam ao armazenamento de nulos de banco de dados porque um nulo ANSI SQL não se comporta da mesma maneira que uma referência de null (ou Nothing no Visual Basic). Para trabalhar com valores nulos ANSI SQL de banco de dados, use nulos System.Data.SqlTypes em vez de Nullable. Para obter mais informações sobre como trabalhar com tipos CLR que permitem valores nulos no C#, confira Tipos que permitem valor nulo e, para C#, confira Como usar tipos que permitem valor nulo.

Lógica nula e de três valores

Permitir valores nulos em definições de coluna apresenta uma lógica de três valores em seu aplicativo. Uma comparação pode ser avaliada como uma das três condições:

  • True

  • Falso

  • Unknown (desconhecido)

Como um nulo é considerado desconhecido, dois valores nulos comparados entre si não são considerados iguais. Em expressões que usam operadores aritméticos, se um dos operandos for nulo, o resultado será nulo também.

Nulos e SqlBoolean

A comparação entre um System.Data.SqlTypes retornará um SqlBoolean. A função IsNull para cada SqlType retorna um SqlBoolean e pode ser usada para verificar se há valores nulos. As tabelas da verdade a seguir mostram como os operadores AND, OR e NOT funcionam na presença de um valor nulo. (T = verdadeiro, F = falso e U = desconhecido ou nulo.)

Truth Table

Noções básicas sobre a opção ANSI_NULLS

O System.Data.SqlTypes fornece a mesma semântica que quando a opção ANSI_NULLS é definida no SQL Server. Todos os operadores aritméticos (+, -, *, /, %), operadores bit a bit (~, &, |) e a maioria das funções retornarão nulo se algum operando ou argumento for nulo, exceto na propriedade IsNull.

O padrão ANSI SQL-92 não oferece suporte a columnName = NULL em uma cláusula WHERE. No SQL Server, a opção ANSI_NULLS controla a nulabilidade padrão no banco de dados e a avaliação de comparações com valores nulos. Se ANSI_NULLS estiver ativado (o padrão), o operador IS NULL deverá ser usado em expressões ao testar valores nulos. Por exemplo, a comparação seguinte sempre retorna unknown quando ANSI_NULLS for on:

colname > NULL  

A comparação com uma variável que contém um valor nulo também produz valores desconhecidos:

colname > @MyVariable  

Use o predicado IS NULL ou IS NOT NULL para testar um valor nulo. Isso pode adicionar complexidade à cláusula WHERE. Por exemplo, a coluna TerritoryID na tabela Cliente AdventureWorks permite valores nulos. Se uma instrução SELECT for testada para obter valores nulos além de outros, ela deverá incluir um predicado IS NULL:

SELECT CustomerID, AccountNumber, TerritoryID  
FROM AdventureWorks.Sales.Customer  
WHERE TerritoryID IN (1, 2, 3)  
   OR TerritoryID IS NULL  

Se você desativar ANSI_NULLS no SQL Server, poderá criar expressões que usam o operador de igualdade para comparar com o valor nulo. No entanto, você não pode impedir que diferentes conexões configurem opções nulas para essa conexão. Usar IS NULL para testar valores nulos sempre funciona, independentemente das configurações de ANSI_NULLS para uma conexão.

A desativação de ANSI_NULLS não é compatível em um DataSet, que sempre segue o padrão ANSI SQL-92 para manipular valores nulos no System.Data.SqlTypes.

Como atribuir valores nulos

Os valores nulos são especiais e suas semânticas de armazenamento e atribuição diferem em sistemas de tipos e sistemas de armazenamento diferentes. Um Dataset foi projetado para ser usado com diferentes sistemas de tipo e armazenamento.

Esta seção descreve a semântica nula para atribuir valores nulos a um DataColumn em um DataRow entre os diferentes sistemas de tipos.

DBNull.Value
Essa atribuição é válida para um DataColumn de qualquer tipo. Se o tipo implementa INullable, o DBNull.Value é forçado no valor nulo fortemente tipado apropriado.

SqlType.Null
Todos os tipos de dados System.Data.SqlTypes implementam INullable. Se o valor nulo fortemente tipado puder ser convertido no tipo de dados da coluna usando operadores de conversão implícitos, a atribuição deverá passar. Caso contrário, uma exceção de conversão inválida será lançada.

null
Se nulo for um valor válido para o tipo de dados DataColumn especificado, ele será forçado no DbNull.Value ou no Null apropriado associado ao tipo INullable (SqlType.Null)

derivedUdt.Null
Para colunas UDT, os nulos são sempre armazenados com base no tipo associado à DataColumn. Considere o caso de um UDT associado a um DataColumn que não implementa INullable, enquanto sua subclasse faz a implementação. Nesse caso, se um valor nulo fortemente tipado associado à classe derivada for atribuído, ele será armazenado como um DbNull.Value não tipado, porque o armazenamento nulo é sempre consistente com o tipo de dados de DataColumn.

Observação

No momento, a estrutura Nullable<T> ou Nullable não é compatível no DataSet.

Atribuição de várias colunas (linhas)

DataTable.Add, DataTable.LoadDataRow ou outras APIs que aceitam um ItemArray que é mapeado para uma linha, mapeiam "nulo" para o valor padrão da DataColumn. Se um objeto na matriz contiver DbNull.Value ou sua contraparte fortemente tipada, as mesmas regras descritas acima serão aplicadas.

Além disso, as seguintes regras se aplicam a uma instância de atribuições nulas de DataRow.["columnName"]:

  • O valor default padrão é DbNull.Value para todos, a não ser para as colunas nulas fortemente tipadas em que ele é o valor nulo fortemente tipado apropriado.

  • Os valores nulos nunca são gravados durante a serialização para arquivos XML (como em "xsi:nil").

  • Todos os valores não nulos, incluindo os padrões, sempre são gravados durante a serialização para XML. Isso é diferente da semântica XSD/XML em que um valor nulo (xsi:nil) é explícito e o valor padrão é implícito (se não estiver presente em XML, um analisador de validação poderá obtê-lo de um esquema XSD associado). O oposto é verdadeiro para um DataTable: um valor nulo é implícito e o valor padrão é explícito.

  • Todos os valores de coluna ausentes para linhas lidas da entrada XML são atribuídos como NULL. As linhas criadas usando NewRow ou métodos semelhantes recebem o valor padrão de DataColumn.

  • O método IsNull retorna true para DbNull.Value e INullable.Null.

Atribuindo valores nulos a SqlTypes

O valor padrão para uma instância System.Data.SqlTypes é nulo.

Os valores nulos em System.Data.SqlTypes são específicos do tipo e não podem ser representados por um valor, como DbNull. Use a propriedade IsNull para verificar valores nulos.

Os valores nulos podem ser atribuídos a um DataColumn, conforme mostrado no exemplo de código a seguir. Você pode atribuir diretamente valores nulos a variáveis SqlTypes sem disparar uma exceção.

Exemplo

O exemplo de código a seguir cria um DataTable com duas colunas definidas como SqlInt32 e SqlString. O código adiciona uma linha de valores conhecidos, uma linha de valores nulos e, em seguida, itera por DataTable, atribuindo os valores a variáveis e exibindo a saída na janela do console.

using Microsoft.Data.SqlClient;
using System.Data.SqlTypes;

class Program
{
    static void Main()
    {
        WorkWithSqlNulls();
        Console.ReadLine();
    }
    static private void WorkWithSqlNulls()
    {
        DataTable table = new DataTable();

        // Specify the SqlType for each column.
        DataColumn idColumn =
            table.Columns.Add("ID", typeof(SqlInt32));
        DataColumn descColumn =
            table.Columns.Add("Description", typeof(SqlString));

        // Add some data.
        DataRow nRow = table.NewRow();
        nRow["ID"] = 123;
        nRow["Description"] = "Side Mirror";
        table.Rows.Add(nRow);

        // Add null values.
        nRow = table.NewRow();
        nRow["ID"] = SqlInt32.Null;
        nRow["Description"] = SqlString.Null;
        table.Rows.Add(nRow);

        // Initialize variables to use when
        // extracting the data.
        SqlBoolean isColumnNull = false;
        SqlInt32 idValue = SqlInt32.Zero;
        SqlString descriptionValue = SqlString.Null;

        // Iterate through the DataTable and display the values.
        foreach (DataRow row in table.Rows)
        {
            // Assign values to variables. Note that you 
            // do not have to test for null values.
            idValue = (SqlInt32)row["ID"];
            descriptionValue = (SqlString)row["Description"];

            // Test for null value in ID column.
            isColumnNull = idValue.IsNull;

            // Display variable values in console window.
            Console.Write("isColumnNull={0}, ID={1}, Description={2}",
                isColumnNull, idValue, descriptionValue);
            Console.WriteLine();
        }
    }
}

Esse exemplo mostra os seguintes resultados:

isColumnNull=False, ID=123, Description=Side Mirror  
isColumnNull=True, ID=Null, Description=Null  

Como comparar valores nulos com SqlTypes e tipos CLR

Ao comparar valores nulos, é importante entender a diferença entre a maneira como o método Equals avalia valores nulos em System.Data.SqlTypes em comparação com o modo como ele funciona com tipos CLR. Todos os métodos System.Data.SqlTypesEquals usam semânticas de banco de dados para avaliar valores nulos: se um ou ambos os valores forem nulos, a comparação produzirá um valor nulo. Por outro lado, usar o método CLR Equals em dois System.Data.SqlTypes produzirá true se ambos forem valores nulos. Isso reflete a diferença entre usar um método de instância, como o método CLR String.Equals, e usar o método estático/compartilhado, SqlString.Equals.

O exemplo a seguir demonstra a diferença nos resultados entre o método SqlString.Equals e o método String.Equals quando cada um é passado por um par de valores nulos e, em seguida, um par de cadeias de caracteres vazias.

using System.Data.SqlTypes;

namespace SqlNullsCS
{
    class Program
    {
        static void Main()
        {
            CompareNulls();
            Console.ReadLine();
        }
        private static void CompareNulls()
        {
            // Create two new null strings.
            SqlString a = new SqlString();
            SqlString b = new SqlString();

            // Compare nulls using static/shared SqlString.Equals.
            Console.WriteLine("SqlString.Equals shared/static method:");
            Console.WriteLine("  Two nulls={0}", SqlStringEquals(a, b));

            // Compare nulls using instance method String.Equals.
            Console.WriteLine();
            Console.WriteLine("String.Equals instance method:");
            Console.WriteLine("  Two nulls={0}", StringEquals(a, b));

            // Make them empty strings.
            a = "";
            b = "";

            // When comparing two empty strings (""), both the shared/static and
            // the instance Equals methods evaluate to true.
            Console.WriteLine();
            Console.WriteLine("SqlString.Equals shared/static method:");
            Console.WriteLine("  Two empty strings={0}", SqlStringEquals(a, b));

            Console.WriteLine();
            Console.WriteLine("String.Equals instance method:");
            Console.WriteLine("  Two empty strings={0}", StringEquals(a, b));
        }

        private static string SqlStringEquals(SqlString string1, SqlString string2)
        {
            // SqlString.Equals uses database semantics for evaluating nulls.
            string returnValue = SqlString.Equals(string1, string2).ToString();
            return returnValue;
        }

        private static string StringEquals(SqlString string1, SqlString string2)
        {
            // String.Equals uses CLR type semantics for evaluating nulls.
            string returnValue = string1.Equals(string2).ToString();
            return returnValue;
        }
    }
}

O código produz a seguinte saída:

SqlString.Equals shared/static method:  
  Two nulls=Null  
  
String.Equals instance method:  
  Two nulls=True  
  
SqlString.Equals shared/static method:  
  Two empty strings=True  
  
String.Equals instance method:  
  Two empty strings=True   

Próximas etapas