it-swarm.com.de

Der effizienteste Weg, um nach DBNull zu suchen und dann einer Variablen zuzuweisen?

Diese Frage taucht gelegentlich auf, aber ich habe keine zufriedenstellende Antwort gefunden.

Ein typisches Muster ist (Zeile ist eine DataRow ):

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

Meine erste Frage ist, welche effizienter ist (ich habe die Bedingung gekippt):

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

This gibt an, dass .GetType () schneller sein sollte, aber vielleicht kennt der Compiler ein paar Tricks, die ich nicht kann?

Zweite Frage: Lohnt es sich, den Wert von row ["value"] zwischenzuspeichern, oder optimiert der Compiler den Indexer trotzdem?

Beispielsweise:

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

Anmerkungen:

  1. zeile ["Wert"] existiert.
  2. Ich kenne den Spaltenindex der Spalte nicht (daher die Suche nach Spaltennamen).
  3. Ich frage speziell nach der Überprüfung auf DBNull und dann nach der Zuweisung (nicht nach vorzeitiger Optimierung usw.).

Ich habe einige Szenarien verglichen (Zeit in Sekunden, 10.000.000 Versuche):

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 hat die gleiche Leistung wie "=="

Das interessanteste Ergebnis? Wenn Sie den Namen der Spalte von Fall zu Fall nicht übereinstimmen (z. B. "Wert" anstelle von "Wert"), dauert dies ungefähr zehnmal länger (für eine Zeichenfolge):

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

Die Moral der Geschichte scheint zu lauten: Wenn Sie eine Spalte nicht anhand ihres Index nachschlagen können, stellen Sie sicher, dass der Spaltenname, den Sie dem Indexer zuführen, genau mit dem Namen der DataColumn übereinstimmt.

Das Zwischenspeichern des Werts scheint auch fast doppelt so schnell zu sein :

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

Also die effizienteste Methode scheint zu sein:

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

Ich muss etwas vermissen. Wird nicht genau nach DBNull gesucht, was die Methode DataRow.IsNull bewirkt?

Ich habe die folgenden zwei Erweiterungsmethoden verwendet:

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

Verwendung:

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

Wenn Sie nicht möchten, dass Nullable<T> Werte für GetValue<T> Zurückgibt, können Sie stattdessen einfach default(T) oder eine andere Option zurückgeben.


Im Übrigen ist hier eine VB.NET-Alternative zum Vorschlag von Stevo3000:

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
71
Dan Tao

Sie sollten die Methode verwenden:

Convert.IsDBNull()

In Anbetracht dessen, dass es in das Framework integriert ist, würde ich davon ausgehen, dass dies das effizienteste ist.

Ich würde folgendes vorschlagen:

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

Und ja, der Compiler sollte es für Sie zwischenspeichern.

34
Jon Grant

Der Compiler optimiert den Indexer nicht weg (d. H. Wenn Sie Zeile ["Wert"] zweimal verwenden), also ist es etwas schneller:

object value = row["value"];

und dann Wert zweimal verwenden; Bei Verwendung von .GetType () treten Probleme auf, wenn es null ist ...

DBNull.Value ist eigentlich ein Singleton, also um eine vierte Option hinzuzufügen - Sie könnten vielleicht ReferenceEquals verwenden -, aber in Wirklichkeit denke ich, dass Sie sich hier zu viele Sorgen machen ... Ich glaube nicht, dass die Geschwindigkeit zwischen "is", " == "etc wird die Ursache für alle auftretenden Leistungsprobleme sein. Profiliere deinen gesamten Code und konzentriere dich auf etwas, das wichtig ist ... es wird nicht so sein.

20
Marc Gravell

Ich würde den folgenden Code in C # verwenden ( VB.NET ist nicht so einfach).

Der Code weist den Wert zu, wenn er nicht null/DBNull ist, andernfalls weist er den Standardwert zu, der auf den LHS-Wert gesetzt werden kann, sodass der Compiler die Zuweisung ignorieren kann.

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

Ich bin der Meinung, dass nur sehr wenige Ansätze die OP-Perspektive nicht am meisten gefährden (Marc Gravell, Stevo3000, Richard Szalay, Neil, Darren Koppand) und die meisten sind unnötig komplex. Da Sie sich völlig bewusst sind, dass dies nutzlose Mikrooptimierung ist, lassen Sie mich sagen, dass Sie im Grunde Folgendes anwenden sollten:

1) Lesen Sie den Wert von DataReader/DataRow nicht zweimal, sondern speichern Sie ihn entweder vor Nullprüfungen und Umwandlungen/Konvertierungen im Cache oder übergeben Sie Ihr record[X] - Objekt direkt an eine benutzerdefinierte Erweiterungsmethode mit der entsprechenden Signatur.

2) Um die obigen Anweisungen zu befolgen, verwenden Sie die integrierte Funktion IsDBNull in Ihrem DataReader/DataRow nicht, da dadurch intern der Wert record[X] Aufgerufen wird. In der Tat werden Sie dies zweimal tun.

3) Der Typvergleich ist in der Regel immer langsamer als der Wertvergleich. Mach einfach record[X] == DBNull.Value Besser.

4) Direktes Casting ist schneller als das Aufrufen der Klasse Convert für die Konvertierung, obwohl ich befürchte, dass diese weniger ins Stocken geraten wird.

