ตรวจสอบชื่อคอลัมน์ในวัตถุ SqlDataReader


212

ฉันจะตรวจสอบเพื่อดูว่ามีคอลัมน์ในSqlDataReaderวัตถุได้อย่างไร ในชั้นการเข้าถึงข้อมูลของฉันฉันได้สร้างวิธีการที่สร้างวัตถุเดียวกันสำหรับการเรียกกระบวนงานที่เก็บไว้หลายรายการ หนึ่งในกระบวนงานที่เก็บไว้มีคอลัมน์เพิ่มเติมที่ไม่ได้ใช้โดยขั้นตอนการจัดเก็บอื่น ๆ ฉันต้องการแก้ไขวิธีการเพื่อรองรับทุกสถานการณ์

ใบสมัครของฉันเขียนด้วยภาษา C #

คำตอบ:


332
public static class DataRecordExtensions
{
    public static bool HasColumn(this IDataRecord dr, string columnName)
    {
        for (int i=0; i < dr.FieldCount; i++)
        {
            if (dr.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase))
                return true;
        }
        return false;
    }
}

การใช้Exceptions สำหรับลอจิกควบคุมเหมือนในคำตอบอื่น ๆถือเป็นการฝึกฝนที่ไม่ดีและมีต้นทุนด้านประสิทธิภาพ นอกจากนี้ยังส่งผลบวกที่ผิดพลาดให้กับผู้สร้างโปรไฟล์ของข้อยกเว้น # ข้อและพระเจ้าช่วยให้ทุกคนตั้งค่าดีบักเกอร์เพื่อแยกข้อยกเว้น

GetSchemaTable () เป็นคำแนะนำอีกข้อหนึ่งในหลายคำตอบ นี่จะไม่ใช่วิธีที่ตรวจสอบล่วงหน้าสำหรับฟิลด์ที่มีอยู่เนื่องจากไม่ได้นำมาใช้ในทุกเวอร์ชัน (เป็นนามธรรมและขว้าง NotSupportedException ใน dotnetcore บางเวอร์ชัน) GetSchemaTable ยังเป็นผลการดำเนินงาน overkill ฉลาดเหมือนมันเป็นฟังก์ชั่นหนักสวยถ้าคุณตรวจสอบแหล่งที่มา

การวนลูปผ่านฟิลด์สามารถมีผลการทำงานเล็กน้อยหากคุณใช้งานบ่อยและคุณอาจต้องการพิจารณาแคชผลลัพธ์


เกิดอะไรขึ้นถ้ามีการใช้นามแฝง การเปรียบเทียบชื่อจะล้มเหลว
Murphybro2

เป็นที่ถกเถียงกันอยู่ว่าการใช้โฟลว์ข้อยกเว้นเป็นการฝึกฝนที่ไม่ดี ครั้งหนึ่งเคยคิดว่าไม่ดีเพราะผู้ให้บริการรายอื่นมีความเกี่ยวข้องค่อนข้างแพง แต่ไม่สำคัญในแอปพลิเคชันที่เชื่อมต่อ เป้าบินวัด 40-118 ข้อยกเว้นต่อมิลลิวินาทีขึ้นอยู่กับความลึกของสแต็คทั้งหมดกลับมาตามทางในปี 2006 stackoverflow.com/a/891230/852208 นอกจากนี้หากไม่มีการทดสอบอาจเป็นไปได้ว่ารหัสนี้ช้าลงโดยมีการตรวจสอบกรณีโดยเฉลี่ยครึ่งหนึ่งของคอลัมน์ทั้งหมด (แม้ว่าจะยังเล็กน้อยในแอปที่เชื่อมต่อ db) ฉันจะแก้ไขคำตอบนี้ให้รวมเฉพาะย่อหน้าตรงกลางเนื่องจากอีกสองข้อเป็นความคิดเห็น
b_levitt

3
@b_levitt มันไม่เป็นที่ถกเถียงกันมันเป็นรหัสอึและคุณไม่ควรพึ่งพาข้อยกเว้นสำหรับโฟลว์การควบคุม
Chad Grant

เช่นเดียวกับสองประโยคที่ฉันได้ชี้ไปว่าเป็นอีกความคิดเห็นที่ไม่สนับสนุนเหตุผลใด ๆ ที่เกินกว่าประสิทธิภาพในการใช้งานการคำนวณอย่างหมดจด ฉันกล้าให้คุณตั้งค่าดีบักเกอร์เพื่อทำลายข้อยกเว้นทั้งหมดและปิดการใช้งานเพียงรหัสของฉันและคุณจะเห็นว่าแม้แต่กรอบงานและห้องสมุดอื่น ๆ กำลังทำสิ่งนี้อยู่ปัญหาของคำแนะนำของคุณคือมันผลักนักพัฒนาให้ส่งคืนรหัสที่มากที่สุด เห็นด้วยเป็นรูปแบบที่ด้อยกว่า: stackoverflow.com/questions/99683/... วิธีการดังกล่าวล้มเหลวในการทดสอบ "หลุมแห่งความสำเร็จ"
b_levitt

จากมุมมองโค้ดคำตอบของคุณคือคำตอบที่ถูกต้อง แต่ความคิดเห็นของคุณพยายามที่จะให้น้ำหนักเป็นคำตอบที่ดีกว่าสำหรับคำตอบด้วยการลอง / จับ (ซึ่งจัดการชื่อแทน) ด้วย
b_levitt

66

จะดีกว่ามากเมื่อใช้ฟังก์ชันบูลีนนี้:

r.GetSchemaTable().Columns.Contains(field)

One call - ไม่มีข้อยกเว้น มันอาจส่งข้อยกเว้นภายใน แต่ฉันไม่คิดอย่างนั้น

