public static class DataRecordExtensions
{
public static bool HasColumn(this IDataRecord dr, string columnName)
{
for (int i=0; i < dr.FieldCount; i++)
{
if (dr.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase))
return true;
}
return false;
}
}
Usar Exception
s para la lógica de control como en otras respuestas se considera una mala práctica y tiene costos de rendimiento. También envía falsos positivos al perfilador de # excepciones lanzadas y que Dios ayude a cualquiera que configure su depurador para romper las excepciones lanzadas.
GetSchemaTable () también es otra sugerencia en muchas respuestas. Esta no sería una forma preferida de verificar la existencia de un campo, ya que no está implementado en todas las versiones (es abstracto y genera NotSupportedException en algunas versiones de dotnetcore). GetSchemaTable también es excesivo en cuanto a rendimiento, ya que es una función bastante pesada si revisa la fuente .
Recorrer los campos puede tener un pequeño impacto en el rendimiento si lo usa mucho y es posible que desee considerar el almacenamiento en caché de los resultados.
Es mucho mejor usar esta función booleana:Una llamada, sin excepciones. Puede arrojar excepciones internamente, pero no lo creo.NOTA: En los comentarios a continuación, descubrimos esto ... el código correcto es en realidad esto:
fuente
Creo que su mejor opción es llamar a GetOrdinal ("columnName") en su DataReader por adelantado, y tomar una IndexOutOfRangeException en caso de que la columna no esté presente.
De hecho, hagamos un método de extensión:
Editar
Ok, esta publicación está comenzando a obtener algunos votos negativos últimamente, y no puedo eliminarla porque es la respuesta aceptada, así que voy a actualizarla y (espero) tratar de justificar el uso del manejo de excepciones como flujo de control.
La otra forma de lograr esto, según lo publicado por Chad Grant , es recorrer cada campo en el DataReader y hacer una comparación entre mayúsculas y minúsculas para el nombre del campo que está buscando. Esto funcionará realmente bien y, sinceramente, probablemente funcionará mejor que mi método anterior. Ciertamente, nunca usaría el método anterior dentro de un ciclo donde el rendimiento fue un problema.
Puedo pensar en una situación en la que el método try / GetOrdinal / catch funcionará donde el bucle no funciona. Sin embargo, es una situación completamente hipotética en este momento, por lo que es una justificación muy endeble. De todos modos, tenga paciencia conmigo y vea lo que piensa.
Imagine una base de datos que le permite "alias" columnas dentro de una tabla. Imagine que podría definir una tabla con una columna llamada "EmployeeName", pero también darle un alias de "EmpName", y al hacer una selección para cualquiera de los nombres devolvería los datos en esa columna. Conmigo hasta ahora?
Ahora imagine que hay un proveedor de ADO.NET para esa base de datos, y han codificado una implementación de IDataReader que tiene en cuenta los alias de columna.
Ahora,
dr.GetName(i)
(como se usa en la respuesta de Chad) solo puede devolver una sola cadena, por lo que tiene que devolver solo uno de los "alias" en una columna. Sin embargo,GetOrdinal("EmpName")
podría usar la implementación interna de los campos de este proveedor para verificar el alias de cada columna para el nombre que está buscando.En esta situación hipotética de "columnas con alias", el método try / GetOrdinal / catch sería la única forma de asegurarse de que está comprobando cada variación del nombre de una columna en el conjunto de resultados.
¿Endeble? Por supuesto. Pero vale la pena pensarlo. Sinceramente, prefiero un método HasColumn "oficial" en IDataRecord.
fuente
En una línea, use esto después de su recuperación de DataReader:
Luego,
Editar
Una línea mucho más eficiente que no requiere cargar el esquema:
fuente
Aquí hay una muestra de trabajo para la idea de Jasmin:
fuente
esto funciona para mi:
fuente
Lo siguiente es simple y funcionó para mí:
fuente
Si leyó la pregunta, Michael preguntó sobre DataReader, no sobre DataRecord. Consigue tus objetos correctos.
Usando un
r.GetSchemaTable().Columns.Contains(field)
en un DataRecord funciona, pero devuelve columnas BS (vea la captura de pantalla a continuación).Para ver si existe una columna de datos Y contiene datos en un DataReader, use las siguientes extensiones:
Uso:
Llamar
r.GetSchemaTable().Columns
a un DataReader devuelve columnas BS:fuente
IDataReader
implementaIDataRecord
. Son diferentes interfaces del mismo objeto - al igual queICollection<T>
yIEnumerable<T>
son diferentes interfaces deList<T>
.IDataReader
permite avanzar al siguiente registro, mientras queIDataRecord
permite leer desde el registro actual. Los métodos que se utilizan en esta respuesta provienen de laIDataRecord
interfaz. Consulte stackoverflow.com/a/1357743/221708 para obtener una explicación de por quéIDataRecord
es preferible declarar el parámetro .r.GetSchemaTable().Columns
es una respuesta absolutamente incorrecta a esta pregunta.Escribí para usuarios de Visual Basic:
Creo que esto es más poderoso y el uso es:
fuente
Aquí hay una versión linq one liner de la respuesta aceptada:
fuente
Aquí la solución de Jasmine en una línea ... (¡una más, aunque simple!):
fuente
fuente
TLDR:
Muchas respuestas con afirmaciones sobre el rendimiento y las malas prácticas, así que lo aclaro aquí.
La ruta de excepción es más rápida para un mayor número de columnas devueltas, la ruta del bucle es más rápida para un menor número de columnas y el punto de cruce es de alrededor de 11 columnas. Desplácese hacia abajo para ver un gráfico y un código de prueba.
Respuesta completa:
El código para algunas de las principales respuestas funciona, pero hay un debate subyacente aquí para la "mejor" respuesta basada en la aceptación del manejo de excepciones en la lógica y su rendimiento relacionado.
Para aclarar eso, no creo que haya mucha guía con respecto a las excepciones de CATCHING. Microsoft tiene alguna orientación con respecto a LANZAR excepciones. Allí dicen:
La primera nota es la indulgencia de "si es posible". Más importante aún, la descripción da este contexto:
Lo que eso significa es que si está escribiendo una API que podría ser consumida por otra persona, bríndeles la capacidad de navegar una excepción sin un intento / captura. Por ejemplo, proporcione un TryParse con su método Parse de lanzamiento de excepciones. Sin embargo, en ninguna parte esto dice que no deberías atrapar una excepción.
Además, como señala otro usuario, las capturas siempre han permitido el filtrado por tipo y, de alguna manera, recientemente permiten un mayor filtrado a través de la cláusula when . Esto parece un desperdicio de características del lenguaje si no se supone que las usemos.
Se puede decir que hay ALGUNOS costos para una excepción lanzada, y ese costo PUEDE afectar el rendimiento en un ciclo pesado. Sin embargo, también se puede decir que el costo de una excepción será insignificante en una "aplicación conectada". El costo real se investigó hace más de una década: https://stackoverflow.com/a/891230/852208 En otras palabras, el costo de una conexión y consulta de una base de datos probablemente eclipsará el de una excepción lanzada.
Aparte de eso, quería determinar qué método es realmente más rápido. Como se esperaba, no hay una respuesta concreta.
Cualquier código que recorra las columnas se vuelve más lento a medida que existe el número de columnas. También se puede decir que cualquier código que se base en excepciones se ralentizará dependiendo de la velocidad con la que no se encuentre la consulta.
Tomando las respuestas de Chad Grant y Matt Hamilton, ejecuté ambos métodos con hasta 20 columnas y una tasa de error de hasta el 50% (el OP indicó que estaba usando estas dos pruebas entre diferentes procesos, por lo que supuse que solo dos) .
Aquí están los resultados, trazados con LinqPad:
Los zigzags aquí son tasas de fallas (columna no encontrada) dentro de cada recuento de columnas.
En conjuntos de resultados más estrechos, el bucle es una buena opción. Sin embargo, el método GetOrdinal / Exception no es tan sensible al número de columnas y comienza a superar el método de bucle alrededor de 11 columnas.
Dicho esto, realmente no tengo un rendimiento de preferencia sabio ya que 11 columnas suenan razonables ya que un número promedio de columnas devueltas en una aplicación completa. En cualquier caso, estamos hablando de fracciones de milisegundos aquí.
Sin embargo, desde un aspecto de simplicidad de código y soporte de alias, probablemente iría con la ruta GetOrdinal.
Aquí está la prueba en forma de linqpad. Siéntase libre de volver a publicar con su propio método:
fuente
Este código corrige los problemas que Levitikon tenía con su código: (adaptado de: [1]: http://msdn.microsoft.com/en-us/library/system.data.datatablereader.getschematable.aspx )
La razón para obtener todos esos nombres de columna inútiles y no el nombre de la columna de su tabla ... es porque está obteniendo el nombre de la columna de esquema (es decir, los nombres de columna para la tabla de Esquema)
NOTA: parece que solo devuelve el nombre de la primera columna ...
EDITAR: código corregido que devuelve el nombre de todas las columnas, pero no puede usar un SqlDataReader para hacerlo
fuente
return r.GetSchemaTable().Rows.Cast<DataRow>().Select(x => (string)x["ColumnName"]).ToList();
:)Para mantener su código robusto y limpio, use una función de extensión única, como esta:
fuente
Tampoco llegué
GetSchemaTable
a trabajar, hasta que encontré este camino .Básicamente hago esto:
fuente
Columns.Contains
Por cierto, no distingue entre mayúsculas y minúsculas.fuente
En su situación particular (todos los procedimientos tienen las mismas columnas excepto 1 que tiene 1 columna adicional), será mejor y más rápido verificar el lector. Propiedad FieldCount para distinguir entre ellos.
Sé que es una publicación antigua, pero decidí responder para ayudar a otros en la misma situación. También puede (por razones de rendimiento) mezclar esta solución con la solución iterativa de la solución.
fuente
Mi clase de acceso a datos debe ser compatible con versiones anteriores, por lo que podría estar intentando acceder a una columna en una versión donde aún no existe en la base de datos. Tenemos algunos conjuntos de datos bastante grandes que se devuelven, así que no soy un gran admirador de un método de extensión que tiene que iterar la colección de columnas DataReader para cada propiedad.
Tengo una clase de utilidad que crea una lista privada de columnas y luego tiene un método genérico que intenta resolver un valor basado en el nombre de una columna y el tipo de parámetro de salida.
Entonces puedo llamar a mi código así
fuente
La clave de todo el problema está aquí :
Si las tres líneas a las que se hace referencia (actualmente las líneas 72, 73 y 74) se eliminan, puede verificarlas fácilmente para
-1
determinar si la columna no existe.La única forma de evitar esto mientras se garantiza el rendimiento nativo es utilizar una
Reflection
implementación basada, como la siguiente:Usos
El método de extensión basado en Reflection:
fuente
También puede llamar a GetSchemaTable () en su DataReader si desea la lista de columnas y no desea obtener una excepción ...
fuente
Qué tal si
Probablemente no sería tan eficiente en un bucle
fuente
dr.GetSchemaTable().Columns
contiene: no es lo que está buscando.Aunque no existe un método expuesto públicamente, existe un método en la clase interna
System.Data.ProviderBase.FieldNameLookup
queSqlDataReader
basa.Para acceder a él y obtener un rendimiento nativo, debe usar el ILGenerator para crear un método en tiempo de ejecución. El siguiente código le dará acceso directo a
int IndexOf(string fieldName)
laSystem.Data.ProviderBase.FieldNameLookup
clase, así como realizar la contabilidad que loSqlDataReader.GetOrdinal()
hace para que no haya efectos secundarios. El código generado refleja el existenteSqlDataReader.GetOrdinal()
excepto que llama enFieldNameLookup.IndexOf()
lugar deFieldNameLookup.GetOrdinal()
. ElGetOrdinal()
método llama a laIndexOf()
función y genera una excepción si-1
se devuelve, por lo que omitimos ese comportamiento.fuente
este trabajo para mi
fuente
puede obtener más detalles desde aquí: ¿Puede obtener los nombres de columna de un SqlDataReader?
fuente