SqlDataReader nesnesindeki sütun adını denetleme


212

Bir SqlDataReadernesnede bir sütunun olup olmadığını nasıl kontrol edebilirim ? Veri erişim katmanımda, birden çok saklı yordam çağrısı için aynı nesneyi oluşturan bir yöntem oluşturduk. Saklı yordamlardan birinde diğer saklı yordamlar tarafından kullanılmayan ek bir sütun vardır. Her senaryo için uygun yöntemi değiştirmek istiyorum.

Uygulamam C # ile yazılmıştır.

Yanıtlar:


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

Kullanımı Exceptiondiğer bazı yanıtlar gibi kontrol devresi için, s olan kötü uygulama olarak kabul ve performans maliyetleri vardır. Ayrıca atılan # istisna profillerine yanlış pozitifler gönderir ve tanrı, hata ayıklayıcısını atılan istisnaları kırmaya ayarlayan herkese yardım eder.

GetSchemaTable () aynı zamanda birçok cevapta başka bir öneridir. Bu, tüm sürümlerde uygulanmadığı için bir alanın varlığını kontrol etmenin tercih edilen bir yolu olmaz (soyuttur ve dotnetcore'un bazı sürümlerinde NotSupportedException özel durumunu oluşturur). GetSchemaTable, kaynağı kontrol ederseniz oldukça ağır bir işlev olduğu için aşırı performans performansına da sahiptir .

Çok fazla kullanırsanız ve sonuçları önbelleğe almayı düşünmek isterseniz, alanlarda döngü yapmak küçük bir performans isabine sahip olabilir.


Ya takma ad kullanılırsa? Ad karşılaştırması başarısız olur.
Murphybro2

İstisna akışını kullanmanın kötü bir uygulama olması tartışmalıdır. Bir zamanlar kötü düşünülmüştü, çünkü diğer operatörler için RELATIVELY pahalı, ancak bağlantılı bir uygulamada ihmal edilebilir. Skeet, 2006'da yığın derinliğine bağlı olarak ms başına 40-118 istisna ölçtü. Stackoverflow.com/a/891230/852208 . Ayrıca test yapmadan, bu kodun aslında tüm sütunların yarısını kontrol eden ortalama durumuyla daha yavaş olması mümkündür (db bağlantılı bir uygulamada hala önemsiz olsa da). Bu cevabı, diğer ikisi fikir olduğu için yalnızca orta paragrafı içerecek şekilde düzenlerdim.
b_levitt

3
@b_levitt tartışmalı değil, bok kodu ve kontrol akışı istisnalarına güvenmemelisiniz
Chad Grant

İşaret ettiğim iki cümle gibi, bu da tamamen hesaplamalı uygulamada performansın ötesinde herhangi bir gerekçe ile desteklenmeyen başka bir fikir. Hata ayıklayıcınızı tüm istisnaları kırmaya ve sadece kodumu devre dışı bırakmaya ayarlamaya cesaret ediyorum ve çerçeve ve diğer kütüphanelerin bunu zaten ne kadar yaptığını göreceksiniz. daha düşük bir model olduğunu kabul edin: stackoverflow.com/questions/99683/… . Böyle bir metodoloji "başarı çukuru" testini geçemez.
b_levitt

Kod açısından, cevabınız geçerli bir cevaptır. Ancak, try / catch (ayrıca takma adları da işleyen) ile cevaba üstün bir cevap olarak düşünmeye çalıştığınız fikriniz burada yersiz.
b_levitt

66

Bu boole işlevini kullanmak çok daha iyidir:

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

Bir çağrı - istisna yok. Dahili olarak istisnalar atabilir, ama sanmıyorum.

NOT: Aşağıdaki yorumlarda, bunu anladık ... doğru kod aslında şu:

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
@ Yasemin: Çok erken konuştum! Kodunuz, sonuç kümeniz yerine şema tablosundaki bir sütunu denetler. "Alan" ı ("alan" sütun adı olduğu varsayılarak) her satırın "SütunAdı" alanının değeri ile karşılaştırmanız gerekir. Bulduğunuzda kırın, yoksa yanlış döndürün.
Steve J

4
@Steve J: Sonuç kümesinin GetSchemaTable'da ne zaman bir sütunu YOKTUR?
Yahu

1
Başka kimsenin kafası karışırsa, BU ÇALIŞMAZ. Şema tablosundan ColumnName satırını alma ve kullanma hakkında aşağıdaki cevaba bakın.
Jason Jackson

3
Evet, bu ÇALIŞMIYOR. Kim bu kadar çok kez onayladı ??? Bu cevap burada olmasaydı, daha sonra hata ayıklama zamanından tasarruf ettim!
c00000fd

1
Her ikisi de çalışıyor mu? Pek sayılmaz. Lütfen cevabınızın ilk kısmını kaldırın. Kendim yapardım, ama son yorumunuz için!
nawfal

33

Bence en iyi bahis GetOrdinal ("columnName") DataReader önünüzde ve sütun mevcut değilse bir IndexOutOfRangeException yakalamak olduğunu düşünüyorum.

Aslında, bir uzantı yöntemi yapalım:

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

Düzenle

Tamam, bu yazı son zamanlarda birkaç aşağı oy toplamaya başlıyor ve kabul edilen cevap olduğu için silemiyorum, bu yüzden güncelleyeceğim ve (umarım) istisna işleme kullanımını haklı göstermeye çalışacağım kontrol akışı.

Chad Grant'in yayınladığı gibi bunu başarmanın diğer yolu , DataReader'daki her alanda döngü yapmak ve aradığınız alan adı için büyük / küçük harfe duyarlı olmayan bir karşılaştırma yapmaktır. Bu gerçekten iyi çalışacak ve doğrusu muhtemelen yukarıdaki yöntemimden daha iyi performans gösterecektir. Kesinlikle yukarıdaki yöntemi hiçbir zaman performace bir sorun olduğu bir döngü içinde kullanmak olmaz.

Ben try / GetOrdinal / catch yönteminin döngü olmadığı yerde çalışacağı bir durum düşünebilirsiniz. Bununla birlikte, şu anda tamamen varsayımsal bir durumdur, bu yüzden çok dayanıksız bir gerekçe. Ne olursa olsun, benimle ayı ve ne düşündüğünü gör.

Bir tablodaki sütunları "takma ad" yapmanıza izin veren bir veritabanı düşünün. "EmployeeName" adlı bir sütun içeren bir tablo tanımlayabilirsiniz ama aynı zamanda "EmpName" bir takma ad vermek ve her iki ad için bir seçim yapmak o sütundaki verileri döndürecek düşünün. Benimle şimdiye kadar mı?

Şimdi bu veritabanı için bir ADO.NET sağlayıcısı olduğunu ve bunun için sütun takma adlarını dikkate alan bir IDataReader uygulaması kodladığını düşünün.

Şimdi dr.GetName(i)(Çad'ın cevabında kullanıldığı gibi) yalnızca tek bir dize döndürebilir, bu nedenle bir sütundaki "takma adlardan" yalnızca birini döndürmek zorundadır . Ancak, GetOrdinal("EmpName")aradığınız ad için her bir sütunun takma adını kontrol etmek üzere bu sağlayıcının alanlarının dahili uygulamasını kullanabilir.

Bu varsayımsal "diğer adlandırılmış sütunlar" durumunda, try / GetOrdinal / catch yöntemi, sonuç kümesindeki bir sütun adının her varyasyonunu kontrol ettiğinizden emin olmanın tek yolu olacaktır.

Çürük? Elbette. Ama düşünmeye değer. Dürüst olmak gerekirse daha çok IDataRecord üzerinde "resmi" bir HasColumn yöntemi.


15
kontrol mantığı için istisnalar mı kullanıyorsunuz? hayır hayır hayır
Chad Grant

28
Bu soruyu ilk gönderdiğimde herkesin göz ardı ettiği küçük bir şey var ... Soruyu 12/8/08 tarihinde sordum ve Matt cevabını 12/17/08 tarihinde gönderdi. Herkes kontrol mantığı için bir istisna yakalama konusunda bir kıyameti yaptı ancak 5/01 / 09'a kadar sağlam bir alternatif çözüm sunmadı. Bu yüzden başlangıçta cevap olarak işaretlendi. Bugün hala bu çözümü kullanıyorum.
Michael Kniskern

19
Bu, yalnızca sütun orada değilse bir performans isabetine sahip olacaktır. Açıklanan diğer yöntemler her seferinde bir performans vuruşuna ve daha büyük bir performans vuruşuna sahip olacaktır. Kontrol akışı için istisna işlemeyi kullanmaktan kaçınmak genellikle kötü bir uygulama olmakla birlikte, bu çözüm ilk olarak sizin durumunuzda çalışıp çalışmadığını göz önünde bulundurmadan göz ardı edilmemelidir.
Nick Harrison

5
+1. Geniş bir tasarım kuralı olarak "Denetim mantığı için istisna kullanma" ile sorun değil. "Her ne pahasına olursa olsun kaçınmak" anlamına gelmez. Yanıt çok iyi belgelenmiş bir çözümdür ve @Nick'in söylediği gibi, isabet performansı (varsa ..) yalnızca sütun mevcut olmadığında gerçekleşir.
Larry

2
İstisnaları kontrol mantığı olarak kullanmak da hata ayıklamayı deneyimlerimde daha hantal hale getirir. "Ortak Dil Çalışma Zamanı Özel Durumları" üzerinde "Atılmış" seçeneğinin işaretini kaldırmanız gerekir ve daha sonra gerçek bir istisna aldığınızda, sorun olan satırda değil, bir yerde bir işleyicide kırılabilir.
cedd

30

Bir satırda, DataReader'ı aldıktan sonra bunu kullanın:

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

Sonra,

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

Düzenle

Şemayı yüklemeyi gerektirmeyen çok daha verimli bir astar:

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

Alan adlarını birden çok kez numaralandırıyorsunuz / bir içerenle taramak için başka bir dizi ayırıyorsunuz, bu yüksek trafik kodunda çok daha az performans gösterir.
Chad Grant

@ChadGrant, bu yüzden Linq bir astar sadece tek bir yineleme gerçekleştirdiğinden çok daha verimlidir.
Larry

18

İşte Jasmin'in fikri için çalışan bir örnek:

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

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

}

1
Sadece bir deneme / yakalama etrafında sarmak
Donald. Kayıt

Bu fikri şununla basitleştirebilirsiniz: reader.GetSchemaTable (). Columns.Contains ("myFiled")
Lev Z

GetSchemaTable () kullanmak, yalnızca bir sütun adı bulmak için aşırıdır (ayırma açısından). Github.com/microsoft/referencesource/blob/…
Chad Grant



8

Soruyu okursanız, Michael DataRecord kullanıcıları değil, DataReader'ı sordu. Nesnelerinizi düzeltin.

Kullanarak r.GetSchemaTable().Columns.Contains(field)DataRecord'da işe yarar, ancak BS sütunlarını döndürür (aşağıdaki ekran görüntüsüne bakın.)

Bir veri sütununun var olup olmadığını VE bir DataReader'da veri içerip içermediğini görmek için aşağıdaki uzantıları kullanın:

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

Kullanımı:

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

Arayan r.GetSchemaTable().Columns, bir DataReader döner BS sütunlarda:

Bir DataReader'da GetSchemeTable çağırma


Matts cevabı altındaki yorumlara bakın
nawfal

Tarafından Ne demek DataRecord çalışır , ancak BS sütunları döndürür ? Yani çalışıyor (ve yanlış sonuçlar veriyor)?
nawfal

2
"Nesnelerinizi düzeltin." - ama IDataReaderuygular IDataRecord. Bunlar aynı nesnenin farklı arayüzleridir - tıpkı onun gibi ICollection<T>ve IEnumerable<T>farklı arayüzlerdir List<T>. IDataReaderbir sonraki kayda ilerlemeye izin verirken IDataRecordmevcut kayıttan okumaya izin verir. Bu cevapta kullanılan yöntemlerin hepsi IDataRecordarayüzden gelir. Parametreyi neden tercih edildiğinin açıklaması için stackoverflow.com/a/1357743/221708 adresine bakın IDataRecord.
Daniel Schilling

Neden r.GetSchemaTable().Columnsbu soruya kesinlikle yanlış bir cevap olduğunu göstermek için oy verin.
Daniel Schilling

GetName (), IDataRecord arabiriminden IDataReader'a miras alınır. Temel arayüzü hedeflemek doğru koddur.
Chad Grant

7

Visual Basic kullanıcıları için yazdım:

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

Bence bu daha güçlü ve kullanımı:

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

4

Kabul edilen cevabın bir liner linq versiyonu:

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

büyük / küçük harfe duyarlı karşılaştırma ... neden?
Chad Grant

4

Burada Yasemin'in bir satırdaki çözümü ... (bir tane daha, basit!):

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

GetSchemaTable () kullanmak, yalnızca bir sütun adı bulmak için aşırıdır (ayırma açısından). Github.com/microsoft/referencesource/blob/…
Chad Grant

@ChadGrant Mümkün. Biri akıllıca bağlam ve bu kullanmak için gerekli sıklığına bağlı olarak seçmek zorunda sanırım ...
spa

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:

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: Sonuçlar - Seri 1 Döngü, 2 İstisna

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


}