หมายเหตุ: ในความคิดเห็นด้านล่างเราพบสิ่งนี้ ... รหัสที่ถูกต้องเป็นจริงนี้:

public static bool HasColumn(DbDataReader Reader, string ColumnName) { 
    foreach (DataRow row in Reader.GetSchemaTable().Rows) { 
        if (row["ColumnName"].ToString() == ColumnName) 
            return true; 
    } //Still here? Column not found. 
    return false; 
}

5
@ จัสมิน: ฉันพูดเร็วเกินไป! รหัสของคุณตรวจสอบคอลัมน์ในตาราง schema ไม่ใช่ชุดผลลัพธ์ของคุณ คุณต้องเปรียบเทียบ "field" (สมมติว่า "field" เป็นชื่อคอลัมน์) กับค่าของฟิลด์ "ColumnName" ของแต่ละแถว ทำลายเมื่อคุณพบมันกลับเท็จถ้าคุณไม่
Steve J

4
@Steve J: เมื่อไหร่ที่ resultset จะไม่มีคอลัมน์ใน GetSchemaTable?
อวยพร Yahu

1
สำหรับผู้อื่นที่สับสนสิ่งนี้ไม่ทำงาน ดูคำตอบด้านล่างเกี่ยวกับการดึงแถว ColumnName จากตาราง schema และใช้งาน
Jason Jackson

3
ใช่มันไม่ทำงาน ใครที่โหวตขึ้นมาหลายต่อหลายครั้ง ??? มันจะช่วยฉันประหยัดเวลาในการดีบั๊กในภายหลังถ้าคำตอบนี้ไม่ได้อยู่ที่นี่!
c00000fd

1
@ จัสมินพวกเขาทั้งสองทำงานอย่างไร ไม่ได้จริงๆ โปรดลบส่วนแรกของคำตอบของคุณออก ฉันจะทำเอง แต่สำหรับความคิดเห็นล่าสุดของคุณ!
nawfal

33

ฉันคิดว่าทางออกที่ดีที่สุดของคุณคือการเรียกGetOrdinal ("columnName")บน DataReader ของคุณและรับ IndexOutOfRangeException ในกรณีที่ไม่มีคอลัมน์อยู่

ในความเป็นจริงเรามาทำวิธีการขยาย:

public static bool HasColumn(this IDataRecord r, string columnName)
{
    try
    {
        return r.GetOrdinal(columnName) >= 0;
    }
    catch (IndexOutOfRangeException)
    {
        return false;
    }
}

แก้ไข

ตกลงโพสต์นี้เริ่มรวบรวมคะแนนไม่กี่เมื่อเร็ว ๆ นี้และฉันไม่สามารถลบได้เพราะเป็นคำตอบที่ยอมรับดังนั้นฉันจะอัปเดตและ (ฉันหวังว่า) พยายามปรับการใช้ข้อยกเว้นให้เหมาะสม การควบคุมการไหล

อีกวิธีหนึ่งในการบรรลุเป้าหมายดังที่โพสต์โดย Chad Grantคือการวนซ้ำแต่ละฟิลด์ใน DataReader และทำการเปรียบเทียบแบบคำนึงถึงขนาดตัวพิมพ์ของชื่อฟิลด์ที่คุณต้องการ สิ่งนี้จะทำงานได้ดีจริง ๆ และจริง ๆ แล้วอาจจะทำงานได้ดีกว่าวิธีการของฉันด้านบน แน่นอนฉันจะไม่ใช้วิธีการข้างต้นภายในวงที่ performace เป็นปัญหา

ฉันสามารถนึกถึงสถานการณ์หนึ่งที่ลอง / GetOrdinal / วิธีการจับจะทำงานที่วงไม่ อย่างไรก็ตามมันเป็นสถานการณ์สมมุติที่สมบูรณ์ในตอนนี้ดังนั้นมันจึงเป็นเหตุผลที่อ่อนแอมาก จงอดทนกับฉันและดูว่าคุณคิดอย่างไร

ลองนึกภาพฐานข้อมูลที่อนุญาตให้คุณใช้คอลัมน์ "นามแฝง" ภายในตาราง ลองนึกภาพว่าฉันสามารถกำหนดตารางด้วยคอลัมน์ชื่อ "EmployeeName" แต่ให้ชื่อแทนของ "EmpName" และการเลือกชื่อใดชื่อหนึ่งจะส่งคืนข้อมูลในคอลัมน์นั้น กับฉันจนถึงตอนนี้

ตอนนี้ลองนึกภาพว่ามีผู้ให้บริการ ADO.NET สำหรับฐานข้อมูลนั้นและพวกเขาได้ประมวลผลการใช้งาน IDataReader ซึ่งจะนำชื่อคอลัมน์มาพิจารณา

ตอนนี้dr.GetName(i)(ตามที่ใช้ในคำตอบของแช้ด) สามารถส่งคืนสตริงเดียวเท่านั้นดังนั้นจึงต้องส่งคืน"นามแฝง" หนึ่งเดียวในคอลัมน์ อย่างไรก็ตามGetOrdinal("EmpName")สามารถใช้การดำเนินการภายในของเขตข้อมูลของผู้ให้บริการนี้เพื่อตรวจสอบชื่อแทนของแต่ละคอลัมน์สำหรับชื่อที่คุณต้องการ

ในสถานการณ์ "คอลัมน์ที่ใช้นามแฝง" สมมุตินี้ลอง / GetOrdinal / วิธีการจับจะเป็นวิธีเดียวที่จะตรวจสอบให้แน่ใจว่าคุณกำลังตรวจสอบทุกรูปแบบของชื่อคอลัมน์ในชุดผลลัพธ์

บอบบาง? แน่ใจ แต่คุ้มค่ากับความคิด จริงๆแล้วฉันค่อนข้างจะเป็นวิธี "HasColumn" อย่างเป็นทางการใน IDataRecord


