Verificar el nombre de la columna en un objeto SqlDataReader

¿Cómo puedo verificar si existe una columna en un objeto SqlDataReader ? En mi capa de acceso a datos, he creado un método que crea el mismo objeto para múltiples llamadas a procedimientos almacenados. Uno de los procedimientos almacenados tiene una columna adicional que no es utilizada por otros procedimientos almacenados. Quiero modificar el método para adaptarlo a cada escenario.

Mi aplicación está escrita en C #.

El uso de Exception para la lógica de control, como en algunas otras respuestas, se considera una mala práctica y tiene costos de rendimiento.

El bucle en los campos puede tener un pequeño rendimiento si lo usa mucho y es posible que desee considerar el almacenamiento en caché de los resultados

La forma más adecuada de hacer esto es:

 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; } } 

Es mucho mejor usar esta función booleana:

 r.GetSchemaTable().Columns.Contains(field) 

Una llamada, sin excepciones. Podría arrojar excepciones internamente, pero no lo creo.

NOTA: En los comentarios a continuación, nos dimos cuenta de esto … el código correcto es en realidad esto:

 public static bool HasColumn(DbDataReader Reader, string ColumnName) { foreach (DataRow row in Reader.GetSchemaTable().Rows) { if (row["ColumnName"].ToString() == ColumnName) return true; } //Still here? Column not found. return false; } 

Creo que su mejor opción es llamar a GetOrdinal (“columnName”) en su DataReader por adelantado, y atrapar un IndexOutOfRangeException en caso de que la columna no esté presente.

De hecho, hagamos un método de extensión:

 public static bool HasColumn(this IDataRecord r, string columnName) { try { return r.GetOrdinal(columnName) >= 0; } catch (IndexOutOfRangeException) { return false; } } 

Editar

Ok, esta publicación está empezando a ganar unos pocos votos atrasados ​​últimamente, y no puedo eliminarla porque es la respuesta aceptada, así que la actualizaré y (espero) intentaré 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 insensible a mayúsculas / minúsculas para el nombre de campo que está buscando. Esto funcionará muy bien, y con toda seguridad probablemente funcione mejor que mi método anterior. Ciertamente, nunca utilizaría el método anterior dentro de un bucle donde el rendimiento era un problema.

Puedo pensar en una situación en la que el método try / GetOrdinal / catch funcione donde no lo hace el bucle. Es, sin embargo, una situación completamente hipotética en este momento, por lo que es una justificación muy endeble. De todos modos, tengan paciencia conmigo y vean lo que piensan.

Imagine una base de datos que le permita “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 dos nombres devolvería los datos en esa columna. Conmigo hasta ahora?

Ahora imagina que hay un proveedor de ADO.NET para esa base de datos, y ellos han codificado una implementación de IDataReader, que toma en cuenta los alias de la columna.

Ahora, dr.GetName(i) (como se usa en la respuesta de Chad) solo puede devolver una sola cadena, por lo que debe 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 alias”, el método try / GetOrdinal / catch sería la única forma de asegurarse de que está comprobando todas las variaciones del nombre de una columna en el conjunto de resultados.

¿Endeble? Por supuesto. Pero vale la pena pensar Honestamente, preferiría un método HasColumn “oficial” en IDataRecord.

En una línea, use esto después de su recuperación de DataReader:

 var fieldNames = Enumerable.Range(0, dr.FieldCount).Select(i => dr.GetName(i)).ToArray(); 

Entonces,

 if (fieldNames.Contains("myField")) { var myFieldValue = dr["myField"]; ... 

Editar

Mucho más eficiente que el que no requiere cargar el esquema:

 var exists = Enumerable.Range(0, dr.FieldCount).Any(i => string.Equals(dr.GetName(i), fieldName, StringComparison.OrdinalIgnoreCase)); 

Aquí hay una muestra de trabajo para la idea de Jasmin:

 var cols = r.GetSchemaTable().Rows.Cast().Select (row => row["ColumnName"] as string).ToList(); if (cols.Contains("the column name")) { } 

esto funciona para mí:

 bool hasColumnName = reader.GetSchemaTable().AsEnumerable().Any(c => c["ColumnName"] == "YOUR_COLUMN_NAME"); 

Lo siguiente es simple y funcionó para mí:

  bool hasMyColumn = (reader.GetSchemaTable().Select("ColumnName = 'MyColumnName'").Count() == 1); 

Si lees la pregunta, Michael preguntó sobre DataReader, no sobre la gente de DataRecord. Consigue tus objetos correctos.

El uso de un r.GetSchemaTable().Columns.Contains(field) en un DataRecord funciona, pero devuelve las columnas BS (consulte la captura de pantalla a continuación).

Para ver si existe una columna de datos Y contiene datos en un lector de datos, use las siguientes extensiones:

 public static class DataReaderExtensions { ///  /// Checks if a column's value is DBNull ///  /// The data reader /// The column name /// A bool indicating if the column's value is DBNull public static bool IsDBNull(this IDataReader dataReader, string columnName) { return dataReader[columnName] == DBNull.Value; } ///  /// Checks if a column exists in a data reader ///  /// The data reader /// The column name /// A bool indicating the column exists public static bool ContainsColumn(this IDataReader dataReader, string columnName) { /// See: http://stackoverflow.com/questions/373230/check-for-column-name-in-a-sqldatareader-object/7248381#7248381 try { return dataReader.GetOrdinal(columnName) >= 0; } catch (IndexOutOfRangeException) { return false; } } } 

Uso:

  public static bool CanCreate(SqlDataReader dataReader) { return dataReader.ContainsColumn("RoleTemplateId") && !dataReader.IsDBNull("RoleTemplateId"); } 

Llamar a r.GetSchemaTable().Columns en un DataReader devuelve columnas BS:

Llamar a GetSchemeTable en un DataReader

Escribí para usuarios de Visual Basic:

 Protected Function HasColumnAndValue(ByRef reader As IDataReader, ByVal columnName As String) As Boolean For i As Integer = 0 To reader.FieldCount - 1 If reader.GetName(i).Equals(columnName) Then Return Not IsDBNull(reader(columnName)) End If Next Return False End Function 

Creo que esto es más poderoso y el uso es:

 If HasColumnAndValue(reader, "ID_USER") Then Me.UserID = reader.GetDecimal(reader.GetOrdinal("ID_USER")).ToString() End If 

Aquí la solución de Jasmine en una línea … (¡una más, tan simple!):

 reader.GetSchemaTable().Select("ColumnName='MyCol'").Length > 0; 

Aquí hay una versión de line line de la respuesta aceptada:

 Enumerable.Range(0, reader.FieldCount).Any(i => reader.GetName(i) == "COLUMN_NAME_GOES_HERE") 
 Hashtable ht = new Hashtable(); Hashtable CreateColumnHash(SqlDataReader dr) { ht = new Hashtable(); for (int i = 0; i < dr.FieldCount; i++) { ht.Add(dr.GetName(i), dr.GetName(i)); } return ht; } bool ValidateColumn(string ColumnName) { return ht.Contains(ColumnName); } 

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 )

 public List GetColumnNames(SqlDataReader r) { List ColumnNames = new List(); DataTable schemaTable = r.GetSchemaTable(); DataRow row = schemaTable.Rows[0]; foreach (DataColumn col in schemaTable.Columns) { if (col.ColumnName == "ColumnName") { ColumnNames.Add(row[col.Ordinal].ToString()); break; } } return ColumnNames; } 

El motivo 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: esto parece solo devolver 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

 public List ExecuteColumnNamesReader(string command, List Params) { List ColumnNames = new List(); SqlDataAdapter da = new SqlDataAdapter(); string connection = ""; // your sql connection string SqlCommand sqlComm = new SqlCommand(command, connection); foreach (SqlParameter p in Params) { sqlComm.Parameters.Add(p); } da.SelectCommand = sqlComm; DataTable dt = new DataTable(); da.Fill(dt); DataRow row = dt.Rows[0]; for (int ordinal = 0; ordinal < dt.Columns.Count; ordinal++) { string column_name = dt.Columns[ordinal].ColumnName; ColumnNames.Add(column_name); } return ColumnNames; // you can then call .Contains("name") on the returned collection } 

Tampoco conseguí que GetSchemaTable funcionara, hasta que encontré de esta manera .

Básicamente hago esto:

 Dim myView As DataView = dr.GetSchemaTable().DefaultView myView.RowFilter = "ColumnName = 'ColumnToBeChecked'" If myView.Count > 0 AndAlso dr.GetOrdinal("ColumnToBeChecked") <> -1 Then obj.ColumnToBeChecked = ColumnFromDb(dr, "ColumnToBeChecked") End If 
 public static bool DataViewColumnExists(DataView dv, string columnName) { return DataTableColumnExists(dv.Table, columnName); } public static bool DataTableColumnExists(DataTable dt, string columnName) { string DebugTrace = "Utils::DataTableColumnExists(" + dt.ToString() + ")"; try { return dt.Columns.Contains(columnName); } catch (Exception ex) { throw new MyExceptionHandler(ex, DebugTrace); } } 

Columns.Contains no distingue entre mayúsculas y minúsculas por cierto.

Para mantener su código robusto y limpio, use una sola función de extensión, como esta:

  Public Module Extensions  Public Function HasColumn(r As SqlDataReader, columnName As String) As Boolean Return If(String.IsNullOrEmpty(columnName) OrElse r.FieldCount = 0, False, Enumerable.Range(0, r.FieldCount).Select(Function(i) r.GetName(i)).Contains(columnName, StringComparer.OrdinalIgnoreCase)) End Function End Module 

También puede llamar a GetSchemaTable () en su DataReader si desea la lista de columnas y no desea tener que obtener una excepción …

Estas respuestas ya están publicadas aquí. Solo Linq-ing un poco:

 bool b = reader.GetSchemaTable().Rows .Cast() .Select(x => (string)x["ColumnName"]) .Contains(colName, StringComparer.OrdinalIgnoreCase); //or bool b = Enumerable.Range(0, reader.FieldCount) .Select(reader.GetName) .Contains(colName, StringComparer.OrdinalIgnoreCase); 

El segundo es más limpio, y mucho más rápido. Incluso si no ejecuta GetSchemaTable cada vez en el primer acercamiento, la búsqueda va a ser muy lenta.

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.

 const int NormalColCount=..... if(reader.FieldCount > NormalColCount) { // Do something special } 

Sé que es una publicación anterior, 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.

Mi clase de acceso a datos necesita ser compatible con versiones anteriores, por lo que podría estar intentando acceder a una columna en un release donde todavía no existe en la base de datos. Tenemos algunos conjuntos de datos bastante grandes que se devuelven, por lo que no soy un gran admirador de un método de extensión que tiene que iterar la colección de columnas de 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 un nombre de columna y un tipo de parámetro de salida.

 private List _lstString; public void GetValueByParameter(IDataReader dr, string parameterName, out T returnValue) { returnValue = default(T); if (!_lstString.Contains(parameterName)) { Logger.Instance.LogVerbose(this, "missing parameter: " + parameterName); return; } try { if (dr[parameterName] != null && [parameterName] != DBNull.Value) returnValue = (T)dr[parameterName]; } catch (Exception ex) { Logger.Instance.LogException(this, ex); } } ///  /// Reset the global list of columns to reflect the fields in the IDataReader ///  /// The IDataReader being acted upon /// Advances IDataReader to next result public void ResetSchemaTable(IDataReader dr, bool nextResult) { if (nextResult) dr.NextResult(); _lstString = new List(); using (DataTable dataTableSchema = dr.GetSchemaTable()) { if (dataTableSchema != null) { foreach (DataRow row in dataTableSchema.Rows) { _lstString.Add(row[dataTableSchema.Columns["ColumnName"]].ToString()); } } } } 

Entonces puedo llamar a mi código como si fuera

 using (var dr = ExecuteReader(databaseCommand)) { int? outInt; string outString; Utility.ResetSchemaTable(dr, false); while (dr.Read()) { Utility.GetValueByParameter(dr, "SomeColumn", out outInt); if (outInt.HasValue) myIntField = outInt.Value; } Utility.ResetSchemaTable(dr, true); while (dr.Read()) { Utility.GetValueByParameter(dr, "AnotherColumn", out outString); if (!string.IsNullOrEmpty(outString)) myIntField = outString; } } 

Aunque no existe un método expuesto públicamente, existe un método en la clase interna System.Data.ProviderBase.FieldNameLookup que se basa SqlDataReader .

Para acceder a él y obtener un rendimiento nativo, debe usar ILGenerator para crear un método en tiempo de ejecución. El siguiente código le dará acceso directo a int IndexOf(string fieldName) en la clase System.Data.ProviderBase.FieldNameLookup , así como a realizar el mantenimiento de libros que SqlDataReader.GetOrdinal() hace para que no haya ningún efecto secundario. El código generado refleja el SqlDataReader.GetOrdinal() existente, excepto que llama a FieldNameLookup.IndexOf() lugar de FieldNameLookup.GetOrdinal() . El método GetOrdinal() llama a la función IndexOf() y lanza una excepción si se devuelve -1 , por lo que omitimos ese comportamiento.

 using System; using System.Data; using System.Data.SqlClient; using System.Reflection; using System.Reflection.Emit; public static class SqlDataReaderExtensions { private delegate int IndexOfDelegate(SqlDataReader reader, string name); private static IndexOfDelegate IndexOf; public static int GetColumnIndex(this SqlDataReader reader, string name) { return name == null ? -1 : IndexOf(reader, name); } public static bool ContainsColumn(this SqlDataReader reader, string name) { return name != null && IndexOf(reader, name) >= 0; } static SqlDataReaderExtensions() { Type typeSqlDataReader = typeof(SqlDataReader); Type typeSqlStatistics = typeSqlDataReader.Assembly.GetType("System.Data.SqlClient.SqlStatistics", true); Type typeFieldNameLookup = typeSqlDataReader.Assembly.GetType("System.Data.ProviderBase.FieldNameLookup", true); BindingFlags staticflags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Static; BindingFlags instflags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance; DynamicMethod dynmethod = new DynamicMethod("SqlDataReader_IndexOf", typeof(int), new Type[2]{ typeSqlDataReader, typeof(string) }, true); ILGenerator gen = dynmethod.GetILGenerator(); gen.DeclareLocal(typeSqlStatistics); gen.DeclareLocal(typeof(int)); // SqlStatistics statistics = (SqlStatistics) null; gen.Emit(OpCodes.Ldnull); gen.Emit(OpCodes.Stloc_0); // try { gen.BeginExceptionBlock(); // statistics = SqlStatistics.StartTimer(this.Statistics); gen.Emit(OpCodes.Ldarg_0); //this gen.Emit(OpCodes.Call, typeSqlDataReader.GetProperty("Statistics", instflags | BindingFlags.GetProperty, null, typeSqlStatistics, Type.EmptyTypes, null).GetMethod); gen.Emit(OpCodes.Call, typeSqlStatistics.GetMethod("StartTimer", staticflags | BindingFlags.InvokeMethod, null, new Type[] { typeSqlStatistics }, null)); gen.Emit(OpCodes.Stloc_0); //statistics // if(this._fieldNameLookup == null) { Label branchTarget = gen.DefineLabel(); gen.Emit(OpCodes.Ldarg_0); //this gen.Emit(OpCodes.Ldfld, typeSqlDataReader.GetField("_fieldNameLookup", instflags | BindingFlags.GetField)); gen.Emit(OpCodes.Brtrue_S, branchTarget); // this.CheckMetaDataIsReady(); gen.Emit(OpCodes.Ldarg_0); //this gen.Emit(OpCodes.Call, typeSqlDataReader.GetMethod("CheckMetaDataIsReady", instflags | BindingFlags.InvokeMethod, null, Type.EmptyTypes, null)); // this._fieldNameLookup = new FieldNameLookup((IDataRecord)this, this._defaultLCID); gen.Emit(OpCodes.Ldarg_0); //this gen.Emit(OpCodes.Ldarg_0); //this gen.Emit(OpCodes.Ldarg_0); //this gen.Emit(OpCodes.Ldfld, typeSqlDataReader.GetField("_defaultLCID", instflags | BindingFlags.GetField)); gen.Emit(OpCodes.Newobj, typeFieldNameLookup.GetConstructor(instflags, null, new Type[] { typeof(IDataReader), typeof(int) }, null)); gen.Emit(OpCodes.Stfld, typeSqlDataReader.GetField("_fieldNameLookup", instflags | BindingFlags.SetField)); // } gen.MarkLabel(branchTarget); gen.Emit(OpCodes.Ldarg_0); //this gen.Emit(OpCodes.Ldfld, typeSqlDataReader.GetField("_fieldNameLookup", instflags | BindingFlags.GetField)); gen.Emit(OpCodes.Ldarg_1); //name gen.Emit(OpCodes.Call, typeFieldNameLookup.GetMethod("IndexOf", instflags | BindingFlags.InvokeMethod, null, new Type[] { typeof(string) }, null)); gen.Emit(OpCodes.Stloc_1); //int output Label leaveProtectedRegion = gen.DefineLabel(); gen.Emit(OpCodes.Leave_S, leaveProtectedRegion); // } finally { gen.BeginFaultBlock(); // SqlStatistics.StopTimer(statistics); gen.Emit(OpCodes.Ldloc_0); //statistics gen.Emit(OpCodes.Call, typeSqlStatistics.GetMethod("StopTimer", staticflags | BindingFlags.InvokeMethod, null, new Type[] { typeSqlStatistics }, null)); // } gen.EndExceptionBlock(); gen.MarkLabel(leaveProtectedRegion); gen.Emit(OpCodes.Ldloc_1); gen.Emit(OpCodes.Ret); IndexOf = (IndexOfDelegate)dynmethod.CreateDelegate(typeof(IndexOfDelegate)); } } 

Qué tal si

 if (dr.GetSchemaTable().Columns.Contains("accounttype")) do something else do something 

Probablemente no sería tan eficiente en un bucle