Esta pregunta surge ocasionalmente, pero no he visto una respuesta satisfactoria.
Un patrón típico es (la fila es una DataRow ):
if (row["value"] != DBNull.Value)
{
someObject.Member = row["value"];
}
Mi primera pregunta es cuál es más eficiente (he cambiado la condición):
row["value"] == DBNull.Value; // Or
row["value"] is DBNull; // Or
row["value"].GetType() == typeof(DBNull) // Or... any suggestions?
Esto indica que .GetType () debería ser más rápido, pero ¿tal vez el compilador conoce algunos trucos que yo no?
Segunda pregunta, ¿vale la pena almacenar en caché el valor de la fila ["valor"] o el compilador optimiza el indexador de todos modos?
Por ejemplo:
object valueHolder;
if (DBNull.Value == (valueHolder = row["value"])) {}
Notas:
- la fila ["valor"] existe.
- No sé el índice de columna de la columna (de ahí la búsqueda del nombre de la columna).
- Estoy preguntando específicamente sobre la comprobación de DBNull y luego la asignación (no sobre la optimización prematura, etc.).
Comparé algunos escenarios (tiempo en segundos, 10,000,000 pruebas):
row["value"] == DBNull.Value: 00:00:01.5478995
row["value"] is DBNull: 00:00:01.6306578
row["value"].GetType() == typeof(DBNull): 00:00:02.0138757
Object.ReferenceEquals tiene el mismo rendimiento que "=="
¿El resultado más interesante? Si no coincide el nombre de la columna por caso (por ejemplo, "Valor" en lugar de "valor", se tarda aproximadamente diez veces más (para una cadena):
row["Value"] == DBNull.Value: 00:00:12.2792374
La moraleja de la historia parece ser que si no puede buscar una columna por su índice, asegúrese de que el nombre de la columna que alimenta al indexador coincida exactamente con el nombre de la columna de datos.
El almacenamiento en caché del valor también parece ser casi el doble de rápido:
No Caching: 00:00:03.0996622
With Caching: 00:00:01.5659920
Entonces, el método más eficiente parece ser:
object temp;
string variable;
if (DBNull.Value != (temp = row["value"]))
{
variable = temp.ToString();
}
IDataRecord
extensiones.Respuestas:
Debo estar perdiendo algo. ¿No está comprobando
DBNull
exactamente qué hace elDataRow.IsNull
método?He estado usando los siguientes dos métodos de extensión:
Uso:
Si no desea
Nullable<T>
valores de retorno paraGetValue<T>
, podría devolver fácilmentedefault(T)
o alguna otra opción en su lugar.En una nota no relacionada, aquí hay una alternativa VB.NET a la sugerencia de Stevo3000:
fuente
row.IsNull(columnName)
ya lo está leyendo una vez y lo está leyendo nuevamente. No es que que hará una diferencia, pero en teoría puede ser menos eficiente ..System.Data.DataSetExtensions.DataRowExtensions.Field<T>(this System.Data.DataRow, string)
haciendo esencialmente lo mismo que el primer método?Deberías usar el método:
Teniendo en cuenta que está integrado en el Marco, espero que sea el más eficiente.
Sugeriría algo en la línea de:
Y sí, el compilador debería almacenarlo en caché.
fuente
El compilador no optimizará el indexador (es decir, si usa la fila ["valor"] dos veces), entonces sí, es un poco más rápido:
y luego usa el valor dos veces; el uso de .GetType () plantea problemas si es nulo ...
DBNull.Value
en realidad es un singleton, así que para agregar una cuarta opción, quizás podría usar ReferenceEquals, pero en realidad, creo que se está preocupando demasiado aquí ... No creo que la velocidad sea diferente entre "es", "== "etc va a ser la causa de cualquier problema de rendimiento que esté viendo. Perfile todo su código y concéntrese en algo importante ... no será esto.fuente
Usaría el siguiente código en C # ( VB.NET no es tan simple).
El código asigna el valor si no es nulo / DBNull; de lo contrario, asigna el valor predeterminado que podría establecerse al valor LHS permitiendo que el compilador ignore la asignación.
fuente
oSomeObject.IntMember = If(TryCast(oRow("Value), Integer?), iDefault)
.TryCast
no proporciona la misma funcionalidad conveniente que elas
operador de C # para losNullable(Of T)
tipos. La forma más cercana en que puedo pensar para imitar esto es escribir su propia función, como ya he sugerido en mi respuesta.Siento que muy pocos enfoques aquí no arriesgan al prospecto OP la mayor preocupación (Marc Gravell, Stevo3000, Richard Szalay, Neil, Darren Koppand) y la mayoría son innecesariamente complejos. Siendo plenamente consciente de que esta es una microoptimización inútil, déjame decirte que básicamente debes emplear estos:
1) No lea el valor de DataReader / DataRow dos veces, por lo tanto, almacénelo en caché antes de verificaciones nulas y conversiones / conversiones, o incluso mejor pase directamente su
record[X]
objeto a un método de extensión personalizado con la firma adecuada.2) Para obedecer lo anterior, no use la
IsDBNull
función incorporada en su DataReader / DataRow ya que eso llamarecord[X]
internamente, por lo que en efecto lo hará dos veces.3) La comparación de tipos siempre será más lenta que la comparación de valores como regla general. Solo hazlo
record[X] == DBNull.Value
mejor.4) El lanzamiento directo será más rápido que llamar a la
Convert
clase para la conversión, aunque me temo que este último fallará menos.5) Por último, acceder al registro por índice en lugar de nombre de columna será más rápido nuevamente.
Siento que seguir los enfoques de Szalay, Neil y Darren Koppand será mejor. Particularmente me gusta el método del método de extensión de Darren Koppand que toma
IDataRecord
(aunque me gustaría limitarlo aún másIDataReader
) y el nombre del índice / columna.Tenga cuidado de llamarlo:
y no
en caso de que necesite diferenciar entre
0
yDBNull
. Por ejemplo, si tiene valores nulos en los campos de enumeración, de lo contrariodefault(MyEnum)
corre el riesgo de que se devuelva el primer valor de enumeración. Así que mejor llamarecord.GetColumnValue<MyEnum?>("Field")
.Puesto que usted está leyendo de una
DataRow
, me gustaría crear método de extensión para ambosDataRow
yIDataReader
por secado código común.Así que ahora llámalo así:
Creo que así es como debería haber sido en el marco (en lugar de los métodos
record.GetInt32
,record.GetString
etc.) en primer lugar: sin excepciones en tiempo de ejecución y nos da la flexibilidad para manejar valores nulos.Según mi experiencia, tuve menos suerte con un método genérico para leer de la base de datos. Siempre tenía para manejar varios tipos de encargo, así que tuve que escribir mi propio
GetInt
,GetEnum
,GetGuid
métodos, etc., en el largo plazo. ¿Qué sucede si desea recortar espacios en blanco al leer cadenas de db por defecto, o tratarlasDBNull
como una cadena vacía? O si su decimal se debe truncar de todos los ceros finales. Tuve más problemas con elGuid
tipo donde los diferentes controladores de conector se comportaban de manera diferente cuando las bases de datos subyacentes pueden almacenarlos como cadenas o binarios. Tengo una sobrecarga como esta:Con el enfoque de Stevo3000, encuentro la llamada un poco fea y tediosa, y será más difícil hacer una función genérica.
fuente
Existe el caso problemático en el que el objeto podría ser una cadena. El siguiente código de método de extensión maneja todos los casos. Así es como lo usarías:
fuente
Personalmente estoy a favor de esta sintaxis, que utiliza el método explícito IsDbNull expuesto por
IDataRecord
, y almacena en caché el índice de la columna para evitar una búsqueda de cadena duplicada.Ampliado para facilitar la lectura, es algo así como:
Reescrito para caber en una sola línea para compacidad en el código DAL; tenga en cuenta que en este ejemplo estamos asignando
int bar = -1
sirow["Bar"]
es nulo.La asignación en línea puede ser confusa si no sabe que está allí, pero mantiene toda la operación en una línea, lo que creo que mejora la legibilidad cuando está completando propiedades de varias columnas en un bloque de código.
fuente
No es que haya hecho esto, pero podría evitar la llamada de doble indexador y aún así mantener limpio su código utilizando un método estático / de extensión.
Es decir.
Luego:
También tiene la ventaja de mantener la lógica de comprobación nula en un solo lugar. Lo malo es, por supuesto, que es una llamada de método adicional.
Solo un pensamiento.
fuente
Intento evitar este control tanto como sea posible.
Obviamente, no es necesario hacerlo para las columnas que no pueden sostenerse
null
.Si está almacenando en un tipo de valor Anulable (
int?
, etc.), puede convertir usandoas int?
.Si no necesita diferenciar entre
string.Empty
ynull
, simplemente puede llamar.ToString()
, ya que DBNull regresarástring.Empty
.fuente
Yo siempre uso:
Lo encontré breve y completo.
fuente
Así es como manejo la lectura de DataRows
Ejemplo de uso:
Accesorios para Monsters Got My .Net para el código ChageTypeTo.
fuente
He hecho algo similar con los métodos de extensión. Aquí está mi código:
Para usarlo, harías algo como
fuente
si en una DataRow la fila ["fieldname"] esDbNull, reemplácela por 0; de lo contrario, obtenga el valor decimal:
fuente
usar así
fuente
Tengo IsDBNull en un programa que lee muchos datos de una base de datos. Con IsDBNull carga datos en unos 20 segundos. Sin IsDBNull, aproximadamente 1 segundo.
Entonces creo que es mejor usar:
fuente