TLDR:
Багато відповідей із претензіями на ефективність та поганою практикою, тому я уточнюю це тут.
Маршрут винятку швидший для більшої кількості повернених стовпців, маршрут циклу швидший для меншої кількості стовпців, а точка перетину - близько 11 стовпців. Прокрутіть донизу, щоб побачити графік та код тесту.
Повна відповідь:
Код для деяких найвищих відповідей працює, але тут лежить в основі суперечок щодо «кращої» відповіді, заснованої на прийнятті обробки винятків у логіці та пов’язаній з цим продуктивності.
Щоб зрозуміти це далеко, я не вірю, що є багато вказівок щодо винятків CATCHING. Корпорація Майкрософт має деякі рекомендації щодо викидання винятків. Там вони заявляють:
НЕ використовуйте винятки для нормального потоку управління, якщо це можливо.
Перша примітка - поблажливість "якщо можливо". Що ще важливіше, опис дає такий контекст:
framework designers should design APIs so users can write code that does not throw exceptions
Це означає, що якщо ви пишете API, який може споживати хтось інший, надайте їм можливість орієнтуватися на виняток без спроби / лову. Наприклад, надайте TryParse методом Parse, який викидає виключення. Ніде це не говорить, хоча ви не повинні виловлювати виняток.
Крім того, як вказує інший користувач, уловки завжди дозволяли фільтрувати за типом, і дещо недавно дозволяють подальше фільтрування за допомогою пункту "коли" . Це здається марною мовою, якщо ми не повинні ними користуватися.
Можна сказати, що для кинутого винятку є ДЕЯКІ витрати, і це МОЖЕ вплинути на ефективність у важкому циклі. Однак можна також сказати, що вартість винятку буде "незначною" у "підключеній програмі". Фактична вартість була досліджена більше десятиліття тому: https://stackoverflow.com/a/891230/852208
Іншими словами, вартість з'єднання та запиту бази даних, ймовірно, може змінити вартість кинутого винятку.
Незважаючи на це, я хотів визначити, який метод справді швидший. Як очікується, конкретної відповіді немає.
Будь-який код, який перетворюється на стовпці, стає повільнішим у міру існування кількості стовпців. Можна також сказати, що будь-який код, який спирається на винятки, сповільнюватиметься залежно від швидкості пошуку запиту.
Отримуючи відповіді як Чада Гранта, так і Метта Гамільтона, я застосував обидва методи з до 20 стовпців і до 50% помилок (ОП показав, що він використовує цей два тести між різними документами, тому я припустив, що два як два) .
Ось результати, побудовані за допомогою LinqPad:
Зигзаги тут - коефіцієнт помилок (стовпчик не знайдений) у межах кожного числа стовпців.
У більш вузьких наборах результатів циклічний цикл - хороший вибір. Однак метод GetOrdinal / Exception не є настільки чутливим до кількості стовпців і починає перевершувати циклічний метод прямо біля 11 стовпців.
Це говорить про те, що я не маю переваги щодо ефективності переваг, оскільки 11 стовпців звучить розумно, оскільки середня кількість стовпців, що повертаються протягом усієї програми. У будь-якому випадку ми тут говоримо про частки мілісекунди.
Однак, з точки зору простоти коду та підтримки псевдоніму, я, мабуть, пішов би шляхом GetOrdinal.
Ось тест у формі linqpad. Не соромтесь робити репост власним методом:
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; }
}