executereader - sqldatareader c# ejemplos
Compruebe el nombre de la columna en un objeto SqlDataReader (22)
¿Cómo SqlDataReader
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 los otros procedimientos almacenados. Quiero modificar el método para adaptarse a cada escenario.
Mi solicitud está escrita en C #.
Aquí hay una muestra de trabajo para la idea de Jasmin:
var cols = r.GetSchemaTable().Rows.Cast<DataRow>().Select
(row => row["ColumnName"] as string).ToList();
if (cols.Contains("the column name"))
{
}
Aquí hay una versión linq de una línea de la respuesta aceptada:
Enumerable.Range(0, reader.FieldCount).Any(i => reader.GetName(i) == "COLUMN_NAME_GOES_HERE")
Aquí la solución de Jasmine en una línea ... (una más, aunque simple!):
reader.GetSchemaTable().Select("ColumnName=''MyCol''").Length > 0;
Aunque no hay 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 utilizar 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
, además de realizar el libro manteniendo que SqlDataReader.GetOrdinal()
hace que no haya efectos secundarios. 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));
}
}
Creo que lo mejor que puede hacer es llamar a GetOrdinal("columnName") en su DataReader por adelantado, y capturar una excepción IndexOutOfRangeException en caso de que la columna no esté presente.
De hecho, vamos a hacer 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 acumular algunos votos negativos últimamente, y no puedo eliminarla porque es la respuesta aceptada, así que la actualizaré 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 de campo que está buscando. Esto funcionará realmente bien, y lo más probable es que se desempeñe mejor que mi método anterior. Ciertamente, nunca usarí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 funcionará donde el bucle no funciona. Sin embargo, en este momento es una situación completamente hipotética, por lo que es una justificación muy endeble. En cualquier caso, 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 hacer una selección para cualquiera de los dos nombres devolvería los datos en esa columna. Conmigo hasta ahora?
Ahora imagine que hay un proveedor ADO.NET para esa base de datos, y ellos han codificado una implementación IDataReader para ella que toma 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 solo tiene que devolver 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 "alias de columnas", el método try / GetOrdinal / catch sería la única manera 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 un pensamiento. Honestamente, prefiero un método HasColumn "oficial" en IDataRecord.
El uso de Exception
s para la lógica de control como en otras respuestas se considera una mala práctica y tiene costos de rendimiento.
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
La forma más apropiada 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;
}
}
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 un post antiguo, 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 de iteración de la solución.
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
Una sola línea mucho más eficiente que no requiere cargar el esquema:
var exists = Enumerable.Range(0, dr.FieldCount).Any(i => string.Equals(dr.GetName(i), fieldName, StringComparison.OrdinalIgnoreCase));
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
Estas respuestas ya están publicadas aquí. Sólo un poco de Linq-ing:
bool b = reader.GetSchemaTable().Rows
.Cast<DataRow>()
.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
todas las veces en el primer enfoque, la búsqueda será muy lenta.
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<string> GetColumnNames(SqlDataReader r)
{
List<string> ColumnNames = new List<string>();
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;
}
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: esto parece devolver solo 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<string> ExecuteColumnNamesReader(string command, List<SqlParameter> Params)
{
List<string> ColumnNames = new List<string>();
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
}
Lo siguiente es simple y trabajado para mí:
bool hasMyColumn = (reader.GetSchemaTable().Select("ColumnName = ''MyColumnName''").Count() == 1);
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 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 fanático 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 un nombre de columna y un tipo de parámetro de salida.
private List<string> _lstString;
public void GetValueByParameter<T>(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);
}
}
/// <summary>
/// Reset the global list of columns to reflect the fields in the IDataReader
/// </summary>
/// <param name="dr">The IDataReader being acted upon</param>
/// <param name="NextResult">Advances IDataReader to next result</param>
public void ResetSchemaTable(IDataReader dr, bool nextResult)
{
if (nextResult)
dr.NextResult();
_lstString = new List<string>();
using (DataTable dataTableSchema = dr.GetSchemaTable())
{
if (dataTableSchema != null)
{
foreach (DataRow row in dataTableSchema.Rows)
{
_lstString.Add(row[dataTableSchema.Columns["ColumnName"]].ToString());
}
}
}
}
Entonces puedo simplemente llamar a mi código así
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;
}
}
Para mantener su código robusto y limpio, use una función de extensión única, como esta:
Public Module Extensions
<Extension()>
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
Qué tal si
if (dr.GetSchemaTable().Columns.Contains("accounttype"))
do something
else
do something
Probablemente no sería tan eficiente en un bucle.
Si leíste la pregunta, Michael preguntó sobre DataReader, no sobre DataRecord. Consigue tus objetos bien
El uso de r.GetSchemaTable().Columns.Contains(field)
en un DataRecord funciona, pero devuelve columnas BS (ver captura de pantalla a continuación).
Para ver si existe una columna de datos Y contiene datos en un DataReader, use las siguientes extensiones:
public static class DataReaderExtensions
{
/// <summary>
/// Checks if a column''s value is DBNull
/// </summary>
/// <param name="dataReader">The data reader</param>
/// <param name="columnName">The column name</param>
/// <returns>A bool indicating if the column''s value is DBNull</returns>
public static bool IsDBNull(this IDataReader dataReader, string columnName)
{
return dataReader[columnName] == DBNull.Value;
}
/// <summary>
/// Checks if a column exists in a data reader
/// </summary>
/// <param name="dataReader">The data reader</param>
/// <param name="columnName">The column name</param>
/// <returns>A bool indicating the column exists</returns>
public static bool ContainsColumn(this IDataReader dataReader, string columnName)
{
/// See: http://.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");
}
Llamando r.GetSchemaTable().Columns
en un DataReader devuelven columnas BS:
También puede llamar a GetSchemaTable() en su DataReader si desea la lista de columnas y no quiere tener que obtener una excepción ...
Tampoco conseguí que GetSchemaTable
funcionara, hasta que encontré 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
esto funciona para mi
bool hasColumnName = reader.GetSchemaTable().AsEnumerable().Any(c => c["ColumnName"] == "YOUR_COLUMN_NAME");
Es mucho mejor usar esta función booleana:
r.GetSchemaTable().Columns.Contains(field)
Una llamada - sin excepciones. Podría lanzar excepciones internamente, pero no lo creo.
NOTA: En los comentarios a continuación, descubrimos 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;
}
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);
}
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 mayúsculas y minúsculas.