Was ist der Sinn von DBNull?

72

In .NET gibt es die nullReferenz, die überall verwendet wird, um anzuzeigen, dass eine Objektreferenz leer ist, und dann die DBNull, die von Datenbanktreibern (und wenigen anderen) verwendet wird, um ... so ziemlich dasselbe zu bezeichnen. Dies führt natürlich zu großer Verwirrung und Konvertierungsroutinen müssen durchgeführt werden usw.

Warum haben sich die ursprünglichen .NET-Autoren dazu entschlossen? Für mich macht es keinen Sinn. Ihre Dokumentation macht auch keinen Sinn:

Die DBNull-Klasse repräsentiert einen nicht vorhandenen Wert. In einer Datenbank enthält beispielsweise eine Spalte in einer Zeile einer Tabelle möglicherweise überhaupt keine Daten. Das heißt, die Spalte wird als überhaupt nicht vorhanden angesehen, anstatt lediglich keinen Wert zu haben. Ein DBNull-Objekt repräsentiert die nicht vorhandene Spalte. Darüber hinaus verwendet COM Interop die DBNull-Klasse, um zwischen einer VT_NULL-Variante, die einen nicht vorhandenen Wert angibt, und einer VT_EMPTY-Variante, die einen nicht angegebenen Wert angibt, zu unterscheiden.

Was ist das für ein Mist an einer "nicht existierenden Spalte"? Eine Spalte existiert, sie hat nur keinen Wert für die bestimmte Zeile. Wenn es nicht existieren würde, würde ich eine Ausnahme bekommen, die versucht, auf die spezifische Zelle zuzugreifen, nicht eine DBNull! Ich kann die Notwendigkeit verstehen, zwischen VT_NULLund zu unterscheiden VT_EMPTY, aber warum dann nicht COMEmptystattdessen eine Klasse machen ? Das würde viel besser in das gesamte .NET-Framework passen.

Vermisse ich etwas Kann jemand etwas Licht ins Dunkel bringen, warum DBNulles erfunden wurde und welche Probleme es zu lösen hilft?

Vilx-
quelle
4
Nur ein weiterer Datenpunkt ... das DBI-Modul (DataBase Interface) in der Perl-Sprache hat kein Konzept von DbNull. Wenn ein Wert in der Datenbank NULL ist, stellt DBI ihn als Perl "undef" dar, was das Perl-Äquivalent von "null" in C # ist. Daher hat Perl die Position vertreten, dass ein spezielles "DbNull" -Konzept nicht erforderlich ist, und ich habe noch nie von Perl-Programmierern gehört, die sich DbNull gewünscht hätten.
JoelFan
1
Ich bin bei dir, @JoelFan - ich sehe auch keine echte Verwendung darin
Marc Gravell
Neben den Antworten unten ist der Kommentar von @ thomas-levesque unten sehr wichtig: DBNullvor der Einführung von echten nullbaren Typen in das .NET-Framework. Beide verhalten sich etwas anders und DBNullmussten bleiben (zu meinem Bedauern, aber das ist eine andere Geschichte).
Jeroen Wiert Pluimers
3
Ich mag die Frage, muss aber sagen, dass die Community inkonsistent ist. Viele Fragen zu "Warum ist das so?" als "keine Frage" geschlossen werden, besonders wenn der Fragesteller sich erlaubt zu erklären, warum etwas keinen Sinn ergibt. (Ich habe versucht, nach dem Mangel an angemessener Unterstützung für abstrakte Typen zu fragen / mich zu beschweren, wenn WCF-Dienste als Webdienste verfügbar gemacht wurden, und wurde sofort abgeschossen!)
The Dag

Antworten:

46

Der Punkt ist, dass es in bestimmten Situationen einen Unterschied zwischen einem Datenbankwert, der null ist, und einem .NET-Null gibt.

