Periksa nama kolom dalam objek SqlDataReader

212

Bagaimana saya memeriksa untuk melihat apakah ada kolom di SqlDataReaderobjek? Di lapisan akses data saya, saya telah membuat metode yang membangun objek yang sama untuk beberapa panggilan prosedur tersimpan. Salah satu prosedur tersimpan memiliki kolom tambahan yang tidak digunakan oleh prosedur tersimpan lainnya. Saya ingin memodifikasi metode untuk mengakomodasi setiap skenario.

Aplikasi saya ditulis dalam C #.

Michael Kniskern
sumber

Jawaban:

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;
    }
}

Menggunakan Exceptions untuk logika kontrol seperti dalam beberapa jawaban lain dianggap praktik buruk dan memiliki biaya kinerja. Itu juga mengirimkan positif palsu ke profiler dari # pengecualian yang dilemparkan dan tuhan membantu siapa pun mengatur debugger mereka untuk memecahkan pengecualian yang dilemparkan.

GetSchemaTable () juga merupakan saran lain dalam banyak jawaban. Ini tidak akan menjadi cara yang dipilih untuk memeriksa keberadaan bidang karena tidak diterapkan di semua versi (abstrak dan melempar NotSupportedException di beberapa versi dotnetcore). GetSchemaTable juga kinerja yang berlebihan karena ini adalah fungsi tugas yang cukup berat jika Anda memeriksa sumbernya .

Melewati bidang dapat memiliki hit kinerja kecil jika Anda sering menggunakannya dan Anda mungkin ingin mempertimbangkan untuk menyimpan hasilnya.

Chad Grant
sumber
Bagaimana jika alias digunakan? Perbandingan nama akan gagal.
Murphybro2
Dapat diperdebatkan bahwa menggunakan aliran pengecualian adalah praktik yang buruk. Dulu dianggap buruk karena harganya relatif mahal untuk operator lain, tetapi dapat diabaikan dalam aplikasi yang terhubung. Skeet mengukur 40-118 pengecualian per ms tergantung pada kedalaman tumpukan sepanjang tahun 2006. stackoverflow.com/a/891230/852208 . Lebih lanjut tanpa pengujian, mungkin kode ini sebenarnya lebih lambat dengan case rata-rata memeriksa setengah dari semua kolom (meskipun masih sepele dalam aplikasi yang terhubung db). Saya akan mengedit jawaban ini untuk hanya menyertakan paragraf tengah karena dua lainnya adalah pendapat.
b_levitt
3
@ b_levitt itu tidak bisa diperdebatkan, itu kode omong kosong dan Anda tidak harus bergantung pada pengecualian untuk aliran kontrol
Chad Grant
Seperti dua kalimat yang saya tunjukkan, itu adalah pendapat lain yang tidak didukung oleh alasan di luar kinerja dalam aplikasi komputasi murni. Saya berani Anda untuk mengatur debugger Anda untuk istirahat pada semua pengecualian dan menonaktifkan hanya kode saya dan Anda akan melihat betapa bahkan kerangka kerja dan perpustakaan lain sudah melakukan ini. Masalah dengan saran Anda adalah mendorong pengembang untuk mengembalikan kode yang paling setuju adalah pola inferior: stackoverflow.com/questions/99683/… . Metodologi semacam itu gagal dalam ujian "lubang kesuksesan".
b_levitt
Dari perspektif kode, jawaban Anda valid. Namun pendapat Anda yang mencoba menjadikannya sebagai jawaban unggul atas jawaban dengan try / catch (yang juga menangani alias) tidak cocok di sini.
b_levitt
66

Jauh lebih baik menggunakan fungsi boolean ini:

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

Satu panggilan - tanpa pengecualian. Mungkin ada pengecualian internal, tapi saya rasa tidak.