15
ใช้ข้อยกเว้นสำหรับตรรกะการควบคุมหรือไม่ ไม่ไม่ไม่
ชาดแกรนท์

28
มีเรื่องเล็ก ๆ น้อย ๆ ที่ทุกคนมองเห็นเมื่อฉันโพสต์คำถามนี้ไว้ก่อน ... ฉันถามคำถามเมื่อ 12/8/08 และแมตต์โพสต์คำตอบของเขาเมื่อ 12/17/08 ทุกคนทำเหม็นเกี่ยวกับการจับยกเว้นสำหรับลอจิกควบคุม แต่ไม่ได้ให้ทางเลือกที่มั่นคงจนกว่า 5/1/09 นั่นคือเหตุผลที่มันถูกทำเครื่องหมายเป็นคำตอบเดิม ฉันยังคงใช้โซลูชันนี้ในวันนี้
Michael Kniskern

19
การดำเนินการนี้จะมีผลกระทบต่อเมื่อคอลัมน์ไม่ได้อยู่ที่นั่น วิธีอื่น ๆ ที่อธิบายไว้จะมีผลกระทบต่อประสิทธิภาพและประสิทธิภาพที่เพิ่มขึ้นทุกครั้ง แม้ว่าโดยทั่วไปจะเป็นแนวปฏิบัติที่ไม่ดีที่จะหลีกเลี่ยงการใช้การจัดการข้อยกเว้นสำหรับการไหลของการควบคุมโซลูชันนี้ไม่ควรถูกตัดออกโดยไม่พิจารณาก่อนว่ามันใช้ได้ในกรณีของคุณหรือไม่
Nick Harrison

5
+1 ฉันตกลงกับ "อย่าใช้ข้อยกเว้นสำหรับลอจิกควบคุม" เป็นกฎการออกแบบที่กว้างขวาง ไม่ได้หมายความว่า "หลีกเลี่ยงค่าใช้จ่ายทั้งหมด" คำตอบนั้นเป็นวิธีการแก้ปัญหาที่ได้รับการบันทึกไว้อย่างดีและอย่างที่ @Nick กล่าวว่าประสิทธิภาพในการทำงาน (ถ้ามี .. ) จะเกิดขึ้นเฉพาะเมื่อคอลัมน์ไม่มีอยู่
Larry

2
การใช้ข้อยกเว้นเป็นลอจิกควบคุมทำให้การดีบักยุ่งยากขึ้นในประสบการณ์ของฉัน คุณต้องยกเลิกการเลือก "โยน" ใน "ข้อยกเว้นรันไทม์ภาษาทั่วไป" จากนั้นเมื่อคุณได้รับข้อยกเว้นจริงอาจทำให้ตัวจัดการบางตัวไม่อยู่ในบรรทัดที่มีปัญหา
cedd

30

ในหนึ่งบรรทัดใช้สิ่งนี้หลังจากการดึง DataReader ของคุณ:

var fieldNames = Enumerable.Range(0, dr.FieldCount).Select(i => dr.GetName(i)).ToArray();

จากนั้น

