Condividi tramite


Gestione dei valori Null

Un valore Null in un database relazionale viene usato quando il valore in una colonna è sconosciuto o mancante. Un valore Null non è né una stringa vuota (per i tipi di dati character o datetime) né un valore zero (per i tipi di dati numerici). La specifica ANSI SQL-92 indica che un valore Null deve essere lo stesso per tutti i tipi di dati, in modo che tutti i valori Null vengano gestiti in modo coerente. Lo System.Data.SqlTypes spazio dei nomi fornisce la semantica null implementando l'interfaccia INullable. Ognuno dei tipi di dati in System.Data.SqlTypes ha una propria IsNull proprietà e un Null valore che può essere assegnato a un'istanza di tale tipo di dati.

Annotazioni

.NET Framework versione 2.0 ha introdotto il supporto per i tipi valore nullable, che consentono ai programmatori di estendere un tipo valore per rappresentare tutti i valori del tipo sottostante. Questi tipi di valore CLR nullable rappresentano un'istanza della struttura Nullable. Questa funzionalità è particolarmente utile quando i tipi valore vengono inscatolati e disinscatolati, fornendo una migliore compatibilità con i tipi di oggetto. I tipi di valore nullable CLR non sono destinati all'archiviazione dei null del database, poiché un valore null di ANSI SQL non si comporta allo stesso modo di un riferimento null (o Nothing in Visual Basic). Per lavorare con i valori null del database ANSI SQL, utilizzare System.Data.SqlTypes anziché Nullable. Per ulteriori informazioni su come lavorare con tipi di dati CLR nullable in Visual Basic, vedere Tipi valore Nullable, e per C# vedere Tipi valore Nullable.

Nulli e logica di Three-Valued

Se si consentono valori Null nelle definizioni di colonna, viene introdotta la logica a tre valori nell'applicazione. Un confronto può restituire una delle tre condizioni seguenti:

  • Vero

  • Falso

  • Sconosciuto

Poiché null è considerato sconosciuto, due valori Null confrontati tra loro non vengono considerati uguali. Nelle espressioni che usano operatori aritmetici, se uno degli operandi è Null, il risultato è null.

Valori Nulli e SqlBoolean

Il confronto tra qualsiasi System.Data.SqlTypes restituirà un oggetto SqlBoolean. La funzione IsNull per ogni SqlType restituisce un SqlBoolean e può essere usata per verificare la presenza di valori null. Le tabelle di verità seguenti illustrano come funzionano gli operatori AND, OR e NOT in presenza di un valore Null. (T=true, F=false e U=unknown o null.)

Tabella verità

Informazioni sull'opzione ANSI_NULLS

System.Data.SqlTypes fornisce la stessa semantica di quando l'opzione ANSI_NULLS è impostata in SQL Server. Tutti gli operatori aritmetici (+, -, *, /, %), gli operatori bit per bit (~, &, |) e la maggior parte delle funzioni restituiscono null se uno degli operandi o degli argomenti è null, ad eccezione della proprietà IsNull.

Lo standard ANSI SQL-92 non supporta columnName = NULL in una clausola WHERE. In SQL Server, l'opzione ANSI_NULLS controlla sia la nullabilità predefinita nel database che la valutazione dei confronti con i valori null. Se ANSI_NULLS è attivato (impostazione predefinita), è necessario usare l'operatore IS NULL nelle espressioni durante il test dei valori Null. Ad esempio, il confronto seguente restituisce sempre un valore sconosciuto quando ANSI_NULLS è attivo.

colname > NULL  

Il confronto con una variabile contenente un valore Null restituisce anche sconosciuti:

colname > @MyVariable  

Usare il predicato IS NULL o IS NOT NULL per testare un valore Null. Ciò può aggiungere complessità alla clausola WHERE. Ad esempio, la colonna TerritoryID nella tabella AdventureWorks Customer consente valori Null. Se un'istruzione SELECT deve testare i valori Null oltre ad altri, deve includere un predicato IS NULL:

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

