.net - ¿Cuál es el punto de DBNull?
history rationale (5)
En .NET existe la referencia null
, que se utiliza en todas partes para indicar que una referencia de objeto está vacía, y luego está el DBNull
, que es utilizado por los controladores de base de datos (y algunos otros) para denotar ... más o menos lo mismo . Naturalmente, esto crea mucha confusión y las rutinas de conversión tienen que ser batidas, etc.
Entonces, ¿por qué los autores originales de .NET decidieron hacer esto? Para mí no tiene sentido. Su documentación tampoco tiene sentido:
La clase DBNull representa un valor inexistente. En una base de datos, por ejemplo, una columna en una fila de una tabla puede no contener ningún dato. Es decir, se considera que la columna no existe en absoluto en lugar de simplemente no tener un valor. Un objeto DBNull representa la columna inexistente. Además, la interoperabilidad COM utiliza la clase DBNull para distinguir entre una variante VT_NULL, que indica un valor inexistente, y una variante VT_EMPTY, que indica un valor no especificado.
¿Qué es esta basura sobre una "columna que no existe"? Existe una columna, simplemente no tiene un valor para la fila en particular. Si no existiera, obtendría una excepción al tratar de acceder a la celda específica, ¡no a DBNull
! Puedo entender la necesidad de diferenciar entre VT_NULL
y VT_EMPTY
, pero ¿por qué no hacer una clase COMEmpty
lugar? Eso sería un ajuste mucho mejor en todo el framework .NET.
¿Me estoy perdiendo de algo? ¿Alguien puede arrojar algo de luz sobre por DBNull
se inventó DBNull
y qué problemas ayuda a resolver?
El punto es que en ciertas situaciones hay una diferencia entre un valor de base de datos que es nulo y un .NET nulo.
Por ejemplo. Si usa ExecuteScalar (que devuelve la primera columna de la primera fila en el conjunto de resultados) y obtiene un valor nulo, eso significa que el SQL ejecutado no devolvió ningún valor. Si recuperas DBNull, significa que el SQL devolvió un valor y fue NULL. Debes poder notar la diferencia.
Hay algunas diferencias entre un CLR nulo y un DBNull. En primer lugar, la nula en las bases de datos relacionales tiene una semántica "igual" diferente: null no es igual a nulo. CLR null IS igual a nulo.
Pero sospecho que la razón principal tiene que ver con la manera en que los valores predeterminados de los parámetros funcionan en el servidor SQL y la implementación del proveedor.
Para ver la diferencia, crea un procedimiento con un parámetro que tenga un valor predeterminado:
CREATE PROC [Echo] @s varchar(MAX) = ''hello''
AS
SELECT @s [Echo]
El código DAL bien estructurado debe separar la creación del comando del uso (para permitir el uso del mismo comando muchas veces, por ejemplo para invocar un procedimiento almacenado muchas veces de manera eficiente). Escriba un método que devuelva un SqlCommand que represente el procedimiento anterior:
SqlCommand GetEchoProc()
{
var cmd = new SqlCommand("Echo");
cmd.Parameters.Add("@s", SqlDbType.VarChar);
return cmd;
}
Si ahora invoca el comando sin establecer el parámetro @s, o establece su valor en (CLR) nulo, usará el valor predeterminado ''hello''. Si, por otro lado, establece el valor del parámetro en DBNull.Value, usará eso y echo DbNull.Value.
Dado que hay dos resultados diferentes que utilizan CLR nulo o nulo de base de datos como valor de parámetro, no puede representar ambos casos con solo uno de ellos. Si CLR null fuera a ser el único, tendría que funcionar como lo hace DBNull.Value hoy. Una forma de indicarle al proveedor "Quiero usar el valor predeterminado" podría ser no declarar el parámetro en absoluto (un parámetro con un valor predeterminado por supuesto tiene sentido describirlo como un "parámetro opcional"), pero en una Escenario donde el objeto de comando se almacena en caché y se reutiliza, esto lleva a eliminar y volver a agregar el parámetro.
No estoy seguro de si creo que DBNull fue una buena idea o no, pero mucha gente desconoce las cosas que mencioné aquí, así que pensé que valía la pena mencionarlo.
Utiliza DBNull para datos faltantes. Nulo en el lenguaje .NET significa que no hay un puntero para un objeto / variable.
Datos faltantes de DBNull: http://msdn.microsoft.com/en-us/library/system.dbnull.value.aspx
Los efectos de los datos faltantes en las estadísticas:
Voy a estar en desacuerdo con la tendencia aquí. Voy a grabar:
No estoy de acuerdo con que
DBNull
tenga un propósito útil; agrega confusión innecesaria, mientras que prácticamente no aporta ningún valor.
El argumento a menudo se presenta que null
es una referencia inválida, y que DBNull
es un patrón de objeto nulo; ninguno es verdad Por ejemplo:
int? x = null;
esto no es una "referencia inválida"; es un valor null
De hecho, null
significa lo que quiera que signifique, y francamente no tengo absolutamente ningún problema para trabajar con valores que pueden ser null
(de hecho, incluso en SQL tenemos que trabajar correctamente con null
, nada cambia aquí). Igualmente, el "patrón de objeto nulo" solo tiene sentido si realmente lo está tratando como un objeto en términos de OOP, pero si tenemos un valor que puede ser "nuestro valor, o un DBNull
", entonces debe ser un object
, por lo que podemos No hará nada útil con eso.
Hay tantas cosas malas con DBNull
:
- te obliga a trabajar con
object
, ya que solo unobject
puede contenerDBNull
u otro valor - no hay una diferencia real entre "podría ser un valor o
DBNull
" vs "podría ser un valor onull
" - el argumento de que se deriva de 1.1 (tipos pre-nulables) no tiene sentido; podríamos usar
null
perfectamente bien en 1.1 - la mayoría de las API tienen "¿es nulo?" métodos, por ejemplo
DBDataReader.IsDBNull
oDataRow.IsNull
- ninguno de los cuales realmente requiere queDBNull
exista en términos de la API -
DBNull
falla en términos de nulo-coalescencia;value ?? defaultValue
value ?? defaultValue
no funciona si el valor esDBNull
-
DBNull.Value
no se puede usar en parámetros opcionales, ya que no es una constante - la semántica de tiempo de ejecución de
DBNull
es idéntica a la semántica denull
; en particular,DBNull
realidad es igual aDBNull
, por lo que no hace el trabajo de representar la semántica de SQL - a menudo fuerza que se encasillen los valores del tipo de valor ya que usa en exceso el
object
- si necesita realizar una prueba para
DBNull
, es muy posible que haya probado solo pornull
- causa enormes problemas para cosas como parámetros de comando, con un comportamiento muy tonto que si un parámetro tiene un valor
null
no se envía ... bueno, aquí hay una idea: si no quieres que se envíe un parámetro, no lo hagas . t agregarlo a la colección de parámetros - cada ORM que puedo pensar funciona perfectamente sin ninguna necesidad o uso de
DBNull
, excepto como una molestia extra cuando hablo con el código ADO.NET
El único argumento, incluso remotamente convincente, que he visto para justificar la existencia de dicho valor, está en DataTable
, al pasar valores para crear una nueva fila; un null
significa "utilizar el valor predeterminado", un DBNull
es explícitamente nulo; francamente, esta API podría haber tenido un tratamiento específico para este caso; un DataRow.DefaultValue
imaginario, por ejemplo, sería mucho mejor que la introducción de un DBNull.Value
que infecta grandes extensiones de código sin razón.
Igualmente, el escenario ExecuteScalar
es ... tenue en el mejor de los casos; si está ejecutando un método escalar, espera un resultado. En el escenario donde no hay filas, devolver null
no parece demasiado terrible. Si es absolutamente necesario eliminar la ambigüedad entre "sin filas" y "se devuelve un único nulo", está la API del lector.
Este barco ha navegado hace mucho tiempo, y es demasiado tarde para arreglarlo. ¡Pero! Por favor, no crean que todos están de acuerdo en que esto es algo "obvio". Muchos desarrolladores no ven el valor en esta extraña arruga en el BCL.
De hecho, me pregunto si todo esto se debe a dos cosas:
- tener que usar la palabra
Nothing
lugar de algo que implica "null" en VB - pudiéndonos
if(value is DBNull)
sintaxisif(value is DBNull)
que "se ve como SQL", en lugar de la oh-tan-trickyif(value==null)
Resumen:
Tener 3 opciones ( null
, DBNull
o un valor real) solo es útil si hay un ejemplo genuino en el que se necesita desambiguar entre 3 casos diferentes. Todavía tengo que ver una ocasión en la que necesito representar dos estados "nulos" diferentes, por lo que DBNull
es completamente redundante dado que null
ya existe y tiene mucho mejor soporte de idioma y tiempo de ejecución.
DbNull
representa un cuadro sin contenido; null
indica la inexistencia de la caja.