TLDR:
Beaucoup de réponses avec des affirmations sur les performances et les mauvaises pratiques, donc je clarifie cela ici.
La route d'exception est plus rapide pour un nombre plus élevé de colonnes retournées, la route de boucle est plus rapide pour un nombre inférieur de colonnes et le point de croisement est d'environ 11 colonnes. Faites défiler vers le bas pour voir un graphique et un code de test.
Réponse complète:
Le code de certaines des meilleures réponses fonctionne, mais il y a un débat sous-jacent ici pour la "meilleure" réponse basée sur l'acceptation de la gestion des exceptions dans la logique et ses performances associées.
Pour clarifier cela, je ne pense pas qu'il y ait beaucoup d'indications concernant les exceptions de capture. Microsoft a quelques conseils concernant les exceptions de LANCEMENT. Là, ils déclarent:
N'UTILISEZ PAS d'exceptions pour le flux normal de contrôle, si possible.
La première note est la clémence de «si possible». Plus important encore, la description donne ce contexte:
framework designers should design APIs so users can write code that does not throw exceptions
Cela signifie que si vous écrivez une API qui pourrait être utilisée par quelqu'un d'autre, donnez-lui la possibilité de naviguer dans une exception sans essayer / attraper. Par exemple, fournissez un TryParse avec votre méthode d'analyse d'exception. Cela ne dit nulle part que vous ne devriez pas attraper une exception.
De plus, comme le souligne un autre utilisateur, les captures ont toujours autorisé le filtrage par type et ont récemment permis un filtrage supplémentaire via la clause when . Cela semble être un gaspillage de fonctionnalités linguistiques si nous ne sommes pas censés les utiliser.
On peut dire qu'il y a QUELQUE coût pour une exception levée, et que le coût PEUT avoir un impact sur les performances dans une boucle lourde. Cependant, on peut également dire que le coût d'une exception va être négligeable dans une "application connectée". Le coût réel a été étudié il y a plus d'une décennie: https://stackoverflow.com/a/891230/852208
En d'autres termes, le coût d'une connexion et d'une requête d'une base de données est susceptible de éclipser celui d'une exception levée.
Tout cela mis à part, je voulais déterminer quelle méthode est vraiment la plus rapide. Comme prévu, il n'y a pas de réponse concrète.
Tout code qui boucle sur les colonnes devient plus lent à mesure que le nombre de colonnes existe. On peut également dire que tout code qui repose sur des exceptions ralentira en fonction de la vitesse à laquelle la requête ne sera pas trouvée.
En prenant les réponses de Chad Grant et de Matt Hamilton, j'ai exécuté les deux méthodes avec jusqu'à 20 colonnes et jusqu'à un taux d'erreur de 50% (l'OP a indiqué qu'il utilisait ces deux tests entre des procs différents, donc j'ai supposé aussi peu que deux) .
Voici les résultats, tracés avec LinqPad:
Les zigzags ici sont des taux d'erreur (colonne non trouvée) dans chaque nombre de colonnes.
Sur des ensembles de résultats plus étroits, le bouclage est un bon choix. Cependant, la méthode GetOrdinal / Exception n'est pas aussi sensible au nombre de colonnes et commence à surpasser la méthode de bouclage autour de 11 colonnes.
Cela dit, je n'ai pas vraiment de préférence en termes de performances, car 11 colonnes semblent raisonnables, car le nombre moyen de colonnes renvoyées sur une application entière. Dans les deux cas, nous parlons ici de fractions de milliseconde.
Cependant, du point de vue de la simplicité du code et de la prise en charge des alias, j'irais probablement avec la route GetOrdinal.
Voici le test sous forme linqpad. N'hésitez pas à republier avec votre propre méthode:
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; }
}