Zum Beispiel. Wenn Sie ExecuteScalar verwenden (das die erste Spalte der ersten Zeile in der Ergebnismenge zurückgibt) und eine Null zurückerhalten, bedeutet dies, dass die ausgeführte SQL keine Werte zurückgegeben hat. Wenn Sie DBNull zurückerhalten, bedeutet dies, dass ein Wert von SQL zurückgegeben wurde und NULL war. Sie müssen in der Lage sein, den Unterschied zu erkennen.

Colin Mackay
quelle
20
Okay, das ist wohl ein gutes Beispiel. Obwohl nicht genug Grund allein, um das ganze Chaos zu rechtfertigen. ExecuteScalarhätte anders umgeschrieben werden können, um dieses spezielle Problem zu lösen ( DBEmptyRowsetirgendjemand?)
Vilx
2
Ich bin zufrieden mit der Art und Weise, wie Microsoft ADO.NET geschrieben hat. Die Verwendung von DBNull hält alles konsistent. Jedes Mal, wenn ein Spaltenwert null ist, erhalten Sie eine DBNull. Sicher, der zu behandelnde Code kann etwas umständlicher sein als die Rückgabe einer einfachen Null, insbesondere wenn Sie DBNull nur in Null konvertieren, aber am Ende des Tages ist Konsistenz der Schlüssel. Wenn sie ExecuteScalar variiert hätten, weil Sie den Unterschied in diesem Szenario erkennen müssen, hätte es viel mehr erschüttert. Das ist natürlich nur meine Meinung.
Colin Mackay
8
Variieren Sie eine ExecuteScalar()Methode und variieren Sie alle DB-Anbieter, DataSets / Tables / Rows / Columns, datengebundene Steuerelemente und was nicht? Ich würde für den ersten gehen. Letztendlich würde dies den gesamten Code VIEL konsistenter machen. Und einfach. Außerdem - ExecuteScalarist eine der am wenigsten verwendeten Methoden in den Datenanbietern. > 90% der Fälle betreffen DataReader. Plus - glaubst du wirklich, dass a bool ExecuteScalar(out object Value)so inkonsistent wäre?
Vilx
1
Persönlich hätte ich es vorgezogen ExecuteScalar, eine Ausnahme ( DbException) auszulösen, wenn es keinen zurückzugebenden Wert gäbe (keine Zeilen oder keine Spalten) und nullstattdessenDBNull
Robert McKee
1
@UfukSURMEN Ich verstehe nicht, was Sie meinen - Sowohl Speicher als auch Festplatte sind Speichermedien (wenn auch eines kurzlebiger als das andere), daher gibt es in dieser Hinsicht keinen Unterschied. Wenn die Zeile vorhanden ist, gibt ein Wert für eine Spalte in dieser Zeile möglicherweise DBNULL zurück, um anzuzeigen, dass die Spalte für diese Zeile keinen Wert hat. Für ExecuteScalarNULL bedeutet, dass keine Zeilen zurückgegeben wurden, und DBNULL bedeutet, dass mindestens eine Zeile zurückgegeben wurde, die erste Spalte jedoch keinen Wert hat.
Colin Mackay
177

Ich werde dem Trend hier nicht zustimmen. Ich werde aufzeichnen:

Ich bin nicht der Meinung, dass dies DBNulleinem nützlichen Zweck dient. es fügt unnötige Verwirrung hinzu und trägt praktisch keinen Wert bei.

Oft wird das Argument vorgebracht, dass nulles sich um eine ungültige Referenz und DBNullein Nullobjektmuster handelt. beides ist nicht wahr . Zum Beispiel:

int? x = null;

Dies ist keine "ungültige Referenz". es ist ein nullWert. In der Tat nullbedeutet dies, was immer Sie wollen, und ehrlich gesagt habe ich absolut kein Problem damit, mit Werten zu arbeiten, die möglicherweise vorhanden sind null(sogar in SQL müssen wir korrekt arbeiten null- hier ändert sich nichts). Ebenso ist das "Null-Objektmuster" nur dann sinnvoll, wenn Sie es tatsächlich als Objekt in OOP-Begriffen behandeln. Wenn wir jedoch einen Wert haben, der "unser Wert oder ein DBNull" sein kann, muss dies der Fall sein object, damit wir dies nicht können etwas Nützliches damit machen.

