La façon la plus efficace de vérifier pour DBNull et ensuite assigner à une variable?

cette question est soulevée de temps en temps, mais je n'ai pas vu une réponse satisfaisante.

un motif typique est (ligne est un DataRow ):

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

ma première question Est laquelle est la plus efficace (j'ai inversé la condition):

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

Ce indique que .GetType () devrait être plus rapide, mais peut-être que le compilateur connaît quelques trucs que je ne connais pas?

deuxième question, vaut-il la peine de cacher la valeur de la rangée["valeur"] ou le compilateur optimise-t-il l'indexeur de toute façon?

par exemple:

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

Notes:

  1. ligne ["Valeur"] existe.
  2. Je ne connais pas l'index des colonnes de la colonne (d'où la recherche du nom de la colonne).
  3. je demande spécifiquement à propos de la vérification de DBNull et puis affectation (pas sur l'optimisation prématurée, etc.).

je comparés quelques scénarios (temps en secondes, 10 000 000 d'essais):

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

objet.ReferenceEquals a la même performance que "= = "

le résultat le plus intéressant? Si vous ne collez pas le nom de la colonne par cas (par exemple, "valeur" au lieu de "valeur", cela prend environ dix fois plus de temps (pour une chaîne de caractères):

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

La morale de l'histoire il semble que si vous ne pouvez pas rechercher une colonne par son index, alors assurez-vous que le nom de la colonne que vous donnez à l'indexeur correspond exactement au nom de la colonne de données.

cache la valeur semble aussi être presque deux fois aussi rapide:

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

ainsi la méthode la plus efficace semble pour être:

 object temp;
 string variable;
 if (DBNull.Value != (temp = row["value"]))
 {
      variable = temp.ToString();
 }
148
demandé sur ilitirit 2008-10-21 15:55:52

15 réponses

je dois rater quelque chose. La vérification de DBNull n'est-elle pas exactement ce que fait la méthode DataRow.IsNull ?

j'ai utilisé les deux méthodes d'extension suivantes:

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

Utilisation:

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

si vous ne voulez pas des valeurs de retour Nullable<T> pour GetValue<T> , vous pouvez facilement retourner default(T) ou une autre option à la place.


sur une note sans rapport, voici un VB.NET alternative à la suggestion de 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
66
répondu Dan Tao 2018-01-24 10:50:46

vous devez utiliser la méthode:

Convert.IsDBNull()

étant donné qu'il est intégré au Cadre, Je m'attends à ce que ce soit le plus efficace.

je suggérerais quelque chose du genre:

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

et oui, le compilateur devrait le mettre en cache pour vous.

33
répondu Jon Grant 2008-10-21 12:32:29

le compilateur n'optimisera pas l'indexeur (i.e. si vous utilisez la ligne["Valeur"] deux fois), donc oui il est légèrement plus rapide à faire:

object value = row["value"];

et ensuite utiliser la valeur deux fois; en utilisant .GetType () risque des problèmes s'il est nul...

DBNull.Value est en fait un singleton, afin d'ajouter une 4ème option - vous pouvez peut-être utiliser ReferenceEquals mais, dans la réalité, je pense que vous êtes trop se préoccuper ici... Je ne pense pas que la vitesse différente entre "est", "==", etc est la cause de tout problème de performance que vous voyez. Profile ton code entier et concentre-toi sur quelque chose qui compte... il ne sera pas présent.

20
répondu Marc Gravell 2008-10-21 12:09:38

j'utiliserais le code suivant dans C# ( VB.NET n'est pas aussi simple).

le code assigne la valeur si elle n'est pas nulle/DBNull, sinon il asigns la valeur par défaut qui pourrait être définie à la valeur LHS permettant au compilateur d'ignorer l'assignation.

oSomeObject.IntMemeber = oRow["Value"] as int? ?? iDefault;
oSomeObject.StringMember = oRow["Name"] as string ?? sDefault;
9
répondu Stevo3000 2014-06-08 23:19:02

j'ai l'impression que très peu d'approches ici ne risque pas la perspective de l'OP le plus de soucis (Marc Gravel, Stevo3000, Richard Szalay, Neil, Darren Koppand) et la plupart sont inutilement complexes. Étant pleinement conscient qu'il s'agit d'une micro-optimisation inutile, permettez-moi de dire que vous devriez fondamentalement employer ceux-ci:

1) Ne lisez pas la valeur de DataReader/DataRow deux fois - donc soit le cache avant les vérifications nulles et les casts / conversions ou encore mieux directement passer votre objet record[X] à une méthode d'extension personnalisée avec signature appropriée.

2) pour obéir à ce qui précède, n'utilisez pas la fonction IsDBNull sur votre DataReader/DataRow car cela appelle le record[X] en interne, donc en fait vous le ferez deux fois.

3) la comparaison de Type sera toujours plus lente que la comparaison de valeur en règle générale. Faites record[X] == DBNull.Value mieux.

4) la coulée directe sera plus rapide que l'appel Convert classe pour la conversion, bien que je crains que ce dernier chancelle moins.

5) Enfin, l'accès au document par index plutôt que par nom de colonne sera encore plus rapide.