Se si imposta ANSI_NULLS disattivato in SQL Server, è possibile creare espressioni che usano l'operatore di uguaglianza per confrontare con null. Tuttavia, non è possibile impedire a connessioni diverse di impostare opzioni Null per tale connessione. L'uso di IS NULL per verificare i valori Null funziona sempre, indipendentemente dalle impostazioni di ANSI_NULLS per una connessione.

L'impostazione di ANSI_NULLS su off non è supportata in un DataSet, che aderisce sempre allo standard ANSI SQL-92 per la gestione dei valori null in System.Data.SqlTypes.

Assegnazione di valori Null

I valori Null sono speciali e la semantica di archiviazione e assegnazione differisce tra i diversi sistemi di tipi e di archiviazione. Un Dataset è progettato per essere usato con diversi sistemi di tipo e di archiviazione.

In questa sezione viene descritta la semantica null per l'assegnazione di valori null a un DataColumn in un DataRow nei diversi sistemi di tipi.

DBNull.Value
Questa assegnazione è valida per un DataColumn qualsiasi tipo. Se il tipo implementa INullable, DBNull.Value viene convertito nel valore Null fortemente tipizzato appropriato.

SqlType.Null
Tutti i System.Data.SqlTypes tipi di dati implementano INullable. Se il valore Null fortemente tipizzato può essere convertito nel tipo di dati della colonna usando operatori cast impliciti, l'assegnazione deve essere eseguita. In caso contrario, viene generata un'eccezione cast non valida.

null
Se il termine 'null' è un valore legale per il tipo di dati DataColumn, viene sottoposto a coercizione nel valore appropriato DbNull.Value o Null associato al tipo INullable (SqlType.Null)

derivedUdt.Null
Per le colonne UDT, i null vengono sempre archiviati in base al tipo associato all'oggetto DataColumn. Si consideri il caso di un UDT associato a un DataColumn che non implementa INullable, mentre la sua sottoclasse lo implementa. In questo caso, se viene assegnato un valore Null fortemente tipizzato associato alla classe derivata, viene archiviato come non tipizzato DbNull.Value, perché l'archiviazione Null è sempre coerente con il tipo di dati DataColumn.

Annotazioni

La struttura Nullable<T> o Nullable non è attualmente supportata in DataSet.

Il valore predefinito per qualsiasi System.Data.SqlTypes istanza è Null.

I valori Null in System.Data.SqlTypes sono specifici del tipo e non possono essere rappresentati da un singolo valore, ad esempio DbNull. Utilizzare la IsNull proprietà per verificare la presenza di valori Null.

I valori Null possono essere assegnati a un oggetto DataColumn come illustrato nell'esempio di codice seguente. È possibile assegnare direttamente valori Null alle SqlTypes variabili senza attivare un'eccezione.

Esempio

Nell'esempio di codice seguente viene creato un DataTable oggetto con due colonne definite come SqlInt32 e SqlString. Il codice aggiunge una riga di valori noti, una riga di valori Null e quindi scorre l'oggetto DataTable, assegnando i valori alle variabili e visualizzando l'output nella finestra della console.

static void WorkWithSqlNulls()
{
    DataTable table = new();

    // 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();
    }
Private Sub WorkWithSqlNulls()
    Dim table As New DataTable()

    ' Specify the SqlType for each column.
    Dim idColumn As DataColumn = _
      table.Columns.Add("ID", GetType(SqlInt32))
    Dim descColumn As DataColumn = _
      table.Columns.Add("Description", GetType(SqlString))

    ' Add some data.
    Dim row As DataRow = table.NewRow()
    row("ID") = 123
    row("Description") = "Side Mirror"
    table.Rows.Add(row)

    ' Add null values.
    row = table.NewRow()
    row("ID") = SqlInt32.Null
    row("Description") = SqlString.Null
    table.Rows.Add(row)

    ' Initialize variables to use when
    ' extracting the data.
    Dim isColumnNull As SqlBoolean = False
    Dim idValue As SqlInt32 = SqlInt32.Zero
    Dim descriptionValue As SqlString = SqlString.Null

    ' Iterate through the DataTable and display the values.
    For Each row In table.Rows
        ' Assign values to variables. Note that you 
        ' do not have to test for null values.
        idValue = CType(row("ID"), SqlInt32)
        descriptionValue = CType(row("Description"), SqlString)

        ' Test for null value with ID column
        isColumnNull = idValue.IsNull

        ' Display variable values in console window.
        Console.Write("isColumnNull={0}, ID={1}, Description={2}", _
          isColumnNull, idValue, descriptionValue)
        Console.WriteLine()
    Next row