1
İstisnalar dışında bir çeşit tuhaf saplantı var. Daha iyi bir yaklaşım, sütun konumunu performans için statik bir aramada önbelleğe almak ve tamsayı aramasını kullanmaktır
Chad Grant

istisnaları kontrol akışı olarak kullanmayla ilgili bir başka sorun, profillerde önerilen kodunuzda kasıtlı oldukları zaman atılan istisna sayısı olarak gösterilmeleridir ... istisnalar değil. Hata ayıklayıcınızı atılan istisnaları aşmaya ayarladığından bahsetmiyorum bile. Temelde hata olmayan hataları bildirmek. Bunu yapmamalısın.
Chad Grant

1
Ayrıca, saniye / saniye ve filtreler / saniye için sayaçlar vardır. Bunlar da kötü mü? Olası bir uyarı söyleyebilirim - sağladığınız ilk gerçek olan. Sayaçlar sadece bilgi. Bir performans sorununa karşılık gelmedikçe hiçbir şey ifade etmiyorlar - ve bu durumda istisnaların DAHA İYİ performansa sahip olduğu noktayı zaten gösterdim. Ayrıca, çerçevenin ve kütüphanelerin zaten çok sayıda istisna attığını belirttim. Şu anda 60 ex / s atan bir görsel stüdyo örneğim var. Yakalanmadığı sürece istisnalar hata değildir.
b_levitt