Es gibt so viele schlechte Dinge mit DBNull:

  • es zwingt dich zu arbeiten object, da nur oder ein anderer Wert objecthalten kannDBNull
  • Es gibt keinen wirklichen Unterschied zwischen "könnte ein Wert sein oder DBNull" vs "könnte ein Wert sein oder null"
  • Das Argument, dass es aus 1.1 (Pre-Nullable-Typen) stammt, ist bedeutungslos. wir könnten nullin 1.1 perfekt gebrauchen
  • Die meisten APIs haben "Ist es null?" Methoden zum Beispiel DBDataReader.IsDBNulloder DataRow.IsNull- von denen keine tatsächlich DBNullin Bezug auf die API existieren muss
  • DBNullscheitert an der Nullverschmelzung; value ?? defaultValuefunktioniert nicht, wenn der Wert istDBNull
  • DBNull.Value kann nicht in optionalen Parametern verwendet werden, da es keine Konstante ist
  • Die Laufzeitsemantik von DBNullist identisch mit der Semantik von null; Insbesondere ist es DBNulltatsächlich gleich DBNull- es erledigt also nicht die Aufgabe, die SQL-Semantik darzustellen
  • Oft wird erzwungen, dass Werte vom Werttyp eingerahmt werden, da sie zu häufig verwendet werden object
  • Wenn Sie testen müssen DBNull, können Sie genauso gut nur testennull
  • Es verursacht große Probleme für Dinge wie Befehlsparameter, mit einem sehr dummen Verhalten, dass ein Parameter, wenn er einen nullWert hat, nicht gesendet wird ... nun, hier ist eine Idee: Wenn Sie keinen Parameter senden möchten - nicht Fügen Sie es der Parametersammlung hinzu
  • Jedes ORM, an das ich denken kann, funktioniert einwandfrei, ohne dass es benötigt oder verwendet werden muss DBNull, außer als zusätzliches Ärgernis, wenn ich mit dem ADO.NET-Code spreche

Das einzige , auch nur annähernd überzeugendes Argument habe ich jemals gesehen das rechtfertigen Existenz eines solchen Wert ist in DataTable, wenn in Werte vorbei eine neue Zeile zu erstellen; a nullbedeutet "Standard verwenden", a DBNullist explizit eine Null - offen gesagt hätte diese API eine spezielle Behandlung für diesen Fall haben können - ein imaginäres DataRow.DefaultValueBeispiel wäre viel besser als die Einführung eines DBNull.Value, das große Codebereiche ohne Grund infiziert.

Ebenso ist das ExecuteScalarSzenario ... bestenfalls dürftig; Wenn Sie eine skalare Methode ausführen, erwarten Sie ein Ergebnis. In dem Szenario, in dem keine Zeilen vorhanden sind, nullscheint die Rückkehr nicht allzu schrecklich. Wenn Sie unbedingt zwischen "keine Zeilen" und "eine einzige zurückgegebene Null" unterscheiden müssen, gibt es die Reader-API.

Dieses Schiff ist schon lange gesegelt und es ist viel zu spät, um es zu reparieren. Aber! Bitte denken Sie nicht , dass alle zustimmen, dass dies eine "offensichtliche" Sache ist. Viele Entwickler sehen keinen Wert in dieser seltsamen Falte in der BCL.

Ich frage mich tatsächlich, ob all dies auf zwei Dinge zurückzuführen ist:

  • das Wort Nothinganstelle von etwas verwenden zu müssen, das "null" in VB beinhaltet
  • in der Lage zu sein, die if(value is DBNull)Syntax, die "genau wie SQL aussieht", und nicht die ach so kniffligeif(value==null)

Zusammenfassung:

