TLDR:
Molte risposte con affermazioni su prestazioni e cattive pratiche, quindi chiarisco che qui.
Il percorso di eccezione è più veloce per un numero maggiore di colonne restituite, il percorso del ciclo è più veloce per un numero inferiore di colonne e il punto di crossover è di circa 11 colonne. Scorri verso il basso per visualizzare un grafico e un codice di prova.
Risposta completa:
Il codice per alcune delle risposte migliori funziona, ma qui c'è un dibattito sottostante per la risposta "migliore" basata sull'accettazione della gestione delle eccezioni nella logica e delle prestazioni correlate.
Per chiarire ciò, non credo che ci siano molte indicazioni riguardo alle eccezioni di CATCHING. Microsoft ha alcune indicazioni per quanto riguarda le eccezioni di LANCIO. Lì dichiarano:
NON utilizzare eccezioni per il normale flusso di controllo, se possibile.
La prima nota è la clemenza di "se possibile". Ancora più importante, la descrizione fornisce questo contesto:
framework designers should design APIs so users can write code that does not throw exceptions
Ciò significa che se stai scrivendo un'API che potrebbe essere utilizzata da qualcun altro, dai loro la possibilità di navigare in un'eccezione senza provare / catturare. Ad esempio, fornire un TryParse con il metodo Parse che genera eccezioni. Da nessuna parte questo dice che non dovresti prendere un'eccezione.
Inoltre, come sottolineato da un altro utente, le catture hanno sempre consentito il filtraggio per tipo e in qualche modo recentemente consentono un ulteriore filtraggio tramite la clausola when . Sembra uno spreco di funzionalità linguistiche se non dovremmo usarle.
Si può dire che vi è ALCUNO costo per un'eccezione generata e che può influire sulle prestazioni in un ciclo pesante. Tuttavia, si può anche affermare che il costo di un'eccezione sarà trascurabile in una "applicazione connessa". Il costo effettivo è stato esaminato oltre un decennio fa: https://stackoverflow.com/a/891230/852208
In altre parole, è probabile che il costo di una connessione e una query di un database riducano quello di un'eccezione generata.
A parte questo, volevo determinare quale metodo fosse veramente più veloce. Come previsto, non esiste una risposta concreta.
Qualsiasi codice che scorre sopra le colonne diventa più lento man mano che esiste il numero di colonne. Si può anche dire che qualsiasi codice che si basa su eccezioni rallenterà a seconda della velocità con cui la query non viene trovata.
Prendendo le risposte di Chad Grant e Matt Hamilton, ho eseguito entrambi i metodi con un massimo di 20 colonne e un tasso di errore fino al 50% (l'OP ha indicato che stava usando questi due test tra processi diversi, quindi ne ho ipotizzati solo due) .
Ecco i risultati, tracciati con LinqPad:
Gli zigzag qui sono i tassi di errore (colonna non trovata) all'interno di ogni conteggio delle colonne.
Su insiemi di risultati più ristretti, il looping è una buona scelta. Tuttavia, il metodo GetOrdinal / Exception non è altrettanto sensibile al numero di colonne e inizia a sovraperformare il metodo di ciclo attorno a 11 colonne.
Detto questo, in realtà non ho una preferenza prestazionale in quanto 11 colonne sembrano ragionevoli poiché un numero medio di colonne restituite su un'intera applicazione. In entrambi i casi stiamo parlando di frazioni di millisecondo qui.
Tuttavia, dal punto di vista della semplicità del codice e del supporto alias, probabilmente andrei con il percorso GetOrdinal.
Ecco il test in formato linqpad. Sentiti libero di ripubblicare con il tuo metodo:
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; }
}