if (fieldNames.Contains("myField"))
{
    var myFieldValue = dr["myField"];
    ...

แก้ไข

หนึ่งซับที่มีประสิทธิภาพมากขึ้นที่ไม่ต้องโหลดสคีมา:

var exists = Enumerable.Range(0, dr.FieldCount).Any(i => string.Equals(dr.GetName(i), fieldName, StringComparison.OrdinalIgnoreCase));

คุณกำลังระบุชื่อฟิลด์หลาย ๆ ครั้ง / จัดสรรอาเรย์อื่นเพื่อสแกนด้วยการบรรจุซึ่งจะมีประสิทธิภาพน้อยกว่าในรหัสการรับส่งข้อมูลสูง
Chad Grant

@ChadGrant แน่นอนว่าทำไม Linq one liner มีประสิทธิภาพมากขึ้นเนื่องจากมีการทำซ้ำเพียงครั้งเดียว
Larry

18

นี่คือตัวอย่างการทำงานสำหรับแนวคิดของ Jasmin:

var cols = r.GetSchemaTable().Rows.Cast<DataRow>().Select
    (row => row["ColumnName"] as string).ToList(); 

if (cols.Contains("the column name"))
{

}

1
เฉพาะในกรณีที่คุณลอง / จับรอบ ๆ มัน
Donald.Record

คุณสามารถทำให้ความคิดนี้ง่ายขึ้นด้วย: reader.GetSchemaTable (). Columns.Contains ("myFiled")
Lev Z

ใช้ GetSchemaTable () มากเกินไป (จัดสรรอย่างชาญฉลาด) เพียงแค่ค้นหาชื่อคอลัมน์ ตรวจสอบแหล่งgithub.com/microsoft/referencesource/blob/…
Chad Grant

12

สิ่งนี้ใช้ได้กับฉัน:

bool hasColumnName = reader.GetSchemaTable().AsEnumerable().Any(c => c["ColumnName"] == "YOUR_COLUMN_NAME");

ใช้ GetSchemaTable () มากเกินไป (จัดสรรอย่างชาญฉลาด) เพียงแค่ค้นหาชื่อคอลัมน์ และมันไม่ได้ติดตั้งใน dotnet core ทุกรุ่น ตรวจสอบแหล่งgithub.com/microsoft/referencesource/blob/…
Chad Grant

10

ต่อไปนี้ง่ายและใช้งานได้สำหรับฉัน:

 bool hasMyColumn = (reader.GetSchemaTable().Select("ColumnName = 'MyColumnName'").Count() == 1);

ใช้ GetSchemaTable () มากเกินไป (จัดสรรอย่างชาญฉลาด) เพียงแค่ค้นหาชื่อคอลัมน์ ตรวจสอบแหล่งgithub.com/microsoft/referencesource/blob/…
Chad Grant

8

ถ้าคุณอ่านคำถาม Michael ถามเกี่ยวกับ DataReader ไม่ใช่ DataRecord folks ทำให้วัตถุของคุณถูกต้อง

การใช้ a r.GetSchemaTable().Columns.Contains(field)บน DataRecord ใช้งานได้ แต่จะคืนค่าคอลัมน์ BS (ดูภาพหน้าจอด้านล่าง)

หากต้องการดูว่ามีคอลัมน์ข้อมูลอยู่และมีข้อมูลใน DataReader หรือไม่ให้ใช้ส่วนขยายต่อไปนี้:

public static class DataReaderExtensions
{
    /// <summary>
    /// Checks if a column's value is DBNull
    /// </summary>
    /// <param name="dataReader">The data reader</param>
    /// <param name="columnName">The column name</param>
    /// <returns>A bool indicating if the column's value is DBNull</returns>
    public static bool IsDBNull(this IDataReader dataReader, string columnName)
    {
        return dataReader[columnName] == DBNull.Value;
    }

    /// <summary>
    /// Checks if a column exists in a data reader
    /// </summary>
    /// <param name="dataReader">The data reader</param>
    /// <param name="columnName">The column name</param>
    /// <returns>A bool indicating the column exists</returns>
    public static bool ContainsColumn(this IDataReader dataReader, string columnName)
    {
        /// See: http://stackoverflow.com/questions/373230/check-for-column-name-in-a-sqldatareader-object/7248381#7248381
        try
        {
            return dataReader.GetOrdinal(columnName) >= 0;
        }
        catch (IndexOutOfRangeException)
        {
            return false;
        }
    }
}

การใช้งาน:

    public static bool CanCreate(SqlDataReader dataReader)
    {
        return dataReader.ContainsColumn("RoleTemplateId") 
            && !dataReader.IsDBNull("RoleTemplateId");
    }

การเรียกr.GetSchemaTable().ColumnsDataReader ส่งคืนคอลัมน์ BS:

การเรียกใช้ GetSchemeTable ใน DataReader


ดูความคิดเห็นภายใต้คำตอบ Matts
nawfal

คุณหมายถึงอะไรโดยDataRecord ทำงานแต่มันกลับคอลัมน์ BS ? คุณหมายถึงมันทำงาน (และให้ผลลัพธ์ที่ผิด)?
nawfal

2
"ทำให้วัตถุของคุณถูกต้อง" - แต่การดำเนินการIDataReader IDataRecordพวกเขามีอินเตอร์เฟซที่แตกต่างกันของวัตถุเดียวกัน - เช่นเดียวกับICollection<T>และมีอินเตอร์เฟซที่แตกต่างกันของ IEnumerable<T> อนุญาตให้ก้าวหน้าไปยังระเบียนถัดไปในขณะที่อนุญาตให้อ่านจากระเบียนปัจจุบัน วิธีการที่ใช้ในคำตอบนี้ทั้งหมดมาจากส่วนต่อประสาน ดูstackoverflow.com/a/1357743/221708สำหรับคำอธิบายว่าเพราะเหตุใดจึงประกาศพารามิเตอร์ตามที่ต้องการ List<T>IDataReaderIDataRecordIDataRecordIDataRecord
Daniel Schilling

โหวตขึ้นเพื่อแสดงว่าทำไมr.GetSchemaTable().Columnsคำตอบที่ไม่ถูกต้องสำหรับคำถามนี้
Daniel Schilling

GetName () สืบทอดมาจากส่วนต่อประสาน IDataRecord ไปยัง IDataReader การกำหนดเป้าหมายอินเทอร์เฟซพื้นฐานคือรหัสที่ถูกต้อง
Chad Grant

7

ฉันเขียนสำหรับผู้ใช้ Visual Basic:

Protected Function HasColumnAndValue(ByRef reader As IDataReader, ByVal columnName As String) As Boolean
    For i As Integer = 0 To reader.FieldCount - 1
        If reader.GetName(i).Equals(columnName) Then
            Return Not IsDBNull(reader(columnName))
        End If
    Next

    Return False
End Function

ฉันคิดว่านี่มีประสิทธิภาพมากขึ้นและการใช้งานคือ:

If HasColumnAndValue(reader, "ID_USER") Then
    Me.UserID = reader.GetDecimal(reader.GetOrdinal("ID_USER")).ToString()
End If

4

ต่อไปนี้เป็นคำตอบที่ยอมรับรุ่นหนึ่ง linq

Enumerable.Range(0, reader.FieldCount).Any(i => reader.GetName(i) == "COLUMN_NAME_GOES_HERE")

การเปรียบเทียบตัวพิมพ์เล็กและใหญ่ ... ทำไม
Chad Grant

4

นี่คือคำตอบของจัสมีนในหนึ่งบรรทัด ... (อีกอันง่าย ๆ !):

reader.GetSchemaTable().Select("ColumnName='MyCol'").Length > 0;

ใช้ GetSchemaTable () มากเกินไป (จัดสรรอย่างชาญฉลาด) เพียงแค่ค้นหาชื่อคอลัมน์ ตรวจสอบแหล่งgithub.com/microsoft/referencesource/blob/…
Chad Grant

@ChadGrant เป็นไปได้ ผมคิดว่าหนึ่งจะต้องเลือกอย่างชาญฉลาดขึ้นอยู่กับบริบทและความถี่มันเป็นสิ่งจำเป็นที่จะใช้นี้ ...
spaark

3
Hashtable ht = new Hashtable();
    Hashtable CreateColumnHash(SqlDataReader dr)
    {
        ht = new Hashtable();
        for (int i = 0; i < dr.FieldCount; i++)
        {
            ht.Add(dr.GetName(i), dr.GetName(i));
        }
        return ht;
    }

    bool ValidateColumn(string ColumnName)
    {
        return ht.Contains(ColumnName);
    }

3

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: ผลลัพธ์ - ชุดที่ 1 คือวนซ้ำ 2 คือข้อยกเว้น

ซิกแซกที่นี่คืออัตราความผิดปกติ (ไม่พบคอลัมน์) ภายในแต่ละคอลัมน์

ด้วยชุดผลลัพธ์ที่แคบกว่าการวนซ้ำเป็นตัวเลือกที่ดี อย่างไรก็ตามวิธีการ 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; }


}