Mit 3 Optionen ( null, DBNulloder ein Ist - Wert) ist nur dann sinnvoll , wenn es ein echtes Beispiel ist , wo Sie zwischen drei verschiedenen Fällen müssen eindeutig zu machen. Ich habe noch keine Gelegenheit gesehen, bei der ich zwei verschiedene "Null" -Zustände darstellen muss. Daher DBNullist dies völlig überflüssig, da es nullbereits existiert und eine viel bessere Sprach- und Laufzeitunterstützung bietet.

Marc Gravell
quelle
16
+1, ich hatte noch nie eine Situation, in der ich eigentlich wollte, dass DBNull.Value von null unterschieden wird. Ich habe immer gedacht, dass dies ein Schmerzpunkt ist, und gesehen (+ erlebt), dass viel Zeit aufgrund der Tatsache verschwendet wurde, dass null! = DBNull.Value
AdaTheDev
Gibt es einen Tippfehler bei "DBNull ist tatsächlich gleich DBNull", wenn nicht - ich verstehe es nicht :). Sollte es nicht "DBNull ist tatsächlich gleich Null" sein?
Alex
5
@ Alex nein, kein Tippfehler; und ist DBNulldefinitiv nicht gleich null; versuche es bool x = DBNull.Value.Equals(DBNull.Value); bool y = DBNull.Value.Equals(null);. Der Punkt, den ich hier ansprechen wollte, ist, dass, wenn Sie dies in einer ANSI-kompatiblen SQL-Datenbank versuchen, Sie feststellen, dass nulldies nicht gleich ist null(aber gleichzeitig nullnicht "nicht gleich" null)
Marc Gravell
1
@ Tag Ich kommentierte das: Wenn das die Absicht war, schlägt es fehl, wie in meiner Antwort besprochen
Marc Gravell
1
@Blam nein, es ist nicht effizienter, einen dbnull anstelle einer null zu verwenden. Und ja, einige Joins usw. hinterlassen keine Daten - eine Null: aber das diktiert nicht null gegen dbnull
Marc Gravell
14

DbNullstellt eine Box ohne Inhalt dar; nullzeigt das Nichtvorhandensein der Box an.

Rikalous
quelle
7
"Ja wirklich?" Möchtest du das näher erläutern? Ich verstehe es irgendwie nicht. Zumal eine Variable ("die Box") nicht verschwindet, wenn Sie ihr eine zuweisen null.
Vilx
2
Wenn Sie die Variable als Adresse der Box betrachten, teilen Sie der Variablen mit, dass die Box nicht mehr existiert, wenn Sie der Variablen eine Null zuweisen. Das heißt, die Adresse ist nicht "besuchbar".
Rikalous
Ich bin damit einverstanden, dass die Dokumentation bezüglich der nicht vorhandenen Spalte eindeutig falsch ist.
Rikalous
In dem Maße, in dem die Dokumentation falsch ist, vermute ich, dass sie eher schlecht geschrieben als falsch war.
Phoog
1
@TheDag, ich denke nicht, dass diese Funktionalität den Aufwand wert ist, sowohl null als auch dbnull zu überprüfen und zu konvertieren, wenn nullfähige Objekte verwendet werden.
Drigoangelo
0

Sie verwenden DBNull für fehlende Daten. Null in der .NET-Sprache bedeutet, dass es keinen Zeiger für ein Objekt / eine Variable gibt.

DBNull fehlende Daten: http://msdn.microsoft.com/en-us/library/system.dbnull.value.aspx

Die Auswirkungen fehlender Daten auf die Statistik:

http://en.wikipedia.org/wiki/Missing_values