End Sub

In questo esempio vengono visualizzati i risultati seguenti:

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

Assegnazione di più colonne (righe)

DataTable.Add, DataTable.LoadDataRow, o altre API che accettano un oggetto ItemArray che viene mappato a una riga, mappare 'null' al valore predefinito di DataColumn. Se un oggetto nella matrice contiene DbNull.Value o la controparte fortemente tipizzata, vengono applicate le stesse regole descritte in precedenza.

Inoltre, per un'istanza di DataRow.["columnName"] assegnazioni nulle si applicano le regole seguenti:

  1. Il valore predefinito è DbNull.Value per tutte le colonne tranne quelle Null fortemente tipizzate, dove il valore è il Null fortemente tipizzato appropriato.

  2. I valori Null non vengono mai scritti durante la serializzazione in file XML (come in "xsi:nil").

  3. Tutti i valori diversi da null, incluse le impostazioni predefinite, vengono sempre esplicitati durante la serializzazione in XML. A differenza della semantica XSD/XML in cui un valore Null (xsi:nil) è esplicito e il valore predefinito è implicito (se non presente in XML, un parser di convalida può ottenerlo da uno schema XSD associato). È vero il contrario per un DataTable: un valore nulla è implicito e il valore predefinito è esplicito.

  4. Tutti i valori di colonna mancanti per le righe lette dall'input XML vengono assegnati NULL. Alle righe create con NewRow o metodi simili viene assegnato il valore predefinito di DataColumn.

  5. Il IsNull metodo restituisce true sia per DbNull.Value che per INullable.Null.

Confrontare valori nulli con tipi SqlTypes e CLR

Quando si confrontano i valori null, è importante comprendere la differenza tra il modo in cui il metodo Equals valuta i valori null in System.Data.SqlTypes e il modo in cui funziona con i tipi CLR. Tutti i metodi usano la System.Data.SqlTypesEquals semantica del database per valutare i valori Null: se uno o entrambi i valori è Null, il confronto restituisce Null. D'altra parte, l'uso del metodo CLR Equals su due System.Data.SqlTypes restituirà true se entrambi sono Null. Ciò riflette la differenza tra l'uso di un metodo di istanza, ad esempio il metodo CLR String.Equals , e l'uso del metodo statico/condiviso, SqlString.Equals.

Nell'esempio seguente viene illustrata la differenza nei risultati tra il SqlString.Equals metodo e il String.Equals metodo quando viene passata una coppia di valori Null e quindi una coppia di stringhe vuote.

    static void CompareNulls()
    {
        // Create two new null strings.
        SqlString a = new();
        SqlString b = new();

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

        // Compare nulls using instance method String.Equals.
        Console.WriteLine();
        Console.WriteLine("String.Equals instance method:");
        Console.WriteLine($"  Two nulls={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={SqlStringEquals(a, b)}");

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

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

    static string StringEquals(SqlString string1, SqlString string2)
    {
        // String.Equals uses CLR type semantics for evaluating nulls.
        var returnValue = string1.Equals(string2).ToString();
        return returnValue;
    }
}
Private Sub CompareNulls()
    ' Create two new null strings.
    Dim a As New SqlString
    Dim b As 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))
End Sub

Private Function SqlStringEquals(ByVal string1 As SqlString, _
    ByVal string2 As SqlString) As String

    ' SqlString.Equals uses database semantics for evaluating nulls.
    Dim returnValue As String = SqlString.Equals(string1, string2).ToString()
    Return returnValue
End Function

Private Function StringEquals(ByVal string1 As SqlString, _
    ByVal string2 As SqlString) As String

    ' String.Equals uses CLR type semantics for evaluating nulls.
    Dim returnValue As String = string1.Equals(string2).ToString()
    Return returnValue
End Function

Il codice produce l'output seguente:

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

Vedere anche