CATATAN: Dalam komentar di bawah ini, kami menemukan ini ... kode yang benar adalah ini:

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; 
}
Melati
sumber
5
@Jasmine: Saya berbicara terlalu cepat! Kode Anda memeriksa kolom dalam tabel skema, bukan hasil yang Anda set. Anda perlu membandingkan "bidang" (dengan asumsi "bidang" adalah nama kolom) dengan nilai bidang "ColumnName" setiap baris. Hancurkan ketika Anda menemukannya, kembalikan salah jika Anda tidak.
Steve J
4
@Steve J: Kapan resultset TIDAK memiliki kolom di GetSchemaTable?
Bless Yahu
1
Untuk orang lain yang bingung, INI TIDAK BEKERJA. Lihat jawaban di bawah ini tentang mengambil baris ColumnName dari tabel skema dan menggunakannya.
Jason Jackson
3
Ya, ini TIDAK BEKERJA. Siapa yang membatalkannya berkali-kali ??? Itu akan menyelamatkan saya banyak waktu debug nanti jika jawaban ini tidak ada di sini!
c00000fd
1
@ Melati mereka berdua bekerja? Tidak juga. Harap hapus bagian pertama dari jawaban Anda. Saya akan melakukannya sendiri, tetapi untuk komentar terakhir Anda!
nawfal
33

Saya pikir taruhan terbaik Anda adalah memanggil GetOrdinal ("columnName") di DataReader Anda di muka, dan tangkap IndexOutOfRangeException jika kolom tidak ada.

Bahkan, mari kita buat metode ekstensi:

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

Edit

Ok, postingan ini mulai mendapatkan beberapa down-vote belakangan ini, dan saya tidak bisa menghapusnya karena ini adalah jawaban yang diterima, jadi saya akan memperbaruinya dan (saya harap) mencoba untuk membenarkan penggunaan penanganan pengecualian sebagai aliran kontrol.

Cara lain untuk mencapai ini, seperti yang diposting oleh Chad Grant , adalah untuk mengulang setiap bidang dalam DataReader dan melakukan perbandingan case-insensitive untuk nama bidang yang Anda cari. Ini akan bekerja dengan sangat baik, dan sejujurnya mungkin akan tampil lebih baik daripada metode saya di atas. Tentu saja saya tidak akan pernah menggunakan metode di atas dalam satu loop di mana performace menjadi masalah.

Saya dapat memikirkan satu situasi di mana metode coba / GetOrdinal / catch akan bekerja di mana loop tidak. Namun, ini adalah situasi yang sepenuhnya hipotetis saat ini sehingga ini merupakan pembenaran yang sangat lemah. Apapun, bersabarlah dan lihat apa yang Anda pikirkan.

Bayangkan sebuah database yang memungkinkan Anda untuk "alias" kolom dalam sebuah tabel. Bayangkan saya bisa mendefinisikan tabel dengan kolom yang disebut "EmployeeName" tetapi juga memberikannya alias "EmpName", dan melakukan pilih untuk salah satu nama akan mengembalikan data di kolom itu. Dengan saya sejauh ini?

Sekarang bayangkan ada penyedia ADO.NET untuk database itu, dan mereka telah membuat kode implementasi IDataReader untuknya yang memperhitungkan alias kolom.

Sekarang, dr.GetName(i)(seperti yang digunakan dalam jawaban Chad) hanya dapat mengembalikan satu string, sehingga harus mengembalikan hanya satu dari "alias" pada kolom. Namun, GetOrdinal("EmpName")dapat menggunakan implementasi internal bidang penyedia ini untuk memeriksa alias setiap kolom untuk nama yang Anda cari.

Dalam situasi "alias kolom" hipotetis ini, metode coba / GetOrdinal / catch akan menjadi satu-satunya cara untuk memastikan bahwa Anda memeriksa setiap variasi nama kolom di resultset.

Tipis? Tentu. Tapi patut dipikirkan. Jujur saya lebih suka metode HasColumn "resmi" pada IDataRecord.

Matt Hamilton
sumber
15
menggunakan pengecualian untuk logika kontrol? tidak, tidak, tidak
Chad Grant
28
Ada satu hal kecil yang setiap orang abaikan ketika saya memposting pertanyaan ini ... Saya mengajukan pertanyaan pada 12/8/08 dan Matt memposting jawabannya pada 12/17/08. Semua orang berusaha menangkap pengecualian untuk logika kontrol tetapi tidak memberikan solusi alternatif yang solid hingga 5/1/09. Itu sebabnya awalnya ditandai sebagai jawabannya. Saya masih menggunakan solusi ini hari ini.
Michael Kniskern
19
Ini akan memiliki hit kinerja hanya jika kolom tidak ada di sana. Metode lain yang dijelaskan akan memiliki hit kinerja, dan hit kinerja yang lebih besar, setiap saat. Meskipun umumnya praktik yang buruk untuk menghindari menggunakan penanganan pengecualian untuk aliran kontrol, solusi ini tidak boleh dikesampingkan tanpa terlebih dahulu mempertimbangkan apakah itu berfungsi dalam kasus Anda.
Nick Harrison
5
+1. Saya setuju dengan "Jangan gunakan pengecualian untuk logika kontrol" sebagai aturan desain yang luas. Itu tidak berarti "menghindarinya sama sekali". Jawabannya adalah solusi yang didokumentasikan dengan sangat baik, dan seperti yang dikatakan @Nick, kinerja hit (jika ada ..) hanya terjadi ketika kolom tidak ada.
Larry
2
Menggunakan Pengecualian sebagai logika kontrol juga membuat proses debug lebih rumit dalam pengalaman saya. Anda harus menghapus centang "Dilempar" pada "Pengecualian Runtime Bahasa Umum" dan kemudian ketika Anda mendapatkan pengecualian nyata, itu mungkin pecah di handler di suatu tempat dan bukan pada garis yang memiliki masalah.
cedd
30