jvdbogae
quelle
1
Was ist Nullable<int>dann? Es ist ein Werttyp, keine Zeiger beteiligt. Vielleicht ist es dann ein Fehler?
Vilx
Und auch - was ist ein "kein Zeiger für ein Objekt", wenn keine Daten fehlen?
Vilx
3
@Vilx, nullbare Typen gab es in .NET 1.0 / 1.1 nicht, daher war eine andere Art der Darstellung von Nullwerten erforderlich
Thomas Levesque
1
@ Thomas Levesque - Ihr Kommentar ist es wert, eine Antwort zu sein, würde ich sagen. Obwohl es immer noch nicht erklärt, warum eine einfache "Null" nicht genauso gut hätte verwendet werden können. Um eine Variable zu erstellen, die sowohl einen Werttyp als auch DBNull enthalten kann, müssen Sie sie trotzdem zu einem "System.Object" machen.
Vilx
3
@ Thomas, dass Logik nicht funktioniert; um einen "Wert oder DBNull" zu speichern, den wir verwenden müssen object- das bleibt in 2.0+ der Fall; objectkann nullperfekt gut gespeichert werden - so dass auch bei Verwendung objectdie DBNull.Valuenoch überflüssig ist
Marc Gravell
0

Es gibt einige Unterschiede zwischen einer CLR-Null und einer DBNull. Erstens hat null in relationalen Datenbanken eine andere "gleiche" Semantik: null ist nicht gleich null. CLR null ist gleich null.

Ich vermute jedoch, dass der Hauptgrund in der Funktionsweise der Standardwerte der Parameter in SQL Server und der Implementierung des Anbieters liegt.

Um den Unterschied zu erkennen, erstellen Sie eine Prozedur mit einem Parameter, der einen Standardwert hat:

CREATE PROC [Echo] @s varchar(MAX) = 'hello'
AS
    SELECT @s [Echo]

Gut strukturierter DAL-Code sollte die Befehlserstellung von der Verwendung trennen (um die mehrfache Verwendung desselben Befehls zu ermöglichen, z. B. um eine gespeicherte Prozedur mehrmals effizient aufzurufen). Schreiben Sie eine Methode, die einen SqlCommand zurückgibt, der die obige Prozedur darstellt:

SqlCommand GetEchoProc()
{
    var cmd = new SqlCommand("Echo");
    cmd.Parameters.Add("@s", SqlDbType.VarChar);
    return cmd;
}

Wenn Sie den Befehl jetzt aufrufen, ohne den Parameter @s zu setzen, oder seinen Wert auf (CLR) null setzen, wird der Standardwert 'hello' verwendet. Wenn Sie andererseits den Parameterwert auf DBNull.Value setzen, wird dieser verwendet und DbNull.Value wiedergegeben.

Da es zwei unterschiedliche Ergebnisse gibt, bei denen CLR null oder database null als Parameterwert verwendet werden, können Sie nicht beide Fälle mit nur einem von ihnen darstellen. Wenn CLR null die einzige sein sollte, müsste es so funktionieren wie DBNull.Value heute. Eine Möglichkeit, dem Anbieter anzuzeigen, dass ich den Standardwert verwenden möchte, könnte darin bestehen, den Parameter überhaupt nicht zu deklarieren (ein Parameter mit einem Standardwert ist natürlich sinnvoll, ihn als "optionalen Parameter" zu beschreiben), sondern in a In einem Szenario, in dem das Befehlsobjekt zwischengespeichert und wiederverwendet wird, wird der Parameter entfernt und erneut hinzugefügt.

Ich bin mir nicht sicher, ob ich DBNull für eine gute Idee halte oder nicht, aber viele Leute wissen nichts von den Dingen, die ich hier erwähnt habe, und ich fand es erwähnenswert.

Der Tag
quelle
1
Ich habe jetzt auch etwas gelernt: DBNull hat nicht die gleiche Semantik wie Datenbank-Nullen. Der zweite Punkt bleibt jedoch bestehen - DBNull oder null funktionieren wirklich anders als Parameterwerte.
Der Tag
1
Ich denke, es ist allgemein bekannt, dass die DBNull- und Null-Diskrepanz ursprünglich existierte, weil .NET und SQL Server ursprünglich von getrennten Teams erstellt wurden. Es ist fraglich, ob die beiden Nullen jetzt als Vermächtnis dieser getrennten Teams existieren oder als etwas, das tatsächlich mehr als nur Ärger bringt.
1c1cle
0