je pense que les approches de Szalay, Neil et Darren Koppand seront meilleures. J'aime particulièrement L'approche de la méthode d'extension de Darren Koppand qui prend IDataRecord (bien que je voudrais le réduire plus bas à IDataReader ) et l'index/nom de colonne.

attention à l'appeler:

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

et non

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

dans le cas où vous devez différencier entre 0 et DBNull . Par exemple, si vous avez des valeurs nulles dans les champs enum, sinon default(MyEnum) risque de retourner la première valeur enum. Alors appelez record.GetColumnValue<MyEnum?>("Field") .

puisque vous lisez à partir d'un DataRow , je créerais une méthode d'extension pour les deux DataRow et IDataReader par séchage code commun.

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

alors appelez ça comme:

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

je crois que c'est comme ça qu'il aurait dû être dans le cadre (au lieu des méthodes record.GetInt32 , record.GetString etc) en premier lieu - pas d'exceptions de temps d'exécution et nous donne la flexibilité de gérer les valeurs nulles.

D'après mon expérience, j'ai eu moins de chance avec une méthode générique pour lire à partir de la base de données. J'ai toujours eu à traiter sur mesure différents types, donc j'ai dû écrire mon propre GetInt , GetEnum , GetGuid , etc. les méthodes à long terme. Que faire si vous souhaitez couper les espaces blancs lors de la lecture de chaîne de db par défaut, ou traiter DBNull comme chaîne vide? Ou si votre décimale doit être tronquée de tous les zéros traînants. J'ai eu le plus de problèmes avec le type Guid où différents pilotes de connecteur se sont comportés différemment que lorsque les bases de données sous-jacentes peuvent stocker sous forme de chaînes ou binaire. J'ai une surcharge comme ceci:

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

Avec Stevo3000 approche, je trouve l'appel un peu moche et pénible, et il sera plus difficile de faire une fonction générique.

8
répondu nawfal 2014-06-08 23:25:25

il y a le cas gênant où l'objet pourrait être une chaîne. Le code de la méthode d'extension ci-dessous traite tous les cas. Voici comment vous l'utiliseriez:

    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
répondu Saleh Najar 2010-06-16 04:10:15

personnellement, je préfère cette syntaxe , qui utilise la méthode IsDBNull explicite exposée par IDataRecord , et Cache l'index de colonne pour éviter une recherche de chaîne dupliquée.

élargi pour la lisibilité, il va quelque chose comme:

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

réécrit pour correspondre à une ligne simple pour la compacité dans le code DAL - notez que dans cet exemple nous assignons int bar = -1 si row["Bar"] est nul.

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

La ligne la tâche peut être déroutante si vous ne savez pas qu'elle est là, mais elle garde toute l'opération sur une ligne, ce qui améliore la lisibilité quand vous peuplez des propriétés à partir de plusieurs colonnes dans un bloc de code.

6
répondu Dylan Beattie 2009-01-22 19:44:57

ce n'est pas que j'ai fait ça, mais vous pourriez contourner l'appel du double indexeur et garder votre code propre en utilisant une méthode statique / extension.

Ie.

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

puis:

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

a également l'avantage de garder la logique de vérification nulle en un seul endroit. L'inconvénient est, bien sûr, que c'est un appel de méthode supplémentaire.

juste une pensée.

5
répondu Richard Szalay 2008-10-21 13:36:43

j'utilise toujours:

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

L'a trouvé court et complet.

4
répondu Patrick Desjardins 2008-10-21 12:03:03

C'est comme ça que je gère la lecture de DataRows

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

exemple d'utilisation:

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

Accessoires Monstres Obtenu Mon .Net pour ChageTypeTo code.

4
répondu Chris Marisic 2008-11-25 20:08:18

j'essaie d'éviter cette vérification autant que possible.

N'a évidemment pas besoin d'être fait pour les colonnes qui ne peuvent pas contenir null .

si vous stockez dans un type de valeur nul ( int? , etc.), vous pouvez simplement convertir en utilisant as int? .

si vous n'avez pas besoin de faire la différence entre string.Empty et null , vous pouvez simplement appeler .ToString() , puisque DBNull retournera string.Empty .

4
répondu bdukes 2009-01-22 19:50:01

j'ai fait quelque chose de similaire avec les méthodes d'extension. Voici mon 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;
        }
    }
}

Pour l'utiliser, vous feriez quelque chose comme

int number = record.GetColumnValue<int>("Number",0)
4
répondu Darren Kopp 2014-06-08 23:17:40

si dans une ligne["fieldname"] IsDBNull remplacer par 0 autrement obtenir la valeur décimale:

decimal result = rw["fieldname"] as decimal? ?? 0;
4
répondu Stefan 2016-02-25 08:16:31
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;
    }
}

utiliser comme ceci

DBH.Get<String>(itemRow["MyField"])
3
répondu Neil 2011-05-05 14:02:05

j'ai IsDBNull dans un programme qui lit beaucoup de données d'une base de données. Avec IsDBNull il charge les données en environ 20 secondes. Sans IsDBNull, environ 1 seconde.

Donc je pense qu'il est préférable d'utiliser:

public String TryGetString(SqlDataReader sqlReader, int row)
{
    String res = "";
    try
    {
        res = sqlReader.GetString(row);
    }
    catch (Exception)
    { 
    }
    return res;
}
3
répondu Mastahh 2014-06-08 23:20:31