Verificar o nome da coluna em um object SqlDataReader

Como faço para verificar se existe uma coluna em um object SqlDataReader ? Na minha camada de access a dados, criei um método que cria o mesmo object para várias chamadas de stored procedures. Um dos stored procedures tem uma coluna adicional que não é usada pelos outros stored procedures. Eu quero modificar o método para acomodar todos os cenários.

Meu aplicativo está escrito em c #.

    Usar Exception para lógica de controle, como em algumas outras respostas, é considerado uma prática ruim e tem custos de desempenho.

    Looping através dos campos pode ter um pequeno desempenho se você usá-lo muito e você pode querer considerar o cache dos resultados

    A maneira mais apropriada de fazer isso é:

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

    É muito melhor usar essa function booleana:

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

    Uma chamada – sem exceções. Pode lançar exceções internamente, mas eu não penso assim.

    NOTA: Nos comentários abaixo, nós descobrimos isso … o código correto é realmente este:

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

    Eu acho que sua melhor aposta é chamar GetOrdinal (“columnName”) em seu DataReader na frente, e pegar um IndexOutOfRangeException no caso da coluna não estar presente.

    De fato, vamos fazer um método de extensão:

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

    Editar

    Ok, este post está começando a ganhar alguns votos negativos ultimamente, e eu não posso deletá-lo porque é a resposta aceita, então eu vou atualizá-lo e (espero) tentar justificar o uso do tratamento de exceção como controle de stream.

    A outra maneira de conseguir isso, conforme postado por Chad Grant , é percorrer cada campo no DataReader e fazer uma comparação sem distinção entre maiúsculas e minúsculas para o nome do campo que você está procurando. Isso funcionará muito bem e, na verdade, provavelmente terá um desempenho melhor do que o meu método acima. Certamente eu nunca usaria o método acima dentro de um loop onde o desempenho era um problema.

    Eu posso pensar em uma situação em que o método try / GetOrdinal / catch funcionará onde o loop não funciona. É, no entanto, uma situação completamente hipotética agora, então é uma justificativa muito frágil. Independentemente disso, tenha paciência comigo e veja o que você pensa.

    Imagine um database que permitisse que você “aliasse” colunas dentro de uma tabela. Imagine que eu poderia definir uma tabela com uma coluna chamada “EmployeeName”, mas também dar a ela um apelido de “EmpName”, e fazer um select para qualquer nome retornaria os dados naquela coluna. Comigo até agora?

    Agora imagine que há um provedor ADO.NET para esse database e eles codificaram uma implementação IDataReader para ele, que leva em conta os aliases de coluna.

    Agora, dr.GetName(i) (como usado na resposta de Chad) só pode retornar uma única string, então ele deve retornar apenas um dos “aliases” em uma coluna. No entanto, GetOrdinal("EmpName") pode usar a implementação interna dos campos desse provedor para verificar o alias de cada coluna para o nome que você está procurando.

    Nessa situação hipotética de “colunas com alias”, o método try / GetOrdinal / catch seria a única maneira de ter certeza de que você está verificando todas as variações do nome de uma coluna no conjunto de resultados.

    Frágil? Certo. Mas vale um pensamento. Honestamente eu prefiro um método “oficial” HasColumn em IDataRecord.

    Em uma linha, use isso após a recuperação do seu DataReader:

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

    Então,

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

    Editar

    Um one-liner muito mais eficiente que não requer o carregamento do esquema:

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

    Aqui está uma amostra de trabalho para a ideia de Jasmin:

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

    isso funciona para mim:

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

    O seguinte é simples e funcionou para mim:

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

    Se você leu a pergunta, Michael perguntou sobre o DataReader, não sobre o DataRecord. Obtenha seus objects certos.

    Usando um r.GetSchemaTable().Columns.Contains(field) em um DataRecord funciona, mas retorna colunas BS (veja a captura de canvas abaixo).

    Para ver se existe uma coluna de dados E contém dados em um DataReader, use as seguintes extensões:

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

    Chamando r.GetSchemaTable().Columns em um DataReader retorna colunas BS:

    Chamando GetSchemeTable em um DataReader

    Eu escrevi para usuários do 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 

    Eu acho que isso é mais poderoso e o uso é:

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

    Aqui a solução da Jasmine em uma linha … (mais uma, tho simples!):

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

    Aqui está uma versão linq liner da resposta aceita:

     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 os problemas que o Levitikon tinha com o seu 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; } 

    O motivo para obter todos esses nomes de coluna inúteis e não o nome da coluna da sua tabela … É porque você está obtendo o nome da coluna do esquema (ou seja, os nomes das colunas da tabela Schema)

    NOTA: isso parece retornar apenas o nome da primeira coluna …

    EDIT: corrigido código que retorna o nome de todas as colunas, mas você não pode usar um SqlDataReader para fazê-lo

     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 } 

    Nem consegui GetSchemaTable para trabalhar, até que encontrei este caminho .

    Basicamente eu faço isso:

     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 faz Columns.Contains entre maiúsculas e minúsculas.

    Para manter seu código robusto e limpo, use uma única function de extensão, 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 

    Você também pode chamar GetSchemaTable () em seu DataReader se quiser a lista de colunas e não quiser obter uma exceção …

    Essas respostas já estão postadas aqui. Apenas Linq-ing um pouco:

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

    O segundo é mais limpo e muito mais rápido. Mesmo se você não executar GetSchemaTable todas as vezes na primeira abordagem, a pesquisa será muito lenta.

    Na sua situação particular (todos os procedimentos têm as mesmas colunas, exceto 1, que tem 1 coluna adicional), será melhor e mais rápido verificar o leitor. FieldCount propriedade para distinguir entre eles.

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

    Eu sei que é um post antigo mas resolvi responder para ajudar outro na mesma situação. Você também pode (por motivo de desempenho) misturar essa solução com a solução iteradora da solução.

    Minha class de access a dados precisa ser compatível com versões anteriores, portanto, talvez eu esteja tentando acessar uma coluna em um release em que ela ainda não exista no database. Temos alguns conjuntos de dados bastante grandes sendo retornados, portanto, não sou muito fã de um método de extensão que precise iterar a coleção de colunas DataReader para cada propriedade.

    Eu tenho uma class de utilitário que cria uma lista particular de colunas e, em seguida, tem um método genérico que tenta resolver um valor com base em um nome de coluna e tipo de parâmetro de saída.

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

    Então eu posso apenas chamar meu código assim

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

    Embora não exista um método publicamente exposto, existe um método na class interna System.Data.ProviderBase.FieldNameLookup que o SqlDataReader depende.

    Para acessá-lo e obter desempenho nativo, você deve usar o ILGenerator para criar um método em tempo de execução. O código a seguir lhe dará access direto ao int IndexOf(string fieldName) na class System.Data.ProviderBase.FieldNameLookup , bem como executará o book keeping que SqlDataReader.GetOrdinal() faz para que não haja efeito colateral. O código gerado espelha o SqlDataReader.GetOrdinal() existente, exceto que chama FieldNameLookup.IndexOf() vez de FieldNameLookup.GetOrdinal() . O método GetOrdinal() chama a function IndexOf() e lança uma exceção se -1 for retornado, portanto, ignoramos esse comportamento.

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

    E se

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

    Provavelmente não seria tão eficiente em um loop