5) Der Zugriff auf Datensätze über den Index und nicht über den Spaltennamen erfolgt wieder schneller.


Ich denke, die Annäherungen von Szalay, Neil und Darren Koppand werden besser sein. Ich mag besonders Darren Koppands Ansatz der Erweiterungsmethode, bei dem IDataRecord (obwohl ich ihn gerne auf IDataReader eingrenzen möchte) und der Index-/Spaltenname verwendet werden.

Achten Sie darauf, es zu nennen:

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

und nicht

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

falls Sie zwischen 0 und DBNull unterscheiden müssen. Wenn Sie beispielsweise Nullwerte in Aufzählungsfeldern haben, läuft default(MyEnum) Gefahr, dass der erste Aufzählungswert zurückgegeben wird. Rufen Sie also besser record.GetColumnValue<MyEnum?>("Field") auf.

Da Sie aus einem DataRow lesen, würde ich eine Erweiterungsmethode für DataRow und IDataReader durch DRYing gemeinsamen Code erstellen.

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

Nennen Sie es jetzt wie folgt:

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

Ich bin der Meinung, dass dies in erster Linie im Framework (anstelle der Methoden record.GetInt32, record.GetString Usw.) der Fall sein sollte - keine Laufzeitausnahmen und die Flexibilität, mit Null umzugehen Werte.

Aus meiner Erfahrung hatte ich weniger Glück mit einer generischen Methode, um aus der Datenbank zu lesen. Ich musste immer mit verschiedenen Typen umgehen, also musste ich auf lange Sicht meine eigenen GetInt, GetEnum, GetGuid usw. Methoden schreiben. Was ist, wenn Sie Leerzeichen entfernen möchten, wenn Sie eine Zeichenfolge standardmäßig aus der Datenbank lesen, oder DBNull als leere Zeichenfolge behandeln möchten? Oder wenn Ihre Dezimalstelle von allen nachgestellten Nullen abgeschnitten werden soll. Ich hatte die meisten Probleme mit dem Typ Guid, bei dem sich verschiedene Connector-Treiber anders verhielten, als wenn zugrunde liegende Datenbanken sie als Zeichenfolge oder Binärdatei speichern könnten. Ich habe eine Überlastung wie folgt:

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

Mit dem Ansatz von Stevo3000 finde ich den Aufruf etwas hässlich und langweilig, und es wird schwieriger, eine generische Funktion daraus zu machen.

8
nawfal

Es gibt den problematischen Fall, in dem das Objekt eine Zeichenfolge sein kann. Der folgende Erweiterungsmethodencode behandelt alle Fälle. So würden Sie es verwenden:

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

Ich persönlich bevorzuge diese Syntax, die die explizite IsDbNull-Methode von IDataRecord verwendet und den Spaltenindex zwischenspeichert, um eine doppelte Suche nach Zeichenfolgen zu vermeiden.

Aus Gründen der Lesbarkeit erweitert, geht es in etwa so:

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

Umgeschrieben, damit der DAL-Code in eine einzelne Zeile passt. Beachten Sie, dass wir in diesem Beispiel int bar = -1 Zuweisen, wenn row["Bar"] Null ist.

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

Die Inline-Zuweisung kann verwirrend sein, wenn Sie nicht wissen, dass sie vorhanden ist, aber die gesamte Operation bleibt in einer Zeile, was meiner Meinung nach die Lesbarkeit verbessert, wenn Sie Eigenschaften aus mehreren Spalten in einem Codeblock einfügen.

6
Dylan Beattie

Ich versuche, diese Prüfung so weit wie möglich zu vermeiden.

Dies muss natürlich nicht für Spalten durchgeführt werden, die null nicht enthalten können.

Wenn Sie in einem Nullable-Werttyp (int? Usw.) speichern, können Sie einfach mit as int? Konvertieren.

Wenn Sie nicht zwischen string.Empty Und null unterscheiden müssen, können Sie .ToString() aufrufen, da DBNull string.Empty Zurückgibt.

5
bdukes

Nicht, dass ich das getan hätte, aber Sie könnten den Doppel-Indexer-Aufruf umgehen und trotzdem Ihren Code sauber halten, indem Sie eine statische/Erweiterungsmethode verwenden.

Dh.

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

Dann:

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

Hat auch den Vorteil, dass die Nullprüflogik an einem Ort bleibt. Der Nachteil ist natürlich, dass es sich um einen zusätzlichen Methodenaufruf handelt.

Nur ein Gedanke.

5
Richard Szalay

Ich benutze immer:

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

Fand es kurz und umfassend.

4

So gehe ich mit dem Lesen aus DataRows um

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

Anwendungsbeispiel:

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

Requisiten an Monsters Got My .Net für ChageTypeTo-Code.

4
Chris Marisic

wenn in einer DataRow die Zeile ["Feldname"] isDbNull ist, ersetzen Sie sie durch 0, andernfalls erhalten Sie den Dezimalwert:

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

Ich habe etwas Ähnliches mit Erweiterungsmethoden gemacht. Hier ist mein Code:

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

Um es zu benutzen, würden Sie so etwas tun

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

Ich habe IsDBNull in einem Programm, das viele Daten aus einer Datenbank liest. Mit IsDBNull werden Daten in ca. 20 Sekunden geladen. Ohne IsDBNull ca. 1 Sekunde.

Also ich denke es ist besser zu benutzen:

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

verwenden Sie wie folgt

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