Dalam satu baris, gunakan ini setelah pengambilan DataReader Anda:

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

Kemudian,

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

Edit

Satu liner yang jauh lebih efisien yang tidak perlu memuat skema:

var exists = Enumerable.Range(0, dr.FieldCount).Any(i => string.Equals(dr.GetName(i), fieldName, StringComparison.OrdinalIgnoreCase));
Larry
sumber
Anda menghitung nama bidang beberapa kali / mengalokasikan array lain untuk dipindai dengan berisi, ini akan jauh lebih sedikit performan dalam kode lalu lintas tinggi.
Chad Grant
@ ChadGrant tentu saja, itu sebabnya Linq one liner jauh lebih efisien karena hanya melakukan satu iterasi.
Larry
18

Berikut adalah contoh kerja untuk ide Jasmin:

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

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

}
Chris Ji
sumber
1
Hanya jika Anda membungkus coba / tangkap di sekitarnya
Donald.Rekam
Anda dapat menyederhanakan ide ini dengan: reader.GetSchemaTable (). Columns.Contains ("myFiled")
Lev Z
menggunakan GetSchemaTable () berlebihan (berdasarkan alokasi) untuk hanya menemukan nama kolom. Lihatlah sumbernya github.com/microsoft/referencesource/blob/…
Chad Grant
12

ini bekerja untuk saya:

bool hasColumnName = reader.GetSchemaTable().AsEnumerable().Any(c => c["ColumnName"] == "YOUR_COLUMN_NAME");
Victor Labastida
sumber
menggunakan GetSchemaTable () berlebihan (berdasarkan alokasi) untuk hanya menemukan nama kolom. Dan itu tidak diterapkan di semua versi inti dotnet. Lihatlah sumbernya github.com/microsoft/referencesource/blob/…
Chad Grant
10

Berikut ini sederhana dan berfungsi untuk saya:

 bool hasMyColumn = (reader.GetSchemaTable().Select("ColumnName = 'MyColumnName'").Count() == 1);
Paulo Lisboa
sumber
menggunakan GetSchemaTable () berlebihan (berdasarkan alokasi) untuk hanya menemukan nama kolom. Lihatlah sumbernya github.com/microsoft/referencesource/blob/…
Chad Grant
8

Jika Anda membaca pertanyaan, Michael bertanya tentang DataReader, bukan orang-orang DataRecord. Dapatkan benda Anda dengan benar.

Menggunakan r.GetSchemaTable().Columns.Contains(field)pada DataRecord berfungsi, tetapi mengembalikan kolom BS (lihat tangkapan layar di bawah.)

Untuk melihat apakah ada kolom data DAN berisi data dalam DataReader, gunakan ekstensi berikut:

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;
        }
    }
}

Pemakaian:

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

Memanggil r.GetSchemaTable().ColumnsDataReader mengembalikan kolom BS:

Memanggil GetSchemeTable dalam DataReader

