TLDR:
มีคำตอบมากมายเกี่ยวกับการอ้างสิทธิ์เกี่ยวกับประสิทธิภาพและการปฏิบัติที่ไม่ดีดังนั้นฉันจึงอธิบายให้ชัดเจนที่นี่
เส้นทางการยกเว้นนั้นเร็วกว่าสำหรับจำนวนคอลัมน์ที่คืนกลับมามากขึ้นเส้นทางลูปจะเร็วขึ้นสำหรับจำนวนคอลัมน์ที่ต่ำกว่าและจุดครอสโอเวอร์จะอยู่ที่ประมาณ 11 คอลัมน์ เลื่อนไปที่ด้านล่างเพื่อดูกราฟและรหัสทดสอบ
คำตอบแบบเต็ม:
โค้ดสำหรับคำตอบยอดนิยมบางข้อใช้งานได้ แต่มีข้อโต้แย้งที่สำคัญสำหรับคำตอบที่ "ดีกว่า" นี้ขึ้นอยู่กับการยอมรับข้อยกเว้นในตรรกะและประสิทธิภาพที่เกี่ยวข้อง
เพื่อล้างสิ่งนั้นออกไปฉันไม่เชื่อว่ามีคำแนะนำมากมายเกี่ยวกับข้อยกเว้นการจับ Microsoft มีคำแนะนำบางอย่างเกี่ยวกับข้อยกเว้นการโยน ที่นั่นพวกเขาทำรัฐ:
อย่าใช้ข้อยกเว้นสำหรับการไหลปกติของการควบคุมถ้าเป็นไปได้
หมายเหตุแรกคือความผ่อนปรนของ "ถ้าเป็นไปได้" ที่สำคัญคำอธิบายให้บริบทนี้:
framework designers should design APIs so users can write code that does not throw exceptions
หมายความว่าถ้าคุณกำลังเขียน API ที่คนอื่นอาจใช้ให้ให้พวกเขาสามารถนำทางข้อยกเว้นโดยไม่ต้องลอง / จับ ตัวอย่างเช่นให้ TryParse ด้วยวิธีการแยกวิเคราะห์การแยกข้อยกเว้นของคุณ ไม่มีที่ไหนพูดเช่นนี้ได้ว่าคุณไม่ควรรับข้อยกเว้น
นอกจากนี้เป็นผู้ใช้อื่นชี้ให้เห็นจับต้องได้รับอนุญาตเสมอกรองตามประเภทและค่อนข้างเร็ว ๆ นี้ช่วยให้การกรองเพิ่มเติมผ่านทางข้อเมื่อ ดูเหมือนว่าจะเป็นคุณลักษณะด้านภาษาที่เสียไปหากเราไม่ควรใช้มัน
อาจกล่าวได้ว่ามีค่าใช้จ่ายบางอย่างสำหรับข้อยกเว้นที่ส่งออกไปและค่าใช้จ่ายนั้นอาจส่งผลกระทบต่อประสิทธิภาพในวงที่มีน้ำหนักมาก อย่างไรก็ตามอาจกล่าวได้ว่าค่าใช้จ่ายของข้อยกเว้นจะเล็กน้อยใน "แอปพลิเคชันที่เชื่อมต่อ" ค่าใช้จ่ายจริงถูกตรวจสอบกว่าทศวรรษที่ผ่านมา: https://stackoverflow.com/a/891230/852208
ในคำอื่น ๆ ค่าใช้จ่ายของการเชื่อมต่อและแบบสอบถามของฐานข้อมูลมีแนวโน้มที่จะแคระว่าเป็นข้อยกเว้นโยน
ทั้งหมดที่เหลือฉันต้องการที่จะกำหนดวิธีการที่แท้จริงได้เร็วขึ้น อย่างที่คาดไว้ไม่มีคำตอบที่เป็นรูปธรรม
รหัสใด ๆ ที่วนรอบคอลัมน์จะช้าลงเมื่อมีจำนวนคอลัมน์อยู่ นอกจากนี้ยังสามารถกล่าวได้ว่ารหัสใด ๆ ที่อาศัยข้อยกเว้นจะช้าขึ้นอยู่กับอัตราที่แบบสอบถามไม่สามารถพบได้
จากคำตอบของทั้ง Chad Grant และ Matt Hamilton ฉันใช้ทั้งสองวิธีที่มีคอลัมน์มากถึง 20 คอลัมน์และอัตราความผิดพลาดสูงถึง 50% (OP ระบุว่าเขาใช้การทดสอบสองแบบนี้ระหว่าง procs ที่แตกต่างกันดังนั้นฉันจึงสันนิษฐานว่ามีสองเท่า) .
นี่คือผลลัพธ์ที่ได้วางแผนด้วย 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; }
}