1
คุณมีการครอบงำจิตใจแปลก ๆ อย่างชัดเจนโดยมีข้อยกเว้นบางประการ วิธีที่ดีกว่าก็คือการแคชตำแหน่งคอลัมน์ในการค้นหาแบบสแตติกเพื่อประสิทธิภาพและใช้การค้นหาจำนวนเต็ม
Chad Grant

ปัญหาอื่นที่มีการใช้ข้อยกเว้นเป็นโฟลว์ควบคุมคือพวกเขาแสดงใน profiler เป็น # ของข้อยกเว้นที่เกิดขึ้นเมื่ออยู่ในโค้ดที่คุณแนะนำพวกเขาตั้งใจ ... ไม่ใช่ข้อยกเว้น ไม่พูดถึงการตั้งค่าดีบักเกอร์ของคุณเพื่อทำลายข้อยกเว้นที่เกิดขึ้น รายงานข้อผิดพลาดที่สำคัญที่ไม่ใช่ข้อผิดพลาด คุณไม่ควรทำสิ่งนี้
Chad Grant

1
นอกจากนี้ยังมีตัวนับสำหรับวินาที / วินาทีและตัวกรอง / วินาที สิ่งเหล่านั้นก็เลวร้ายเช่นกัน? ฉันจะเรียกมันว่า caveat ที่เป็นไปได้ - ของจริงแรกที่คุณให้ เคาน์เตอร์เป็นเพียงข้อมูล พวกเขาไม่ได้มีความหมายอะไรนอกจากว่าพวกเขาจะสอดคล้องกับปัญหาด้านประสิทธิภาพ - และในกรณีนี้ฉันได้แสดงจุดที่ข้อยกเว้นมีประสิทธิภาพที่ดีขึ้นแล้ว ฉันยังระบุด้วยว่ากรอบงานและไลบรารีมีข้อยกเว้นมากมายอยู่แล้ว ฉันมีอินสแตนซ์ของสตูดิโอภาพที่ขว้าง 60 ครั้งต่อวินาทีในขณะนี้ ข้อยกเว้นไม่ใช่ข้อผิดพลาดเว้นแต่จะไม่ถูกตรวจจับ
b_levitt

การวิเคราะห์ที่ยอดเยี่ยม ฉันใช้ผลลัพธ์ในคำตอบใหม่ของฉัน
yazanpro

1

รหัสนี้แก้ไขปัญหาที่ Levitikon มีกับรหัส: (ดัดแปลงจาก: [1]: http://msdn.microsoft.com/en-us/library/system.data.datatablereader.getschematable.aspx )

public List<string> GetColumnNames(SqlDataReader r)
{
    List<string> ColumnNames = new List<string>();
    DataTable schemaTable = r.GetSchemaTable();
    DataRow row = schemaTable.Rows[0];
    foreach (DataColumn col in schemaTable.Columns)
    {
        if (col.ColumnName == "ColumnName") 
        { 
            ColumnNames.Add(row[col.Ordinal].ToString()); 
            break; 
        }
    }
    return ColumnNames;
}

เหตุผลในการรับชื่อคอลัมน์ที่ไม่มีประโยชน์ทั้งหมดไม่ใช่ชื่อของคอลัมน์จากตารางของคุณ ... เป็นเพราะคุณได้รับชื่อคอลัมน์สคีมา (เช่นชื่อคอลัมน์สำหรับตาราง Schema)

หมายเหตุ: ดูเหมือนว่าจะส่งคืนชื่อของคอลัมน์แรกเท่านั้น ...

แก้ไข: รหัสที่แก้ไขที่ส่งคืนชื่อของคอลัมน์ทั้งหมด แต่คุณไม่สามารถใช้ SqlDataReader ได้

public List<string> ExecuteColumnNamesReader(string command, List<SqlParameter> Params)
{
    List<string> ColumnNames = new List<string>();
    SqlDataAdapter da = new SqlDataAdapter();
    string connection = ""; // your sql connection string
    SqlCommand sqlComm = new SqlCommand(command, connection);
    foreach (SqlParameter p in Params) { sqlComm.Parameters.Add(p); }
    da.SelectCommand = sqlComm;
    DataTable dt = new DataTable();
    da.Fill(dt);
    DataRow row = dt.Rows[0];
    for (int ordinal = 0; ordinal < dt.Columns.Count; ordinal++)
    {
        string column_name = dt.Columns[ordinal].ColumnName;
        ColumnNames.Add(column_name);
    }
    return ColumnNames; // you can then call .Contains("name") on the returned collection
}

หรือในหนึ่งบรรทัดreturn r.GetSchemaTable().Rows.Cast<DataRow>().Select(x => (string)x["ColumnName"]).ToList();:)
nawfal

ใช้ GetSchemaTable () มากเกินไป (จัดสรรอย่างชาญฉลาด) เพียงแค่ค้นหาชื่อคอลัมน์ ตรวจสอบแหล่งgithub.com/microsoft/referencesource/blob/…
Chad Grant

1

