TLDR:
Performans ve kötü uygulama ile ilgili iddiaların bir sürü yanıtı, bu yüzden burada açıklığa kavuşuyorum.
İstisna yolu, daha fazla sayıda döndürülen sütun için daha hızlıdır, döngü yolu daha az sayıda sütun için daha hızlıdır ve geçiş noktası yaklaşık 11 sütundur. Bir grafik ve test kodunu görmek için aşağı kaydırın.
Tam cevap:
Bazı en iyi yanıtların kodu işe yarıyor, ancak burada mantıkta istisna işlemenin kabulüne ve bununla ilgili performansa dayalı "daha iyi" cevap için temel bir tartışma var.
Bunu açıklığa kavuşturmak için, CATCHING istisnaları hakkında çok fazla rehberlik olduğuna inanmıyorum. Microsoft'un THROWING istisnaları hakkında bazı rehberleri vardır . Orada şöyle diyorlar:
Mümkünse normal kontrol akışı için istisnalar KULLANMAYIN.
İlk not "mümkünse" nin yumuşaklığıdır. Daha da önemlisi, açıklama bu bağlamı verir:
framework designers should design APIs so users can write code that does not throw exceptions
Bunun anlamı, başka biri tarafından tüketilebilecek bir API yazıyorsanız, denemeye / yakalamaya gerek kalmadan onlara bir istisnada gezinme yeteneği vermesidir. Örneğin, istisna atma Ayrıştırma yönteminizle birlikte bir TryParse sağlayın. Bu hiçbir yerde bir istisna yakalamamanız gerektiği anlamına gelmez.
Ayrıca, başka bir kullanıcının belirttiği gibi, yakalamalar her zaman türe göre filtrelemeye izin vermiştir ve bir süre son olarak when cümlesi ile daha fazla filtrelemeye izin vermiştir. . Bu, onları kullanmamamız gerekiyorsa, dil özelliklerinin israfı gibi görünüyor.
Atılan bir istisna için BAZI maliyet olduğu ve bu maliyetin ağır bir döngüde MAYIS etkisi performansını söyleyebiliriz. Ancak, bir "bağlantılı uygulama" da istisna maliyetinin ihmal edilebileceği de söylenebilir. Gerçek maliyet on yıl önce araştırıldı: https://stackoverflow.com/a/891230/852208
Başka bir deyişle, bir veritabanının bağlantı ve sorgusunun maliyeti atılan bir istisnanın maliyetini gölgede bırakacaktır.
Bütün bunlar bir yana, hangi yöntemin gerçekten daha hızlı olduğunu belirlemek istedim. Beklendiği gibi somut bir cevap yoktur.
Sütun sayısı arttıkça, sütunların üzerinden dönen tüm kodlar yavaşlar. İstisnalara dayanan herhangi bir kodun, sorgunun bulunamamasına bağlı olarak yavaşlayacağı da söylenebilir.
Hem Chad Grant hem de Matt Hamilton'ın cevaplarını alarak, her iki yöntemi de 20 sütuna kadar ve% 50'ye kadar hata oranıyla çalıştırdım (OP, bu iki testi farklı proclar arasında kullandığını belirtti, bu yüzden iki kadar az olduğunu varsaydım) .
LinqPad ile çizilen sonuçlar şunlardır:
Buradaki zikzaklar, her sütun sayısındaki hata oranlarıdır (sütun bulunamadı).
Daha dar sonuç kümelerine göre, döngü iyi bir seçimdir. Ancak, GetOrdinal / Exception yöntemi neredeyse sütun sayısı kadar duyarlı değildir ve hemen hemen 11 sütun civarında döngü yönteminden daha iyi performans göstermeye başlar.
11 sütun tüm uygulama üzerinde döndürülen ortalama sütun sayısı makul olarak makul gibi akıllıca bir tercih performans var dedi. Her iki durumda da burada bir milisaniyelik kesirlerden bahsediyoruz.
Ancak, bir kod basitlik yönü ve takma destek, muhtemelen GetOrdinal yolu ile gitmek istiyorum.
İşte linqpad formundaki test. Kendi yönteminizle yeniden yayınlamaktan çekinmeyin:
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; }
}