Harika analiz. Sonuçları yeni cevabımda kullandım.
yazanpro

1

Bu kod, Levitikon'un kodlarıyla ilgili sorunları düzeltir: (aşağıdakilerden uyarlanmıştır: [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;
}

Tablonuzdan sütunun adını değil, tüm bu işe yaramaz sütun adlarını almanın nedeni ... Şema sütununun adını almanızdır (yani Şema tablosu için sütun adları)

NOT: Bu yalnızca ilk sütunun adını döndürüyor gibi görünüyor ...

EDIT: tüm sütunların adını döndüren düzeltilmiş kod, ancak bunu yapmak için bir SqlDataReader kullanamazsınız

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
}

Veya bir satırda return r.GetSchemaTable().Rows.Cast<DataRow>().Select(x => (string)x["ColumnName"]).ToList();:)
nawfal

GetSchemaTable () kullanmak, yalnızca bir sütun adı bulmak için aşırıdır (ayırma açısından). Github.com/microsoft/referencesource/blob/…
Chad Grant

1

Kodunuzu sağlam ve temiz tutmak için, aşağıdaki gibi tek bir uzantı işlevi kullanın:

    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

Ben de bu yoluGetSchemaTable bulana kadar işe başlayamadım .

Temelde ben bunu:

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 büyük / küçük harf duyarsız btw'dir.


