.net history dbnull rationale

.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.



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 un object puede contener DBNull u otro valor
  • no hay una diferencia real entre "podría ser un valor o DBNull " vs "podría ser un valor o null "
  • 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 o DataRow.IsNull - ninguno de los cuales realmente requiere que DBNull exista en términos de la API
  • DBNull falla en términos de nulo-coalescencia; value ?? defaultValue value ?? defaultValue no funciona si el valor es DBNull
  • 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 de null ; en particular, DBNull realidad es igual a DBNull , 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 por null
  • 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) sintaxis if(value is DBNull) que "se ve como SQL", en lugar de la oh-tan-tricky if(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.