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 lógica de controle, como em algumas outras respostas, é considerado uma prática ruim e tem custos de desempenho. Ele também envia falsos positivos para o criador de perfil de # exceções lançadas e que Deus ajude qualquer um que esteja configurando seu depurador para quebrar as exceções lançadas.
GetSchemaTable () também é outra sugestão em muitas respostas. Essa não seria uma maneira preferida de verificar a existência de um campo, pois ele não é implementado em todas as versões (é abstrato e lança NotSupportedException em algumas versões do dotnetcore). GetSchemaTable também é um desempenho exagerado, pois é uma função bastante pesada se você verificar a fonte .
O loop pelos campos pode ter um pequeno impacto no desempenho se você o usar muito e considerar o armazenamento em cache dos resultados.
É muito melhor usar esta função booleana:Uma chamada - sem exceções. Pode lançar exceções internamente, mas acho que não.NOTA: Nos comentários abaixo, descobrimos isso ... o código correto é realmente este:
fonte
Acho que sua melhor aposta é chamar GetOrdinal ("columnName") no seu DataReader na frente e pegar uma IndexOutOfRangeException no caso de a coluna não estar presente.
De fato, vamos criar um método de extensão:
Editar
Ok, este post está começando a receber alguns votos negativos recentemente, e não posso excluí-lo porque é a resposta aceita, então vou atualizá-lo e (espero) tentar justificar o uso do tratamento de exceções como controle de fluxo.
A outra maneira de conseguir isso, conforme publicado 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 que o meu método acima. Certamente eu nunca usaria o método acima dentro de um loop em que 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 no momento, por isso é uma justificativa muito frágil. Independentemente disso, tenha paciência comigo e veja o que pensa.
Imagine um banco de dados que permita "alias" colunas dentro de uma tabela. Imagine que eu poderia definir uma tabela com uma coluna chamada "EmployeeName", mas também fornecer um apelido de "EmpName", e fazer uma seleção para qualquer nome retornaria os dados nessa coluna. Comigo até agora?
Agora imagine que exista um provedor ADO.NET para esse banco de dados, e eles codificaram uma implementação do IDataReader, que leva em consideração os aliases da coluna.
Agora,
dr.GetName(i)
(como usado na resposta do Chade), pode retornar apenas uma única sequência, portanto, ele deve retornar apenas um dos "aliases" em uma coluna. No entanto, você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 garantir que você esteja verificando todas as variações do nome de uma coluna no conjunto de resultados.
Frágil? Certo. Mas vale a pena pensar. Honestamente, eu prefiro um método HasColumn "oficial" no IDataRecord.
fonte
Em uma linha, use isso após a recuperação do DataReader:
Então,
Editar
Linha única muito mais eficiente que não requer o carregamento do esquema:
fonte
Aqui está uma amostra de trabalho da ideia de Jasmin:
fonte
isso funciona para mim:
fonte
O seguinte é simples e funcionou para mim:
fonte
Se você leu a pergunta, Michael perguntou sobre o DataReader, não sobre o pessoal do DataRecord. Acerte seus objetos.
Usar um
r.GetSchemaTable().Columns.Contains(field)
em um DataRecord funciona, mas retorna colunas BS (veja a captura de tela abaixo).Para verificar se existe uma coluna de dados E contém dados em um DataReader, use as seguintes extensões:
Uso:
Chamar
r.GetSchemaTable().Columns
um DataReader retorna colunas BS:fonte
IDataReader
implementaIDataRecord
. Eles são interfaces diferentes do mesmo objeto - exatamente comoICollection<T>
eIEnumerable<T>
são interfaces diferentesList<T>
.IDataReader
permite avançar para o próximo registro, enquantoIDataRecord
permite a leitura do registro atual. Os métodos que estão sendo usados nesta resposta são todos daIDataRecord
interface. Consulte stackoverflow.com/a/1357743/221708 para obter uma explicação sobre por queIDataRecord
é preferível declarar o parâmetro .r.GetSchemaTable().Columns
é uma resposta absolutamente errada para esta pergunta.Eu escrevi para usuários do Visual Basic:
Eu acho que isso é mais poderoso e o uso é:
fonte
Aqui está uma versão linq de uma linha da resposta aceita:
fonte
Aqui a solução do Jasmine em uma linha ... (mais uma, muito simples!):
fonte
fonte
TLDR:
Muitas respostas com alegações sobre desempenho e práticas inadequadas, por isso esclareço isso aqui.
A rota de exceção é mais rápida para um número maior de colunas retornadas, a rota de loop é mais rápida para um número menor de colunas e o ponto de cruzamento é de cerca de 11 colunas. Role para baixo para ver um gráfico e código de teste.
Resposta completa:
O código para algumas das principais respostas funciona, mas há um debate subjacente aqui para a resposta "melhor", com base na aceitação do tratamento de exceções na lógica e no desempenho relacionado.
Para esclarecer isso, não acredito que exista muita orientação sobre as exceções de CAPTURA. A Microsoft tem algumas orientações sobre exceções de THROWING. Lá eles afirmam:
A primeira nota é a clemência de "se possível". Mais importante, a descrição fornece este contexto:
O que isso significa é que, se você estiver escrevendo uma API que possa ser consumida por outra pessoa, permita que ela navegue em uma exceção sem uma tentativa / captura. Por exemplo, forneça um TryParse com o método Parse que lança uma exceção. Em nenhum lugar isso diz que você não deve capturar uma exceção.
Além disso, como outro usuário ressalta, as capturas sempre permitiram a filtragem por tipo e, recentemente, permitem a filtragem adicional através da cláusula when . Parece um desperdício de recursos de linguagem se não devemos usá-los.
Pode-se dizer que há ALGUM custo para uma exceção lançada, e esse custo PODE impactar o desempenho em um loop pesado. No entanto, também se pode dizer que o custo de uma exceção será insignificante em um "aplicativo conectado". O custo real foi investigado há mais de uma década: https://stackoverflow.com/a/891230/852208 Em outras palavras, o custo de uma conexão e consulta de um banco de dados provavelmente superará o de uma exceção lançada.
Tudo isso de lado, eu queria determinar qual método é realmente mais rápido. Como esperado, não há resposta concreta.
Qualquer código que passa por cima das colunas se torna mais lento conforme o número de colunas. Também pode-se dizer que qualquer código que dependa de exceções diminuirá dependendo da taxa em que a consulta não for encontrada.
Tomando as respostas de Chad Grant e Matt Hamilton, eu executei os dois métodos com até 20 colunas e até uma taxa de erro de 50% (o OP indicou que ele estava usando esse dois testes entre procs diferentes, então assumi que apenas dois) .
Aqui estão os resultados, plotados com o LinqPad:
Os ziguezagues aqui são taxas de falha (coluna não encontrada) em cada contagem de colunas.
Em conjuntos de resultados mais restritos, o loop é uma boa escolha. No entanto, o método GetOrdinal / Exception não é tão sensível ao número de colunas e começa a superar o método de loop em torno de 11 colunas.
Dito isso, eu realmente não tenho um desempenho preferencial, pois 11 colunas parecem razoáveis, pois um número médio de colunas retornadas por um aplicativo inteiro. Em ambos os casos, estamos falando de frações de milissegundos aqui.
No entanto, de um aspecto de simplicidade de código e suporte a alias, eu provavelmente iria com a rota GetOrdinal.
Aqui está o teste no formato linqpad. Sinta-se livre para repassar com seu próprio método:
fonte
Esse código corrige os problemas que a Levitikon teve com seu código: (adaptado de: [1]: http://msdn.microsoft.com/en-us/library/system.data.datatablereader.getschematable.aspx )
O motivo para obter todos esses nomes de colunas inúteis e não o nome da coluna da sua tabela ... É porque você está recebendo 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: código corrigido que retorna o nome de todas as colunas, mas você não pode usar um SqlDataReader para fazer isso
fonte
return r.GetSchemaTable().Rows.Cast<DataRow>().Select(x => (string)x["ColumnName"]).ToList();
:)Para manter seu código robusto e limpo, use uma função de extensão única, como esta:
fonte
Nem fui
GetSchemaTable
trabalhar até encontrar esse caminho .Basicamente, faço isso:
fonte
Columns.Contains
é entre maiúsculas e minúsculas.fonte
Na sua situação específica (todos os procedimentos têm as mesmas colunas, exceto 1 que possui 1 coluna adicional), será melhor e mais rápido verificar o leitor. Propriedade FieldCount para distinguir entre eles.
Sei que é um post antigo, mas decidi responder para ajudar outros na mesma situação. você também pode (por motivos de desempenho) misturar esta solução com a solução de iteração da solução.
fonte
Minha classe de acesso a dados precisa ser compatível com versões anteriores; portanto, talvez eu esteja tentando acessar uma coluna em uma versão em que ela ainda não existe no banco de dados. Temos alguns conjuntos de dados bastante grandes sendo retornados, por isso não sou um grande fã de um método de extensão que precisa iterar a coleção de colunas DataReader para cada propriedade.
Eu tenho uma classe de utilitário que cria uma lista particular de colunas e, em seguida, possui um método genérico que tenta resolver um valor com base no nome da coluna e no tipo de parâmetro de saída.
Então eu posso chamar meu código assim
fonte
A chave para todo o problema está aqui :
Se as três linhas referenciadas (atualmente as linhas 72, 73 e 74) forem retiradas, você poderá procurar facilmente
-1
para determinar se a coluna não existe.A única maneira de contornar isso e garantir o desempenho nativo é usar uma
Reflection
implementação baseada, como a seguir:Usos:
O método de extensão baseado em reflexão:
fonte
Você também pode chamar GetSchemaTable () no seu DataReader se quiser a lista de colunas e não precisar obter uma exceção ...
fonte
E se
Provavelmente não seria tão eficiente em um loop
fonte
dr.GetSchemaTable().Columns
contém - não é o que você está procurando.Embora não exista um método exposto publicamente, existe um método na classe interna em
System.Data.ProviderBase.FieldNameLookup
que seSqlDataReader
baseia.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á acesso direto
int IndexOf(string fieldName)
naSystem.Data.ProviderBase.FieldNameLookup
classe, além de executar a manutenção de livros queSqlDataReader.GetOrdinal()
faz para que não haja efeito colateral. O código gerado espelha o existente,SqlDataReader.GetOrdinal()
exceto que ele chama emFieldNameLookup.IndexOf()
vez deFieldNameLookup.GetOrdinal()
. OGetOrdinal()
método chama aIndexOf()
função e lança uma exceção se-1
for retornado, portanto ignoramos esse comportamento.fonte
esse trabalho pra mim
fonte
você pode obter mais detalhes aqui: Você pode obter os nomes das colunas de um SqlDataReader?
fonte