TLDR:
Muchas respuestas con afirmaciones sobre el rendimiento y las malas prácticas, así que lo aclaro aquí.
La ruta de excepción es más rápida para un mayor número de columnas devueltas, la ruta del bucle es más rápida para un menor número de columnas y el punto de cruce es de alrededor de 11 columnas. Desplácese hacia abajo para ver un gráfico y un código de prueba.
Respuesta completa:
El código para algunas de las principales respuestas funciona, pero hay un debate subyacente aquí para la "mejor" respuesta basada en la aceptación del manejo de excepciones en la lógica y su rendimiento relacionado.
Para aclarar eso, no creo que haya mucha guía con respecto a las excepciones de CATCHING. Microsoft tiene alguna orientación con respecto a LANZAR excepciones. Allí dicen:
NO use excepciones para el flujo normal de control, si es posible.
La primera nota es la indulgencia de "si es posible". Más importante aún, la descripción da este contexto:
framework designers should design APIs so users can write code that does not throw exceptions
Lo que eso significa es que si está escribiendo una API que podría ser consumida por otra persona, bríndeles la capacidad de navegar una excepción sin un intento / captura. Por ejemplo, proporcione un TryParse con su método Parse de lanzamiento de excepciones. Sin embargo, en ninguna parte esto dice que no deberías atrapar una excepción.
Además, como señala otro usuario, las capturas siempre han permitido el filtrado por tipo y, de alguna manera, recientemente permiten un mayor filtrado a través de la cláusula when . Esto parece un desperdicio de características del lenguaje si no se supone que las usemos.
Se puede decir que hay ALGUNOS costos para una excepción lanzada, y ese costo PUEDE afectar el rendimiento en un ciclo pesado. Sin embargo, también se puede decir que el costo de una excepción será insignificante en una "aplicación conectada". El costo real se investigó hace más de una década: https://stackoverflow.com/a/891230/852208
En otras palabras, el costo de una conexión y consulta de una base de datos probablemente eclipsará el de una excepción lanzada.
Aparte de eso, quería determinar qué método es realmente más rápido. Como se esperaba, no hay una respuesta concreta.
Cualquier código que recorra las columnas se vuelve más lento a medida que existe el número de columnas. También se puede decir que cualquier código que se base en excepciones se ralentizará dependiendo de la velocidad con la que no se encuentre la consulta.
Tomando las respuestas de Chad Grant y Matt Hamilton, ejecuté ambos métodos con hasta 20 columnas y una tasa de error de hasta el 50% (el OP indicó que estaba usando estas dos pruebas entre diferentes procesos, por lo que supuse que solo dos) .
Aquí están los resultados, trazados con LinqPad:
Los zigzags aquí son tasas de fallas (columna no encontrada) dentro de cada recuento de columnas.
En conjuntos de resultados más estrechos, el bucle es una buena opción. Sin embargo, el método GetOrdinal / Exception no es tan sensible al número de columnas y comienza a superar el método de bucle alrededor de 11 columnas.
Dicho esto, realmente no tengo un rendimiento de preferencia sabio ya que 11 columnas suenan razonables ya que un número promedio de columnas devueltas en una aplicación completa. En cualquier caso, estamos hablando de fracciones de milisegundos aquí.
Sin embargo, desde un aspecto de simplicidad de código y soporte de alias, probablemente iría con la ruta GetOrdinal.
Aquí está la prueba en forma de linqpad. Siéntase libre de volver a publicar con su propio 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; }
}