İçerir () istisnalar atmaz, bu kod anlamsızdır. Yalnızca boş gösterici istisnalarını yakalarsınız.
Chad Grant

0

Özel durumunuzda (ek 1 sütunu olan 1 hariç tüm prosedürler aynı sütunlara sahiptir), okuyucuyu kontrol etmek daha iyi ve daha hızlı olacaktır. Aralarında ayrım yapmak için FieldCount özelliği.

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

Eski bir yazı olduğunu biliyorum ama aynı durumda başkalarına yardım etmeye cevap vermeye karar verdim. ayrıca (performans nedeniyle) bu çözümü çözüm yineleme çözeltisiyle karıştırabilirsiniz.


Lütfen başvurduğunuz çözümü adlandırın. Hangi iki çözelti karıştırılmalıdır?
Pablo Jomer

0

Veri erişim sınıfımın geriye dönük olarak uyumlu olması gerekiyor, bu yüzden henüz veritabanında bulunmayan bir sürümde bir sütuna erişmeye çalışıyor olabilirim. Her özellik için DataReader sütun koleksiyonunu yinelemek zorunda bir uzantı yönteminin büyük bir hayranı değilim, biz dönen oldukça oldukça büyük veri kümeleri var.

Sütunların özel bir listesini oluşturur ve sonra bir sütun adı ve çıktı parametre türüne dayalı bir değeri çözümlemeye çalışan genel bir yöntem vardır bir yardımcı sınıf var.

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

