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:
NÃO use exceções para o fluxo normal de controle, se possível.
A primeira nota é a clemência de "se possível". Mais importante, a descrição fornece este contexto:
framework designers should design APIs so users can write code that does not throw exceptions
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:
void Main()
{
var loopResults = new List<Results>();
var exceptionResults = new List<Results>();
var totalRuns = 10000;
for (var colCount = 1; colCount < 20; colCount++)
{
using (var conn = new SqlConnection(@"Data Source=(localdb)\MSSQLLocalDb;Initial Catalog=master;Integrated Security=True;"))
{
conn.Open();
//create a dummy table where we can control the total columns
var columns = String.Join(",",
(new int[colCount]).Select((item, i) => $"'{i}' as col{i}")
);
var sql = $"select {columns} into #dummyTable";
var cmd = new SqlCommand(sql,conn);
cmd.ExecuteNonQuery();
var cmd2 = new SqlCommand("select * from #dummyTable", conn);
var reader = cmd2.ExecuteReader();
reader.Read();
Func<Func<IDataRecord, String, Boolean>, List<Results>> test = funcToTest =>
{
var results = new List<Results>();
Random r = new Random();
for (var faultRate = 0.1; faultRate <= 0.5; faultRate += 0.1)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var faultCount=0;
for (var testRun = 0; testRun < totalRuns; testRun++)
{
if (r.NextDouble() <= faultRate)
{
faultCount++;
if(funcToTest(reader, "colDNE"))
throw new ApplicationException("Should have thrown false");
}
else
{
for (var col = 0; col < colCount; col++)
{
if(!funcToTest(reader, $"col{col}"))
throw new ApplicationException("Should have thrown true");
}
}
}
stopwatch.Stop();
results.Add(new UserQuery.Results{
ColumnCount = colCount,
TargetNotFoundRate = faultRate,
NotFoundRate = faultCount * 1.0f / totalRuns,
TotalTime=stopwatch.Elapsed
});
}
return results;
};
loopResults.AddRange(test(HasColumnLoop));
exceptionResults.AddRange(test(HasColumnException));
}
}
"Loop".Dump();
loopResults.Dump();
"Exception".Dump();
exceptionResults.Dump();
var combinedResults = loopResults.Join(exceptionResults,l => l.ResultKey, e=> e.ResultKey, (l, e) => new{ResultKey = l.ResultKey, LoopResult=l.TotalTime, ExceptionResult=e.TotalTime});
combinedResults.Dump();
combinedResults
.Chart(r => r.ResultKey, r => r.LoopResult.Milliseconds * 1.0 / totalRuns, LINQPad.Util.SeriesType.Line)
.AddYSeries(r => r.ExceptionResult.Milliseconds * 1.0 / totalRuns, LINQPad.Util.SeriesType.Line)
.Dump();
}
public static bool HasColumnLoop(IDataRecord dr, string columnName)
{
for (int i = 0; i < dr.FieldCount; i++)
{
if (dr.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase))
return true;
}
return false;
}
public static bool HasColumnException(IDataRecord r, string columnName)
{
try
{
return r.GetOrdinal(columnName) >= 0;
}
catch (IndexOutOfRangeException)
{
return false;
}
}
public class Results
{
public double NotFoundRate { get; set; }
public double TargetNotFoundRate { get; set; }
public int ColumnCount { get; set; }
public double ResultKey {get => ColumnCount + TargetNotFoundRate;}
public TimeSpan TotalTime { get; set; }
}