เพื่อให้รหัสของคุณแข็งแรงและสะอาดให้ใช้ฟังก์ชันส่วนขยายเดียวเช่นนี้

    Public Module Extensions

        <Extension()>
        Public Function HasColumn(r As SqlDataReader, columnName As String) As Boolean

            Return If(String.IsNullOrEmpty(columnName) OrElse r.FieldCount = 0, False, Enumerable.Range(0, r.FieldCount).Select(Function(i) r.GetName(i)).Contains(columnName, StringComparer.OrdinalIgnoreCase))

        End Function

    End Module

0

ฉันไม่ได้GetSchemaTableไปทำงานจนกว่าฉันจะพบวิธีนี้

โดยทั่วไปฉันทำสิ่งนี้:

Dim myView As DataView = dr.GetSchemaTable().DefaultView
myView.RowFilter = "ColumnName = 'ColumnToBeChecked'"

If myView.Count > 0 AndAlso dr.GetOrdinal("ColumnToBeChecked") <> -1 Then
  obj.ColumnToBeChecked = ColumnFromDb(dr, "ColumnToBeChecked")
End If

0
public static bool DataViewColumnExists(DataView dv, string columnName)
{
    return DataTableColumnExists(dv.Table, columnName);
}

public static bool DataTableColumnExists(DataTable dt, string columnName)
{
    string DebugTrace = "Utils::DataTableColumnExists(" + dt.ToString() + ")";
    try
    {
        return dt.Columns.Contains(columnName);
    }
    catch (Exception ex)
    {
        throw new MyExceptionHandler(ex, DebugTrace);
    }
}

Columns.Contains ไม่คำนึงถึงตัวพิมพ์ใหญ่ - เล็ก


มี () ไม่ส่งข้อยกเว้นรหัสนี้ไม่มีจุดหมาย คุณจะได้รับการยกเว้นตัวชี้โมฆะเท่านั้น
Chad Grant

0

ในสถานการณ์เฉพาะของคุณ (กระบวนการทั้งหมดมีคอลัมน์เดียวกันยกเว้น 1 ซึ่งมี 1 คอลัมน์เพิ่มเติม) มันจะดีกว่าและเร็วกว่าในการตรวจสอบผู้อ่าน คุณสมบัติ FieldCount เพื่อแยกความแตกต่างระหว่างพวกเขา

const int NormalColCount=.....
if(reader.FieldCount > NormalColCount)
{
// Do something special
}

ฉันรู้ว่ามันเป็นโพสต์เก่า แต่ฉันตัดสินใจที่จะตอบเพื่อช่วยเหลืออื่น ๆ ในสถานการณ์เดียวกัน คุณยังสามารถ (สำหรับเหตุผลด้านประสิทธิภาพ) ผสมโซลูชันนี้กับโซลูชันวนซ้ำโซลูชัน


โปรดระบุชื่อโซลูชันที่คุณอ้างอิง ควรผสมสองโซลูชั่นใด
Pablo Jomer

0

คลาสการเข้าถึงข้อมูลของฉันจะต้องเข้ากันได้แบบย้อนหลังดังนั้นฉันอาจพยายามเข้าถึงคอลัมน์ในรุ่นที่ยังไม่มีอยู่ในฐานข้อมูล เรามีชุดข้อมูลที่ค่อนข้างใหญ่บางส่วนถูกส่งคืนดังนั้นฉันจึงไม่ใช่แฟนตัวยงของวิธีการขยายที่ต้องย้ำคอลเลกชันคอลัมน์ DataReader สำหรับแต่ละคุณสมบัติ

ฉันมีคลาสยูทิลิตี้ที่สร้างรายการส่วนตัวของคอลัมน์แล้วมีวิธีการทั่วไปที่พยายามแก้ไขค่าตามชื่อคอลัมน์และประเภทพารามิเตอร์เอาท์พุท

private List<string> _lstString;

public void GetValueByParameter<T>(IDataReader dr, string parameterName, out T returnValue)
{
    returnValue = default(T);

    if (!_lstString.Contains(parameterName))
    {
        Logger.Instance.LogVerbose(this, "missing parameter: " + parameterName);
        return;
    }

    try
    {
        if (dr[parameterName] != null && [parameterName] != DBNull.Value)
            returnValue = (T)dr[parameterName];
    }
    catch (Exception ex)
    {
        Logger.Instance.LogException(this, ex);
    }
}

/// <summary>
/// Reset the global list of columns to reflect the fields in the IDataReader
/// </summary>
/// <param name="dr">The IDataReader being acted upon</param>
/// <param name="NextResult">Advances IDataReader to next result</param>
public void ResetSchemaTable(IDataReader dr, bool nextResult)
{
    if (nextResult)
        dr.NextResult();

    _lstString = new List<string>();

    using (DataTable dataTableSchema = dr.GetSchemaTable())
    {
        if (dataTableSchema != null)
        {
            foreach (DataRow row in dataTableSchema.Rows)
            {
                _lstString.Add(row[dataTableSchema.Columns["ColumnName"]].ToString());
            }
        }
    }
}

จากนั้นฉันก็สามารถเรียกรหัสของฉันได้

using (var dr = ExecuteReader(databaseCommand))
{
    int? outInt;
    string outString;

    Utility.ResetSchemaTable(dr, false);        
    while (dr.Read())
    {
        Utility.GetValueByParameter(dr, "SomeColumn", out outInt);
        if (outInt.HasValue) myIntField = outInt.Value;
    }

    Utility.ResetSchemaTable(dr, true);
    while (dr.Read())
    {
        Utility.GetValueByParameter(dr, "AnotherColumn", out outString);
        if (!string.IsNullOrEmpty(outString)) myIntField = outString;
    }
}

0

กุญแจสำคัญของปัญหาทั้งหมดอยู่ที่นี่ :