Sonra sadece kodumu bu şekilde arayabilirim

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

Tüm sorunun anahtarı burada :

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

Referans verilen üç satır (şu anda 72, 73 ve 74 satırları) çıkarılırsa, kolayca kontrol edebilirsiniz. -1 , sütunun bulunup bulunmadığını belirlemek .

Yerel performansı garanti ederken bunun tek yolu, Reflection , aşağıdakiler gibi temel bir uygulama :

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

Yansıma tabanlı uzantı yöntemi:

/// 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

Kamuya açık bir yöntem olmamasına rağmen, iç sınıfta System.Data.ProviderBase.FieldNameLookup,SqlDataReader dayanır.

Erişmek ve yerel performans elde etmek için çalışma zamanında bir yöntem oluşturmak üzere ILGenerator'ı kullanmanız gerekir. Aşağıdaki kod int IndexOf(string fieldName), System.Data.ProviderBase.FieldNameLookupsınıfta doğrudan erişim sağlayacak ve bunun yanı sıra SqlDataReader.GetOrdinal()herhangi bir yan etkisi olmayacak şekilde kitap tutma işlemini gerçekleştirecektir . Oluşturulan kod, yerine çağırması SqlDataReader.GetOrdinal()dışında mevcut FieldNameLookup.IndexOf()olanı yansıtır FieldNameLookup.GetOrdinal(). GetOrdinal()Yöntem çağrıları IndexOf()işlev ve eğer bir istisna atar -1döndürülür biz böylece davranış baypas.

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
Dahili kod, bu garip yansıma / delege gerek duymadan cevabımın yaptığı ile neredeyse aynı şeyi yapıyor. Gerçek dünyada, sorguyu ilk kez çalıştırdığınızda ordinalleri önbelleğe almak istediğiniz her nesne örneği için aramayı önbelleğe alır ve her önbellek için yeni bir önbellek oluşturmaz, bu önbelleği uygulamanın ömrü boyunca kullanır.
Chad Grant

-1

bu iş bana

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.