TLDR:
Banyak jawaban dengan klaim tentang kinerja dan praktik buruk, jadi saya jelaskan di sini.
Rute pengecualian lebih cepat untuk jumlah kolom yang dikembalikan lebih tinggi, rute loop lebih cepat untuk jumlah kolom yang lebih rendah, dan titik crossover sekitar 11 kolom. Gulir ke bawah untuk melihat grafik dan kode uji.
Jawaban lengkap:
Kode untuk beberapa jawaban teratas berfungsi, tetapi ada debat mendasar di sini untuk jawaban "lebih baik" berdasarkan penerimaan penanganan pengecualian dalam logika dan terkait kinerjanya.
Untuk menghapusnya, saya tidak percaya ada banyak panduan tentang pengecualian PENCOCOKAN. Microsoft memang memiliki beberapa panduan tentang pengecualian. Di sana mereka menyatakan:
JANGAN gunakan pengecualian untuk aliran kontrol normal, jika memungkinkan.
Catatan pertama adalah keringanan hukuman "jika mungkin". Lebih penting lagi, deskripsi memberikan konteks ini:
framework designers should design APIs so users can write code that does not throw exceptions
Artinya adalah jika Anda menulis API yang mungkin dikonsumsi oleh orang lain, beri mereka kemampuan untuk menavigasi pengecualian tanpa mencoba / menangkap. Misalnya, berikan TryParse dengan metode Parse pelempar pengecualian Anda. Namun tidak ada yang mengatakan bahwa Anda tidak harus menangkap pengecualian.
Lebih jauh, seperti yang ditunjukkan oleh pengguna lain, tangkapan selalu memungkinkan penyaringan berdasarkan jenis dan agak baru memungkinkan penyaringan lebih lanjut melalui klausa when . Ini sepertinya membuang-buang fitur bahasa jika kita tidak seharusnya menggunakannya.
Dapat dikatakan bahwa ada BEBERAPA biaya untuk pengecualian yang dilemparkan, dan biaya yang MUNGKIN berdampak pada kinerja dalam loop yang berat. Namun, dapat juga dikatakan bahwa biaya pengecualian akan diabaikan dalam "aplikasi yang terhubung". Biaya aktual telah diselidiki lebih dari satu dekade yang lalu: https://stackoverflow.com/a/891230/852208
Dengan kata lain, biaya koneksi dan permintaan basis data cenderung kecil dibandingkan dengan pengecualian yang dilemparkan.
Selain itu, saya ingin menentukan metode mana yang benar-benar lebih cepat. Seperti yang diharapkan tidak ada jawaban konkret.
Kode apa pun yang loop di atas kolom menjadi lebih lambat karena jumlah kolom ada. Dapat juga dikatakan bahwa kode apa pun yang bergantung pada pengecualian akan melambat tergantung pada tingkat di mana kueri gagal ditemukan.
Mengambil jawaban dari Chad Grant dan Matt Hamilton, saya menjalankan kedua metode dengan hingga 20 kolom dan hingga tingkat kesalahan 50% (OP mengindikasikan bahwa ia menggunakan dua tes ini antara procs yang berbeda, jadi saya mengasumsikan sedikitnya dua) .
Inilah hasilnya, diplot dengan LinqPad:
Zigzag di sini adalah tingkat kesalahan (kolom tidak ditemukan) dalam setiap jumlah kolom.
Lebih dari set hasil yang lebih sempit, perulangan adalah pilihan yang baik. Namun, metode GetOrdinal / Exception hampir tidak sensitif terhadap jumlah kolom dan mulai mengungguli metode perulangan tepat di sekitar 11 kolom.
Yang mengatakan saya tidak benar-benar memiliki kinerja preferensi bijaksana karena 11 kolom terdengar masuk akal karena jumlah rata-rata kolom dikembalikan ke seluruh aplikasi. Dalam kedua kasus kita berbicara tentang pecahan satu milidetik di sini.
Namun, dari aspek kesederhanaan kode, dan dukungan alias, saya mungkin akan pergi dengan rute GetOrdinal.
Berikut ini adalah tes dalam bentuk linqpad. Merasa bebas untuk mengirim ulang dengan metode Anda sendiri:
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; }
}