if (-1 == index) {
    throw ADP.IndexOutOfRange(fieldName);
}

หากมีการอ้างอิงสามบรรทัด (บรรทัดปัจจุบัน 72, 73 และ 74) จากนั้นคุณสามารถตรวจสอบได้อย่างง่ายดาย-1เพื่อกำหนดว่าคอลัมน์ไม่มีอยู่หรือไม่

วิธีเดียวที่จะทำได้ในขณะนี้เพื่อให้มั่นใจว่าประสิทธิภาพดั้งเดิมคือการใช้Reflectionการนำไปปฏิบัติตามเช่น:

Usings:

using System;
using System.Data;
using System.Reflection;
using System.Data.SqlClient;
using System.Linq;
using System.Web.Compilation; // I'm not sure what the .NET Core equivalent to BuildManager.cs

วิธีการขยายตาม Reflection:

/// Gets the column ordinal, given the name of the column.
/// </summary>
/// <param name="reader"></param>
/// <param name="name">The name of the column.</param>
/// <returns> The zero-based column ordinal. -1 if the column does not exist.</returns>
public static int GetOrdinalSoft(this SqlDataReader reader, string name)
{
    try
    {
        // Note that "Statistics" will not be accounted for in this implemenation
        // If you have SqlConnection.StatisticsEnabled set to true (the default is false), you probably don't want to use this method
        // All of the following logic is inspired by the actual implementation of the framework:
        // https://referencesource.microsoft.com/#System.Data/fx/src/data/System/Data/SqlClient/SqlDataReader.cs,d66096b6f57cac74
        if (name == null)
            throw new ArgumentNullException("fieldName");

        Type sqlDataReaderType = typeof(SqlDataReader);
        object fieldNameLookup = sqlDataReaderType.GetField("_fieldNameLookup", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(reader);
        Type fieldNameLookupType;
        if (fieldNameLookup == null)
        {
            MethodInfo checkMetaDataIsReady = sqlDataReaderType.GetRuntimeMethods().First(x => x.Name == "CheckMetaDataIsReady" && x.GetParameters().Length == 0);
            checkMetaDataIsReady.Invoke(reader, null);
            fieldNameLookupType = BuildManager.GetType("System.Data.ProviderBase.FieldNameLookup", true, false);
            ConstructorInfo ctor = fieldNameLookupType.GetConstructor(new[] { typeof(SqlDataReader), typeof(int) });
            fieldNameLookup = ctor.Invoke(new object[] { reader, sqlDataReaderType.GetField("_defaultLCID", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(reader) });
        }
        else
            fieldNameLookupType = fieldNameLookup.GetType();

        MethodInfo indexOf = fieldNameLookupType.GetMethod("IndexOf", BindingFlags.Public | BindingFlags.Instance, null, new Type[] { typeof(string) }, null);

        return (int)indexOf.Invoke(fieldNameLookup, new object[] { name });
    }
    catch
    {
        // .NET Implemenation might have changed, revert back to the classic solution.
        if (reader.FieldCount > 11) // Performance observation by b_levitt
        {
            try
            {
                return reader.GetOrdinal(name);
            }
            catch
            {
                return -1;
            }
        }
        else
        {
            var exists = Enumerable.Range(0, reader.FieldCount).Any(i => string.Equals(reader.GetName(i), name, StringComparison.OrdinalIgnoreCase));
            if (exists)
                return reader.GetOrdinal(name);
            else
                return -1;
        }
    }
}

-1

คุณยังสามารถเรียกใช้GetSchemaTable ()จาก DataReader ของคุณได้หากคุณต้องการรายการของคอลัมน์และคุณไม่ต้องการได้รับข้อยกเว้น ...


2
มีการถกเถียงกันว่างานนี้เป็นอย่างไร: stackoverflow.com/questions/373230/…
bzlm

-1

เกี่ยวกับ

if (dr.GetSchemaTable().Columns.Contains("accounttype"))
   do something
else
   do something

มันอาจจะไม่ได้ผลในวง


ดูคำตอบของ Levitikonเพื่อดูชนิดของสิ่งต่าง ๆdr.GetSchemaTable().Columns- ไม่ใช่สิ่งที่คุณกำลังมองหา
Daniel Schilling

-1

แม้ว่าจะไม่มีวิธีเปิดเผยต่อสาธารณะ แต่มีวิธีการในชั้นเรียนภายในSystem.Data.ProviderBase.FieldNameLookupที่SqlDataReaderอาศัย

ในการเข้าถึงและรับประสิทธิภาพเนทิฟคุณต้องใช้ ILGenerator เพื่อสร้างวิธีการที่รันไทม์ รหัสต่อไปนี้จะช่วยให้คุณสามารถเข้าถึงโดยตรงint IndexOf(string fieldName)ในSystem.Data.ProviderBase.FieldNameLookupชั้นเรียนรวมถึงการทำหนังสือที่SqlDataReader.GetOrdinal()ไม่ทำให้เกิดผลข้างเคียง ที่สร้างกระจกรหัสที่มีอยู่SqlDataReader.GetOrdinal()ยกเว้นว่ามันเรียกร้องแทนFieldNameLookup.IndexOf() วิธีการเรียกร้องไปยังฟังก์ชั่นและพ่นยกเว้นถ้าจะถูกส่งกลับเพื่อให้เราข้ามพฤติกรรมที่ FieldNameLookup.GetOrdinal()GetOrdinal()IndexOf()-1

using System;
using System.Data;
using System.Data.SqlClient;
using System.Reflection;
using System.Reflection.Emit;

public static class SqlDataReaderExtensions {

   private delegate int IndexOfDelegate(SqlDataReader reader, string name);
   private static IndexOfDelegate IndexOf;

   public static int GetColumnIndex(this SqlDataReader reader, string name) {
      return name == null ? -1 : IndexOf(reader, name);
   }

   public static bool ContainsColumn(this SqlDataReader reader, string name) {
      return name != null && IndexOf(reader, name) >= 0;
   }

   static SqlDataReaderExtensions() {
      Type typeSqlDataReader = typeof(SqlDataReader);
      Type typeSqlStatistics = typeSqlDataReader.Assembly.GetType("System.Data.SqlClient.SqlStatistics", true);
      Type typeFieldNameLookup = typeSqlDataReader.Assembly.GetType("System.Data.ProviderBase.FieldNameLookup", true);

      BindingFlags staticflags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Static;
      BindingFlags instflags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance;

      DynamicMethod dynmethod = new DynamicMethod("SqlDataReader_IndexOf", typeof(int), new Type[2]{ typeSqlDataReader, typeof(string) }, true);
      ILGenerator gen = dynmethod.GetILGenerator();
      gen.DeclareLocal(typeSqlStatistics);
      gen.DeclareLocal(typeof(int));

      // SqlStatistics statistics = (SqlStatistics) null;
      gen.Emit(OpCodes.Ldnull);
      gen.Emit(OpCodes.Stloc_0);
      // try {
      gen.BeginExceptionBlock();
      //    statistics = SqlStatistics.StartTimer(this.Statistics);
      gen.Emit(OpCodes.Ldarg_0); //this
      gen.Emit(OpCodes.Call, typeSqlDataReader.GetProperty("Statistics", instflags | BindingFlags.GetProperty, null, typeSqlStatistics, Type.EmptyTypes, null).GetMethod);
      gen.Emit(OpCodes.Call, typeSqlStatistics.GetMethod("StartTimer", staticflags | BindingFlags.InvokeMethod, null, new Type[] { typeSqlStatistics }, null));
      gen.Emit(OpCodes.Stloc_0); //statistics
      //    if(this._fieldNameLookup == null) {
      Label branchTarget = gen.DefineLabel();
      gen.Emit(OpCodes.Ldarg_0); //this
      gen.Emit(OpCodes.Ldfld, typeSqlDataReader.GetField("_fieldNameLookup", instflags | BindingFlags.GetField));
      gen.Emit(OpCodes.Brtrue_S, branchTarget);
      //       this.CheckMetaDataIsReady();
      gen.Emit(OpCodes.Ldarg_0); //this
      gen.Emit(OpCodes.Call, typeSqlDataReader.GetMethod("CheckMetaDataIsReady", instflags | BindingFlags.InvokeMethod, null, Type.EmptyTypes, null));
      //       this._fieldNameLookup = new FieldNameLookup((IDataRecord)this, this._defaultLCID);
      gen.Emit(OpCodes.Ldarg_0); //this
      gen.Emit(OpCodes.Ldarg_0); //this
      gen.Emit(OpCodes.Ldarg_0); //this
      gen.Emit(OpCodes.Ldfld, typeSqlDataReader.GetField("_defaultLCID", instflags | BindingFlags.GetField));
      gen.Emit(OpCodes.Newobj, typeFieldNameLookup.GetConstructor(instflags, null, new Type[] { typeof(IDataReader), typeof(int) }, null));
      gen.Emit(OpCodes.Stfld, typeSqlDataReader.GetField("_fieldNameLookup", instflags | BindingFlags.SetField));
      //    }
      gen.MarkLabel(branchTarget);
      gen.Emit(OpCodes.Ldarg_0); //this
      gen.Emit(OpCodes.Ldfld, typeSqlDataReader.GetField("_fieldNameLookup", instflags | BindingFlags.GetField));
      gen.Emit(OpCodes.Ldarg_1); //name
      gen.Emit(OpCodes.Call, typeFieldNameLookup.GetMethod("IndexOf", instflags | BindingFlags.InvokeMethod, null, new Type[] { typeof(string) }, null));
      gen.Emit(OpCodes.Stloc_1); //int output
      Label leaveProtectedRegion = gen.DefineLabel();
      gen.Emit(OpCodes.Leave_S, leaveProtectedRegion);
      // } finally {
      gen.BeginFaultBlock();
      //    SqlStatistics.StopTimer(statistics);
      gen.Emit(OpCodes.Ldloc_0); //statistics
      gen.Emit(OpCodes.Call, typeSqlStatistics.GetMethod("StopTimer", staticflags | BindingFlags.InvokeMethod, null, new Type[] { typeSqlStatistics }, null));
      // }
      gen.EndExceptionBlock();
      gen.MarkLabel(leaveProtectedRegion);
      gen.Emit(OpCodes.Ldloc_1);
      gen.Emit(OpCodes.Ret);

      IndexOf = (IndexOfDelegate)dynmethod.CreateDelegate(typeof(IndexOfDelegate));
   }

}

1
รหัสภายในทำสิ่งเดียวกันกับที่ฉันตอบโดยไม่จำเป็นต้องมีการสะท้อนกลับ / ผู้ได้รับมอบหมายที่แปลกประหลาดนี้ มันกำลังแคชการค้นหาต่ออินสแตนซ์ของวัตถุซึ่งจะไม่เป็นประโยชน์เช่นเดียวกับในโลกแห่งความจริงที่คุณต้องการแคชกฎในครั้งแรกที่มีการเรียกใช้คิวรีและใช้แคชนั้นตลอดช่วงอายุของแอปไม่ใช่สร้างแคชใหม่
Chad Grant

-1

งานนี้สำหรับฉัน

public static class DataRecordExtensions
{
        public static bool HasColumn(IDataReader dataReader, string columnName)
        {
            dataReader.GetSchemaTable().DefaultView.RowFilter = $"ColumnName= '{columnName}'";
            return (dataReader.GetSchemaTable().DefaultView.Count > 0);
        }
}

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.