DBNull kontrol ve sonra bir değişkene atamak için en verimli yolu?


151

Bu soru zaman zaman ortaya çıkıyor, ancak tatmin edici bir cevap görmedim.

Tipik bir desen (satır bir DataRow'dur ):

 if (row["value"] != DBNull.Value)
 {
      someObject.Member = row["value"];
 }

İlk sorum daha verimli (durumu ters çevirdim):

  row["value"] == DBNull.Value; // Or
  row["value"] is DBNull; // Or
  row["value"].GetType() == typeof(DBNull) // Or... any suggestions?

Bu , .GetType () işlevinin daha hızlı olması gerektiğini gösterir, ancak derleyici bilmediğim birkaç numara biliyordur?

İkinci soru, [[value]] satırının değerini önbelleğe almaya değer mi yoksa derleyici zaten indeksleyiciyi optimize ediyor mu?

Örneğin:

  object valueHolder;
  if (DBNull.Value == (valueHolder = row["value"])) {}

Notlar:

  1. satır ["değer"] var.
  2. Ben sütun (böylece sütun adı arama) sütun dizini bilmiyorum.
  3. Özellikle DBNull ve sonra atama (erken optimizasyon, vb. Hakkında değil) denetleme hakkında soruyorum.

Birkaç senaryoyu karşılaştırdım (saniye cinsinden süre, 10.000.000 deneme):

row["value"] == DBNull.Value: 00:00:01.5478995
row["value"] is DBNull: 00:00:01.6306578
row["value"].GetType() == typeof(DBNull): 00:00:02.0138757

Object.ReferenceEquals, "==" ile aynı performansa sahip

En ilginç sonuç? Sütun adını duruma göre eşleşmezseniz (örneğin, "değer" yerine "Değer", yaklaşık on kat daha uzun sürer (bir dize için):

row["Value"] == DBNull.Value: 00:00:12.2792374

Hikayenin ahlakı, bir sütunu dizinine göre arayamazsanız, dizinleyiciye beslediğiniz sütun adının DataColumn'un adıyla tam olarak eşleştiğinden emin olunmasıdır.

Değeri önbelleğe almak da neredeyse iki kat daha hızlıdır:

No Caching: 00:00:03.0996622
With Caching: 00:00:01.5659920

Yani en etkili yöntem şöyledir :

 object temp;
 string variable;
 if (DBNull.Value != (temp = row["value"]))
 {
      variable = temp.ToString();
 }

1
Satırın bir DataRow veya IDataRecord / IDataReader olup olmadığını netleştirebilir misiniz?
Marc Gravell

7
Artık çok daha iyi .NET Framework'ümüz var ve DataRowExtensions Yöntemlerini kullanabiliriz .
Pavel Hodek

Sütun adını duruma göre eşleşmezseniz (örneğin, "değer" yerine "Değer", kabaca on kat daha uzun sürer (bir dize için) Bu tamamen uygulamaya bağlıdır. sütun adı çok daha yavaş) MySQL ADO.NET bağlayıcı ile, ama hiç SqlServer veya SQLite için (hatırlamıyorum).
nawfal

@PavelHodek sadece DataRow için böyle bir utanç. IDataRecordUzantıları isterdim .
nawfal

Yanıtlar:


72

Bir şey eksik olmalıyım. Yöntemin DBNulltam olarak ne yaptığını kontrol DataRow.IsNulletmiyor mu?

Aşağıdaki iki uzantı yöntemini kullanıyorum:

public static T? GetValue<T>(this DataRow row, string columnName) where T : struct
{
    if (row.IsNull(columnName))
        return null;

    return row[columnName] as T?;
}

public static string GetText(this DataRow row, string columnName)
{
    if (row.IsNull(columnName))
        return string.Empty;

    return row[columnName] as string ?? string.Empty;
}

Kullanımı:

int? id = row.GetValue<int>("Id");
string name = row.GetText("Name");
double? price = row.GetValue<double>("Price");

İçin Nullable<T>değer döndürmek istemediyseniz GetValue<T>, default(T)bunun yerine kolayca geri dönebilir veya başka bir seçenek kullanabilirsiniz.


İlişkisiz bir notta, Stevo3000'in önerisine bir VB.NET alternatifi var:

oSomeObject.IntMember = If(TryConvert(Of Integer)(oRow("Value")), iDefault)
oSomeObject.StringMember = If(TryCast(oRow("Name"), String), sDefault)

Function TryConvert(Of T As Structure)(ByVal obj As Object) As T?
    If TypeOf obj Is T Then
        Return New T?(DirectCast(obj, T))
    Else
        Return Nothing
    End If
End Function

3
Dan, OP'nin kaçınmak istediklerini tekrar riske atıyor. Yazarak row.IsNull(columnName)zaten bir kez okuyup tekrar okuyorsunuz. Bunun bir fark yaratacağını söylememek, ancak teorik olarak daha az verimli olabilir ..
nawfal

2
Is not System.Data.DataSetExtensions.DataRowExtensions.Field<T>(this System.Data.DataRow, string)esasen birinci yöntem olarak aynı şeyi yapıyor?
Dennis G

35

Yöntemi kullanmalısınız:

Convert.IsDBNull()

Çerçevede yerleşik olduğu düşünüldüğünde, bunun en verimli olmasını beklerim.

Şunlar boyunca bir şey öneriyorum:

int? myValue = (Convert.IsDBNull(row["column"]) ? null : (int?) Convert.ToInt32(row["column"]));

Ve evet, derleyici sizin için önbelleğe almalıdır.


5
Eh, bütün söz seçenekler çerçeve içine inşa edilir ... Aslında Convert.IsDBNull ... IConvertible ilişkin ekstra bir sürü iş yapar
Marc Gravell

1
Ve önbelleği yeniden - koşullu örnekle kastediyorsanız, hayır - gerçekten olmamalı (ve etmiyor). Dizinleyiciyi iki kez yürütür.
Marc Gravell

Oh, ve bu kod derlenmez - ama bunlardan birine (int?) Ekleyin ve şunu göreceksiniz (IL'de) callvirt örnek nesnesi [System.Data] System.Data.DataRow :: get_Item (string)
Marc Gravell

20

Derleyici dizinleyiciyi optimize etmez (yani [[value]] satırını iki kez kullanırsanız), bu yüzden evet biraz daha hızlıdır:

object value = row["value"];

ve sonra değeri iki kez kullanın; null ise .GetType () sorunları riskler ...

DBNull.Valueaslında bir singleton, bu nedenle 4. seçenek eklemek için - belki de ReferenceEquals kullanabilirsiniz - ama gerçekte, burada çok fazla endişeleniyorsunuz ... "", "=" arasındaki hızın farklı olduğunu düşünmüyorum "vb. gördüğünüz herhangi bir performans sorununun nedeni olacaktır. Kodunuzun tamamını profil haline getirin ve önemli bir şeye odaklanın ... bu olmayacak.


2
Hemen hemen tüm durumlarda == ReferenceEquals ile eşdeğerdir (özellikle DBNull'a) ve çok daha okunabilir. İsterseniz @Marc Gravell'in optimizasyonunu kullanın, ancak onunla beraberim - muhtemelen çok yardımcı olmayacak. BTW, referans eşitliği her zaman tip kontrolünü geçmelidir.
tvanfosson

1
Eski, ama son zamanlarda profillerin tam olarak düzeltmek için söylediği şey bu oldu. Her hücrenin bu kontrolü yapması gereken büyük veri kümelerini değerlendirdiğinizi düşünün. Bunu optimize etmek büyük ödüller kazanabilir. Ancak cevabın önemli kısmı hala iyidir: önce profil , zamanınızı en iyi nerede geçireceğinizi bilmek.
Joel Coehoorn

Elvis operatörünün C # 6 tanıtımı, önerdiğiniz çekte boş referans istisnasını önlemeyi kolaylaştırır. değer? .GetType () == typeof (DBNull)
Eniola

Evet katılıyorum. genellikle gitmek için daha iyi bir yoldur, ancak risklerini belirttiğiniz .GetType () yöntemini kullanmak isteyenler için? etrafında bir yol sağlar.
Eniola

9

Aşağıdaki kodu C # ( VB.NET kadar basit değil) kullanırsınız.

Kod null / DBNull değilse değeri atar, aksi takdirde derleyicinin atamayı yoksaymasına izin veren LHS değerine ayarlanabilen varsayılanı atar.

oSomeObject.IntMemeber = oRow["Value"] as int? ?? iDefault;
oSomeObject.StringMember = oRow["Name"] as string ?? sDefault;

1
VB.NET versiyonu olan basit olarak: oSomeObject.IntMember = If(TryCast(oRow("Value), Integer?), iDefault).
Dan Tao

1
@Dan Tao - Bu kodu derlediğini sanmıyorum. Kodunuzun neden çalışmadığını açıklayan eski bir soruma bakın. stackoverflow.com/questions/746767/…
stevehipwell

Ve bir kez daha, kendi bilgisayarımdan uzaktayken bir SO sorusuna yorum yapmak (üzerinde dev araçlarıyla) bir hata olduğunu kanıtladı! Haklısın; Türler için TryCastC # asoperatörü ile aynı işlevselliği sağlamadığını öğrenmek için şaşırdım Nullable(Of T). Bunu taklit etmeyi düşünebileceğim en yakın yol, cevabımda önerdiğim gibi, kendi fonksiyonunuzu yazmaktır.
Dan Tao

Bunu genel bir yönteme dönüştürmek için zor zamanlarınız olacak ve bunu yapsanız bile, çok fazla döküm yapmak daha az verimli hale getirecektir.
nawfal

8

Burada çok az yaklaşımın OP'nin en endişe olasılığını riske atmadığını hissediyorum (Marc Gravell, Stevo3000, Richard Szalay, Neil, Darren Koppand) ve çoğu gereksiz yere karmaşık. Bunun tamamen işe yaramaz bir mikro optimizasyon olduğunun bilincinde olarak, bunları temel olarak kullanmanız gerektiğini söyleyelim:

1) DataReader / DataRow'daki değeri iki kez okumayın; bu nedenle, boş denetimler ve yayınlar / dönüşümlerden önce önbelleğe alın ya da record[X]nesnenizi uygun imzayla özel bir uzantı yöntemine doğrudan geçirin .

2) Yukarıdakilere uymak için, IsDBNullDataReader / DataRow'unuzdaki dahili işlevi kullanmayın, çünkü bu record[X]dahili olarak çağırır , dolayısıyla aslında iki kez yapacaksınız.

3) Tip karşılaştırması, genel bir kural olarak her zaman değer karşılaştırmasından daha yavaş olacaktır. Sadece record[X] == DBNull.Valuedaha iyisini yap.

4) Doğrudan döküm, Convertdönüştürmek için sınıf çağırmaktan daha hızlı olacaktır , ancak ikincisinin daha az titremesinden korkuyorum.

5) Son olarak, kayda sütun ismi yerine indekse göre erişim daha hızlı olacaktır.


Szalay, Neil ve Darren Koppand'ın yaklaşımlarından daha iyi olacağını düşünüyorum. Özellikle Darren Koppand'ın IDataRecord(daha da daraltmak istiyorum da olsa IDataReader) ve dizin / sütun adını alan genişletme yöntemi yaklaşımını seviyorum .

Onu aramaya dikkat edin:

record.GetColumnValue<int?>("field");

ve yok

record.GetColumnValue<int>("field");

durumda ayırt etmek gerek 0ve DBNull. Örneğin, numaralandırma alanlarında null değerleriniz varsa, aksi takdirde default(MyEnum)ilk numaralandırma değeri döndürülme riski taşır. Daha iyi ara record.GetColumnValue<MyEnum?>("Field").

Bir gelen okuyorsanız beri DataRow, ben her ikisi için uzatma yöntemi yaratacak DataRowve IDataReadertarafından kuruma ortak kod.

public static T Get<T>(this DataRow dr, int index, T defaultValue = default(T))
{
    return dr[index].Get<T>(defaultValue);
}

static T Get<T>(this object obj, T defaultValue) //Private method on object.. just to use internally.
{
    if (obj.IsNull())
        return defaultValue;

    return (T)obj;
}

public static bool IsNull<T>(this T obj) where T : class 
{
    return (object)obj == null || obj == DBNull.Value;
} 

public static T Get<T>(this IDataReader dr, int index, T defaultValue = default(T))
{
    return dr[index].Get<T>(defaultValue);
}

Şimdi şöyle deyin:

record.Get<int>(1); //if DBNull should be treated as 0
record.Get<int?>(1); //if DBNull should be treated as null
record.Get<int>(1, -1); //if DBNull should be treated as a custom value, say -1

Bu o çerçevenin (yerine de olmalıydı nasıl olduğuna inanıyoruz record.GetInt32, record.GetStringhiçbir çalışma zamanı istisnaları ve bize sap boş değerlere esneklik sağlar - ilk etapta yöntemleri vb.)

Deneyimlerime göre veritabanından okumak için bir genel yöntem ile daha az şans vardı. Ben kendi yazmak zorunda Hep, özel tanıtıcı çeşitli gerekiyordu GetInt, GetEnum, GetGuiduzun vadede, vb yöntemler. Varsayılan DBNullolarak db'den dize okurken veya boş dize olarak davranırken beyaz boşlukları kırpmak isterseniz ne olur ? Veya ondalık sayınızın tüm izleyen sıfırlardan kesilmesi gerekiyorsa. GuidFarklı bağlayıcı sürücülerinin de altta yatan veritabanları dize veya ikili olarak saklayabildiğinde farklı davrandığı tipte en fazla sorun yaşadım . Ben böyle bir aşırı yük var:

static T Get<T>(this object obj, T defaultValue, Func<object, T> converter)
{
    if (obj.IsNull())
        return defaultValue;

    return converter  == null ? (T)obj : converter(obj);
}

Stevo3000'in yaklaşımı ile aramayı biraz çirkin ve sıkıcı buluyorum ve genel bir işlev yapmak daha zor olacak.


7

Nesnenin bir dize olabileceği sıkıntılı bir durum var. Aşağıdaki uzantı yöntemi kodu tüm durumları işler. İşte nasıl kullanacağınız:

    static void Main(string[] args)
    {
        object number = DBNull.Value;

        int newNumber = number.SafeDBNull<int>();

        Console.WriteLine(newNumber);
    }



    public static T SafeDBNull<T>(this object value, T defaultValue) 
    {
        if (value == null)
            return default(T);

        if (value is string)
            return (T) Convert.ChangeType(value, typeof(T));

        return (value == DBNull.Value) ? defaultValue : (T)value;
    } 

    public static T SafeDBNull<T>(this object value) 
    { 
        return value.SafeDBNull(default(T)); 
    } 

6

Ben şahsen maruz açık IsDbNull yöntemini kullanan ve IDataRecordyinelenen dize aramasını önlemek için sütun dizini önbelleğe bu sözdizimi lehine .

Okunabilirlik için genişletildi, şuna benzer:

int columnIndex = row.GetOrdinal("Foo");
string foo; // the variable we're assigning based on the column value.
if (row.IsDBNull(columnIndex)) {
  foo = String.Empty; // or whatever
} else { 
  foo = row.GetString(columnIndex);
}

DAL kodunda kompaktlık için tek bir satıra sığacak şekilde yeniden yazılmıştır - bu örnekte null int bar = -1olursa row["Bar"]atayacağımızı unutmayın.

int i; // can be reused for every field.
string foo  = (row.IsDBNull(i  = row.GetOrdinal("Foo")) ? null : row.GetString(i));
int bar = (row.IsDbNull(i = row.GetOrdinal("Bar")) ? -1 : row.GetInt32(i));

Satır içi atama, orada olduğunu bilmiyorsanız kafa karıştırıcı olabilir, ancak tüm işlemi tek bir satırda tutar; bu, bir kod bloğundaki birden çok sütundan özellikleri doldurduğunuzda okunabilirliği geliştirdiğini düşünüyorum.


3
DataRow, IDataRecord'u uygulamıyor.
ilitirit

5

Bunu yaptığımdan değil, ancak çift dizinleyici çağrısının etrafından dolaşabilir ve yine de statik / uzantı yöntemi kullanarak kodunuzu temiz tutabilirsiniz.

Yani.

public static IsDBNull<T>(this object value, T default)
{
    return (value == DBNull.Value)
        ? default
        : (T)value;
}

public static IsDBNull<T>(this object value)
{
    return value.IsDBNull(default(T));
}

Sonra:

IDataRecord record; // Comes from somewhere

entity.StringProperty = record["StringProperty"].IsDBNull<string>(null);
entity.Int32Property = record["Int32Property"].IsDBNull<int>(50);

entity.NoDefaultString = record["NoDefaultString"].IsDBNull<string>();
entity.NoDefaultInt = record["NoDefaultInt"].IsDBNull<int>();

Ayrıca null kontrol mantığını tek bir yerde tutma avantajı vardır. Dezavantajı, elbette, ekstra bir yöntem çağrısı olmasıdır.

Sadece bir düşünce.


2
Ancak nesneye bir uzantı yöntemi eklemek çok geniştir. Şahsen ben DataRow bir uzantısı yöntemi düşünmüş olabilir, ama nesne değil.
Marc Gravell

Doğru, ancak uzantı yöntemlerinin yalnızca uzantı sınıfının ad alanı içe aktarıldığında kullanılabileceğini unutmayın.
Richard Szalay

5

Bu kontrolden mümkün olduğunca kaçınmaya çalışıyorum.

Açıkçası tutamayan sütunlar için yapılması gerekmez null.

Bir Boş Değer değeri türünde (int? vb.) , şunu kullanarak dönüştürebilirsiniz as int?.

Eğer ayırt etmek gerekmiyorsa string.Emptyve null, sadece çağırabilir .ToString()DBNull dönecektir beri string.Empty.


4

Her zaman kullanıyorum:

if (row["value"] != DBNull.Value)
  someObject.Member = row["value"];

Kısa ve kapsamlı buldum.


4

DataRows okuma bu şekilde

///<summary>
/// Handles operations for Enumerations
///</summary>
public static class DataRowUserExtensions
{
    /// <summary>
    /// Gets the specified data row.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="dataRow">The data row.</param>
    /// <param name="key">The key.</param>
    /// <returns></returns>
    public static T Get<T>(this DataRow dataRow, string key)
    {
        return (T) ChangeTypeTo<T>(dataRow[key]);
    }

    private static object ChangeTypeTo<T>(this object value)
    {
        Type underlyingType = typeof (T);
        if (underlyingType == null)
            throw new ArgumentNullException("value");

        if (underlyingType.IsGenericType && underlyingType.GetGenericTypeDefinition().Equals(typeof (Nullable<>)))
        {
            if (value == null)
                return null;
            var converter = new NullableConverter(underlyingType);
            underlyingType = converter.UnderlyingType;
        }

        // Try changing to Guid  
        if (underlyingType == typeof (Guid))
        {
            try
            {
                return new Guid(value.ToString());
            }
            catch

            {
                return null;
            }
        }
        return Convert.ChangeType(value, underlyingType);
    }
}

Kullanım örneği:

if (dbRow.Get<int>("Type") == 1)
{
    newNode = new TreeViewNode
                  {
                      ToolTip = dbRow.Get<string>("Name"),
                      Text = (dbRow.Get<string>("Name").Length > 25 ? dbRow.Get<string>("Name").Substring(0, 25) + "..." : dbRow.Get<string>("Name")),
                      ImageUrl = "file.gif",
                      ID = dbRow.Get<string>("ReportPath"),
                      Value = dbRow.Get<string>("ReportDescription").Replace("'", "\'"),
                      NavigateUrl = ("?ReportType=" + dbRow.Get<string>("ReportPath"))
                  };
}

Canavarlar Sahne ChageTypeTo kodu için benim .Net var .


4

Uzantı yöntemleriyle benzer bir şey yaptım. İşte benim kod:

public static class DataExtensions
{
    /// <summary>
    /// Gets the value.
    /// </summary>
    /// <typeparam name="T">The type of the data stored in the record</typeparam>
    /// <param name="record">The record.</param>
    /// <param name="columnName">Name of the column.</param>
    /// <returns></returns>
    public static T GetColumnValue<T>(this IDataRecord record, string columnName)
    {
        return GetColumnValue<T>(record, columnName, default(T));
    }

    /// <summary>
    /// Gets the value.
    /// </summary>
    /// <typeparam name="T">The type of the data stored in the record</typeparam>
    /// <param name="record">The record.</param>
    /// <param name="columnName">Name of the column.</param>
    /// <param name="defaultValue">The value to return if the column contains a <value>DBNull.Value</value> value.</param>
    /// <returns></returns>
    public static T GetColumnValue<T>(this IDataRecord record, string columnName, T defaultValue)
    {
        object value = record[columnName];
        if (value == null || value == DBNull.Value)
        {
            return defaultValue;
        }
        else
        {
            return (T)value;
        }
    }
}

Kullanmak için şöyle bir şey yaparsınız

int number = record.GetColumnValue<int>("Number",0)

4

bir DataRow satırında ["fieldname"] isDbNull ise 0 ile değiştirin, aksi takdirde ondalık değeri alın:

decimal result = rw["fieldname"] as decimal? ?? 0;

3
public static class DBH
{
    /// <summary>
    /// Return default(T) if supplied with DBNull.Value
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <returns></returns>
    public static T Get<T>(object value)
    {   
        return value == DBNull.Value ? default(T) : (T)value;
    }
}

böyle kullan

DBH.Get<String>(itemRow["MyField"])

3

Bir veritabanından çok fazla veri okuyan bir programda IsDBNull var. IsDBNull ile verileri yaklaşık 20 saniyede yükler. IsDBNull olmadan, yaklaşık 1 saniye.

Bu yüzden kullanmak daha iyi olduğunu düşünüyorum:

public String TryGetString(SqlDataReader sqlReader, int row)
{
    String res = "";
    try
    {
        res = sqlReader.GetString(row);
    }
    catch (Exception)
    { 
    }
    return res;
}
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.