Levitikon
sumber
lihat komentar di bawah jawaban Matts
nawfal
Apa yang Anda maksud dengan DataRecord tidak berfungsi , tetapi mengembalikan kolom BS ? Maksud Anda itu berjalan (dan memberikan hasil yang salah)?
nawfal
2
"Perbaiki objekmu." - tetapi IDataReadermengimplementasikan IDataRecord. Mereka adalah antarmuka yang berbeda dari objek yang sama - sama seperti ICollection<T>dan IEnumerable<T>merupakan antarmuka yang berbeda dari List<T>. IDataReadermemungkinkan maju ke catatan berikutnya, sementara IDataRecordmemungkinkan membaca dari catatan saat ini. Metode yang digunakan dalam jawaban ini semua berasal dari IDataRecordantarmuka. Lihat stackoverflow.com/a/1357743/221708 untuk penjelasan mengapa mendeklarasikan parameter sebagai IDataRecordlebih disukai.
Daniel Schilling
Suara positif untuk menunjukkan mengapa r.GetSchemaTable().Columnsjawaban yang benar-benar salah untuk pertanyaan ini.
Daniel Schilling
GetName () diwarisi dari antarmuka IDataRecord ke IDataReader. Menargetkan antarmuka dasar adalah kode yang benar.
Chad Grant
7

Saya menulis untuk pengguna 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

Saya pikir ini lebih kuat dan penggunaannya adalah:

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

Berikut ini adalah versi linq liner dari jawaban yang diterima:

Enumerable.Range(0, reader.FieldCount).Any(i => reader.GetName(i) == "COLUMN_NAME_GOES_HERE")
Sejuk
sumber
perbandingan sensitif huruf ... mengapa?
Chad Grant
4

Di sini solusi dari Jasmine dalam satu baris ... (satu lagi, sederhana!):

reader.GetSchemaTable().Select("ColumnName='MyCol'").Length > 0;
spaark
sumber
menggunakan GetSchemaTable () berlebihan (berdasarkan alokasi) untuk hanya menemukan nama kolom. Lihatlah sumbernya github.com/microsoft/referencesource/blob/…
Chad Grant
@ ChadGrant Kemungkinan. Saya kira kita harus memilih dengan bijak tergantung pada konteks dan frekuensi yang diperlukan untuk menggunakan ini ...
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);
    }
Deepak
sumber
3

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: Hasil - Seri 1 adalah Loop, 2 adalah Pengecualian

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; }


}
b_levitt
sumber
1
Anda jelas memiliki semacam obsesi aneh dengan pengecualian. Pendekatan yang lebih baik hanya akan melakukan cache lokasi kolom dalam pencarian statis untuk kinerja dan menggunakan pencarian integer
Chad Grant
masalah lain dengan menggunakan pengecualian sebagai aliran kontrol adalah mereka muncul di profiler sebagai # pengecualian yang dilemparkan ketika dalam kode yang Anda sarankan mereka disengaja ... bukan pengecualian. Belum lagi mengatur debugger Anda untuk istirahat pada pengecualian yang dilemparkan. Pada dasarnya melaporkan kesalahan yang bukan kesalahan. Anda seharusnya tidak melakukan ini.
Chad Grant
1
Ada juga penghitung untuk akhirnya / detik, dan filter / detik. Apakah itu buruk juga? Saya akan menyebutnya sebagai kemungkinan peringatan - yang nyata pertama yang Anda berikan. Counter hanyalah informasi. Mereka tidak berarti apa-apa kecuali mereka sesuai dengan masalah kinerja - dan dalam hal ini saya sudah menunjukkan titik di mana pengecualian memiliki kinerja LEBIH BAIK. Saya juga telah menunjukkan bahwa kerangka kerja dan perpustakaan sudah membuang banyak pengecualian. Saya punya contoh studio visual yang melempar 60 ex / s sekarang. Pengecualian bukanlah kesalahan kecuali mereka tidak tertangkap.
b_levitt
Analisis hebat. Saya menggunakan hasilnya dalam jawaban baru saya.
yazanpro
1

Kode ini memperbaiki masalah yang dimiliki Levitikon dengan kode mereka: (diadaptasi dari: [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;
}

Alasan untuk mendapatkan semua nama kolom yang tidak berguna dan bukan nama kolom dari tabel Anda ... Apakah karena Anda mendapatkan nama kolom skema (yaitu nama kolom untuk tabel Skema)

CATATAN: ini sepertinya hanya mengembalikan nama kolom pertama ...

EDIT: kode yang diperbaiki yang mengembalikan nama semua kolom, tetapi Anda tidak dapat menggunakan SqlDataReader untuk melakukannya

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
}
NeoH4x0r
sumber
Atau dalam satu baris return r.GetSchemaTable().Rows.Cast<DataRow>().Select(x => (string)x["ColumnName"]).ToList();:)
nawfal
menggunakan GetSchemaTable () berlebihan (berdasarkan alokasi) untuk hanya menemukan nama kolom. Lihatlah sumbernya github.com/microsoft/referencesource/blob/…
Chad Grant
1