Um Ihre Frage zu beantworten, müssen Sie sich überlegen, warum es DBNull überhaupt gibt.

DBNull ist in einem engen Anwendungsfall erforderlich. Sonst ist es nicht. Die meisten Leute brauchen nie DBNull. Ich erlaube niemals, dass sie in Datenspeicher eingegeben werden, die ich entwerfe. Ich habe IMMER einen Wert, daher sind meine Daten niemals "<null>", ich wähle immer einen Typ mit aussagekräftigem 'Standardwert' und ich muss diese absurde Doppelprüfung niemals zweimal im Code durchführen, einmal für ist das Objekt null, und wieder für ist es DBNull, bevor ich mein Objekt in meinen tatsächlichen Datentyp (wie Int usw.) umwandle.

DBNull ist in einem Fall erforderlich, den Sie möglicherweise benötigen ... wenn Sie einige der SQL-Statistikfunktionen verwenden (z. B. Median, Durchschnitt). Sie behandeln DBNull speziell. Sehen Sie sich diese Dokumente an. Einige Funktionen enthalten kein DBNull in der Gesamtzahl für die Statistik ... zB: (87 Summe / 127 Summe) vs. (87 Summe / 117 Summe) .. Der Unterschied besteht darin, dass 10 dieser Spaltenwerte DBNull waren ... Sie können sehen, dass sich dies ändern würde das statistische Ergebnis.

Ich muss meine Datenbanken nicht mit DBNull entwerfen. Wenn ich jemals statistische Ergebnisse benötigte, würde ich explizit eine Spalte wie 'UserDidProvideValue' für dieses eine Element erfinden oder hinzufügen, das eine spezielle Behandlung benötigt, weil es nicht existiert (z. B. wäre meine Summe von 117 die Summe der markierten Felder UserDidProvideValue = true) ... haha ​​lol - in meinem nächsten Leben als Herrscher des Universums lol - DBNull hätte niemals den SQL-Bereich verlassen dürfen ... die gesamte Programmierwelt ist jetzt belastet, alles zweimal zu überprüfen ... wann Hatten Sie jemals eine mobile App, eine Desktop-App oder eine Website, für die eine Ganzzahl "null" erforderlich ist? - Noch nie...

user2014638
quelle
Oooh, gehst du zum Nekromantenabzeichen? :) Ich sehe mehr als einen guten Anwendungsfall für den nullWert in DB. Tatsächlich wünschte ich mir manchmal, ich könnte mehr als einen speziellen Wert definieren.
Vilx
Hier ist ein sehr häufiger Anwendungsfall: optionale Fremdschlüssel. Wenn es einen Wert gibt (sogar einen Standardwert), benötigen Sie einen Datensatz in der Remote-Tabelle. Für Nullwerte ist jedoch kein Datensatz in der Remote-Tabelle erforderlich. Sie könnten natürlich sagen - "OK, aber dann erstellen Sie diesen Standarddatensatz in der Remote-Tabelle mit einer festen ID". Aber dann müssen Sie DB-IDs in Ihrem Anwendungscode fest codieren, und Sie müssen auch daran denken, diesen Datensatz überall dort zu überspringen, wo Sie die Werte aus dieser Remote-Tabelle auflisten.
Vilx
Ich sehe, wie fast jeder nullWert durch Aufteilen einer Spalte in zwei entfernt werden kann - einer würde "Null" (und vielleicht sogar andere spezielle Werte, wie ich oben wollte) und die anderen tatsächlichen Daten (die nur verwendet werden, wenn "Null" verwendet wird) enthalten. ist false). Aber wäre das besser? Ich bin mir nicht sicher ... mein erster Instinkt besagt, dass IFsder Code und CASEsdas SQL viel, viel mehr enthalten würden, da ich jetzt zwei Spalten anstelle einer betrachten muss. Und ich würde viele CHECKEinschränkungen benötigen , um die Datenintegrität sicherzustellen.
Vilx