Untuk menjaga kode Anda kuat dan bersih, gunakan fungsi ekstensi tunggal, seperti ini:

    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
Michael B
sumber
0

Saya juga tidak mulai GetSchemaTablebekerja, sampai saya menemukan cara ini .

Pada dasarnya saya melakukan ini:

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
David Andersson
sumber
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 btw tidak sensitif huruf.

RBAFF79
sumber
Berisi () tidak membuang pengecualian, kode ini tidak ada gunanya. Anda hanya akan menangkap pengecualian null pointer.
Chad Grant
0

Dalam situasi khusus Anda (semua prosedur memiliki kolom yang sama kecuali 1 yang memiliki 1 kolom tambahan), akan lebih baik dan lebih cepat untuk memeriksa pembaca. Properti FieldCount untuk membedakannya.

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

Saya tahu ini adalah posting lama tetapi saya memutuskan untuk menjawab untuk membantu orang lain dalam situasi yang sama. Anda juga dapat (karena alasan kinerja) mencampur solusi ini dengan solusi solusi iterasi.

pkrzemo
sumber
Silakan sebutkan solusi yang Anda maksud. Dua solusi mana yang harus dicampur?
Pablo Jomer
0

Kelas akses data saya harus kompatibel ke belakang, jadi saya mungkin mencoba mengakses kolom dalam rilis yang belum ada di database. Kami memiliki beberapa set data yang agak besar yang dikembalikan jadi saya bukan penggemar metode ekstensi yang harus mengulangi pengumpulan kolom DataReader untuk setiap properti.

Saya memiliki kelas utilitas yang membuat daftar kolom pribadi dan kemudian memiliki metode generik yang mencoba untuk menyelesaikan nilai berdasarkan nama kolom dan tipe parameter output.

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());
            }
        }
    }
}

Maka saya bisa memanggil kode saya seperti itu

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;
    }
}
Tresto
sumber
0

Kunci dari seluruh masalah ada di sini :

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

Jika tiga baris yang direferensikan (saat ini baris 72, 73, dan 74) diambil, maka Anda dapat dengan mudah memeriksa untuk -1menentukan apakah kolom tidak ada.

Satu-satunya cara mengatasi ini sambil memastikan kinerja asli adalah dengan menggunakan Reflectionimplementasi berbasis, seperti berikut:

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

Metode ekstensi berbasis Refleksi:

/// 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;
        }
    }
}
yazanpro
sumber
-1

Bagaimana tentang

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

Mungkin tidak seefisien dalam satu lingkaran

Skadoosh
sumber
Lihat jawaban Levitikon untuk melihat jenis barang yang dr.GetSchemaTable().Columnsmengandung - itu bukan yang Anda cari.
Daniel Schilling
-1

Meskipun tidak ada metode terbuka untuk umum, metode memang ada di kelas internal System.Data.ProviderBase.FieldNameLookupyang SqlDataReaderdiandalkan.

Untuk mengaksesnya dan mendapatkan kinerja asli, Anda harus menggunakan ILGenerator untuk membuat metode saat runtime. Kode berikut akan memberi Anda akses langsung ke int IndexOf(string fieldName)dalam System.Data.ProviderBase.FieldNameLookupkelas serta melakukan pembukuan yang berfungsi SqlDataReader.GetOrdinal()sehingga tidak ada efek samping. Kode yang dihasilkan mencerminkan yang sudah ada SqlDataReader.GetOrdinal()kecuali yang dipanggil FieldNameLookup.IndexOf()alih-alih FieldNameLookup.GetOrdinal(). The GetOrdinal()metode panggilan ke IndexOf()fungsi dan melempar pengecualian jika -1dikembalikan, jadi kami memotong perilaku itu.

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));
   }

}
Derek Ziemba
sumber
1
Kode internal melakukan hal yang hampir sama persis dengan jawaban saya, tanpa perlu refleksi / delegasi aneh ini. Itu caching lookup per instance objek yang tidak akan bermanfaat karena di dunia nyata Anda ingin men-cache ordinals saat pertama kali query dijalankan dan menggunakan cache itu selama masa pakai aplikasi, bukan membangun cache baru pada setiap query.
Chad Grant
-1

ini bekerja untuk saya

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