Le moyen le plus efficace de vérifier DBNull puis d’affecter une variable?

Cette question revient occasionnellement, mais je n’ai pas vu de réponse satisfaisante.

Un modèle typique est (la ligne est un DataRow ):

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

Ma première question est celle qui 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? 

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

Deuxième question, vaut-il la peine de mettre en cache la valeur de row [“value”] ou le compilateur optimise-t-il l’indexeur de toute façon?

Par exemple:

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

Remarques:

  1. row [“value”] existe.
  2. Je ne connais pas l’index de colonne de la colonne (d’où la recherche de nom de colonne).
  3. Je m’interroge spécifiquement sur la vérification de DBNull, puis sur l’affectation (pas sur l’optimisation prématurée, etc.).

J’ai évalué quelques scénarios (temps en secondes, 10 000 000 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 

Object.ReferenceEquals a les mêmes performances que “==”

Le résultat le plus intéressant? Si vous ne correspondez pas au 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):

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

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

La mise en cache de la valeur semble également être presque deux fois plus rapide:

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

La méthode la plus efficace semble donc être:

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

    J’ai dû louper 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(this DataRow row, ssortingng columnName) where T : struct { if (row.IsNull(columnName)) return null; return row[columnName] as T?; } public static ssortingng GetText(this DataRow row, ssortingng columnName) { if (row.IsNull(columnName)) return ssortingng.Empty; return row[columnName] as ssortingng ?? ssortingng.Empty; } 

    Usage:

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

    Si vous ne vouliez pas de valeurs de retour Nullable pour GetValue , vous pourriez facilement retourner la default(T) ou une autre option à la place.


    Sur une note sans rapport, voici une alternative VB.NET à la suggestion de Stevo3000:

     oSomeObject.IntMember = If(TryConvert(Of Integer)(oRow("Value")), iDefault) oSomeObject.SsortingngMember = If(TryCast(oRow("Name"), Ssortingng), 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 

    Vous devez utiliser la méthode:

     Convert.IsDBNull() 

    Étant donné qu’il est intégré au cadre, je m’attendrais à ce que ce soit le plus efficace.

    Je suggérerais quelque chose comme:

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

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

    Le compilateur n’optimisera pas l’indexeur (c’est-à-dire que si vous utilisez row [“value”] deux fois), alors oui, il est légèrement plus rapide à faire:

     object value = row["value"]; 

    puis utilisez la valeur deux fois; l’utilisation de .GetType () risque de poser des problèmes si elle est nulle …

    DBNull.Value est en fait un singleton, donc pour append une 4ème option – vous pourriez peut-être utiliser ReferenceEquals – mais en réalité, je pense que vous vous inquiétez trop ici … Je ne pense pas que la vitesse différente entre “is”, “==” etc sera la cause de tout problème de performance que vous voyez. Profil complet de votre code et concentrez-vous sur quelque chose qui compte … ce ne sera pas ça.

    J’utiliserais le code suivant en C # ( VB.NET n’est pas aussi simple).

    Le code assigne la valeur s’il n’est pas null / DBNull, sinon il atsortingbue la valeur par défaut qui pourrait être définie à la valeur LHS, ce qui permet au compilateur d’ignorer l’affectation.

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

    Je pense que très peu d’approches ici ne risquent pas les outlook les plus inquiétantes (Marc Gravell, Stevo3000, Richard Szalay, Neil, Darren Koppand) et la plupart sont inutilement complexes. Étant pleinement conscient de l’utilité de la micro-optimisation, permettez-moi de vous dire que vous devriez essentiellement les employer:

    1) Ne lisez pas la valeur de DataReader / DataRow deux fois – alors mettez-la en cache avant les vérifications null et les conversions / conversions ou mieux, passez directement votre object record[X] à une méthode d’extension personnalisée avec la signature appropriée.

    2) Pour obéir à ce qui précède, n’utilisez pas la fonction intégrée IsDBNull sur votre DataReader / DataRow, car elle appelle l’ record[X] interne, vous le ferez donc deux fois.

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

    4) Le lancer direct sera plus rapide que d’appeler la classe Convert pour la conversion, même si je crains que ce dernier ne faiblisse moins.

    5) Enfin, l’access à l’enregistrement par index plutôt que par nom de colonne sera encore plus rapide.


    Je pense que les approches de Szalay, Neil et Darren Koppand iront mieux. J’aime particulièrement l’approche de la méthode d’extension de Darren Koppand qui IDataRecord (bien que je voudrais la réduire à IDataReader ) et le nom de l’index / colonne.

    Prenez soin de l’appeler:

     record.GetColumnValue("field"); 

    et pas

     record.GetColumnValue("field"); 

    au cas où vous devriez différencier entre 0 et DBNull . Par exemple, si vous avez des valeurs NULL dans les champs enum, sinon, la default(MyEnum) par default(MyEnum) risque d’être renvoyée. Alors, mieux vaut appeler record.GetColumnValue("Field") .

    Puisque vous lisez à partir d’un DataRow , je DataRow une méthode d’extension à la fois pour DataRow et IDataReader en utilisant le code commun DRYing .

     public static T Get(this DataRow dr, int index, T defaultValue = default(T)) { return dr[index].Get(defaultValue); } static T Get(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(this T obj) where T : class { return (object)obj == null || obj == DBNull.Value; } public static T Get(this IDataReader dr, int index, T defaultValue = default(T)) { return dr[index].Get(defaultValue); } 

    Alors maintenant, appelez ça comme:

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

    Je pense que c’est ainsi que cela aurait dû être dans le framework (au lieu des record.GetInt32 , record.GetSsortingng etc) en premier lieu – pas d’exceptions à l’exécution et nous donne la flexibilité de gérer les valeurs NULL.

    D’après mon expérience, j’ai eu moins de chance avec une méthode générique à lire dans la firebase database. J’ai toujours dû gérer des types différents, donc j’ai dû écrire mes propres GetInt , GetEnum , GetGuid , etc. à long terme. Que faire si vous vouliez couper des espaces blancs lors de la lecture de la chaîne de la firebase database par défaut, ou traiter DBNull comme une chaîne vide? Ou si votre décimal doit être tronqué de tous les zéros de fin. J’ai eu le plus de problèmes avec le type Guid où les différents pilotes de connecteur se comportaient différemment, aussi lorsque les bases de données sous-jacentes pouvaient les stocker sous forme de chaîne ou de binary. J’ai une surcharge comme celle-ci:

     static T Get(this object obj, T defaultValue, Func converter) { if (obj.IsNull()) return defaultValue; return converter == null ? (T)obj : converter(obj); } 

    Avec l’approche de Stevo3000, je trouve l’appel un peu moche et fastidieux, et il sera plus difficile de créer une fonction générique.

    Il y a le cas gênant où l’object pourrait être une chaîne. Le code de méthode d’extension ci-dessous gère tous les cas. Voici comment l’utiliser:

      static void Main(ssortingng[] args) { object number = DBNull.Value; int newNumber = number.SafeDBNull(); Console.WriteLine(newNumber); } public static T SafeDBNull(this object value, T defaultValue) { if (value == null) return default(T); if (value is ssortingng) return (T) Convert.ChangeType(value, typeof(T)); return (value == DBNull.Value) ? defaultValue : (T)value; } public static T SafeDBNull(this object value) { return value.SafeDBNull(default(T)); } 

    Je privilégie personnellement cette syntaxe, qui utilise la méthode explicite IsDbNull exposée par IDataRecord , et met en cache l’index de colonne pour éviter une recherche de chaîne en double.

    Développé pour la lisibilité, il va quelque chose comme:

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

    Réécrit pour tenir sur une seule ligne pour la compacité du 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. ssortingng foo = (row.IsDBNull(i = row.GetOrdinal("Foo")) ? null : row.GetSsortingng(i)); int bar = (row.IsDbNull(i = row.GetOrdinal("Bar")) ? -1 : row.GetInt32(i)); 

    L’atsortingbution en ligne peut être source de confusion si vous ne le savez pas, mais elle conserve toute l’opération sur une seule ligne, ce qui améliore la lisibilité lorsque vous remplissez des propriétés à partir de plusieurs colonnes dans un bloc de code.

    Non pas que je l’ai fait, mais vous pouvez contourner l’appel à double indexeur et toujours garder votre code propre en utilisant une méthode statique / d’extension.

    C’est à dire.

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

    Alors:

     IDataRecord record; // Comes from somewhere entity.SsortingngProperty = record["SsortingngProperty"].IsDBNull(null); entity.Int32Property = record["Int32Property"].IsDBNull(50); entity.NoDefaultSsortingng = record["NoDefaultSsortingng"].IsDBNull(); entity.NoDefaultInt = record["NoDefaultInt"].IsDBNull(); 

    A également l’avantage de conserver la logique de vérification de nullité à un seul endroit. L’inconvénient est, bien sûr, que c’est un appel de méthode supplémentaire.

    Juste une pensée.

    J’utilise toujours:

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

    Je l’ai trouvé court et complet.

    Voici comment je gère la lecture à partir de DataRows

     /// /// Handles operations for Enumerations /// public static class DataRowUserExtensions { ///  /// Gets the specified data row. ///  ///  /// The data row. /// The key. ///  public static T Get(this DataRow dataRow, ssortingng key) { return (T) ChangeTypeTo(dataRow[key]); } private static object ChangeTypeTo(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("Type") == 1) { newNode = new TreeViewNode { ToolTip = dbRow.Get("Name"), Text = (dbRow.Get("Name").Length > 25 ? dbRow.Get("Name").Subssortingng(0, 25) + "..." : dbRow.Get("Name")), ImageUrl = "file.gif", ID = dbRow.Get("ReportPath"), Value = dbRow.Get("ReportDescription").Replace("'", "\'"), NavigateUrl = ("?ReportType=" + dbRow.Get("ReportPath")) }; } 

    Props to Monsters A obtenu mon code .Net pour ChageTypeTo.

    J’essaie d’éviter cette vérification autant que possible.

    De toute évidence, il n’est pas nécessaire de le faire pour les colonnes qui ne peuvent contenir la valeur null .

    Si vous stockez dans un type de valeur Nullable ( int? Etc.), vous pouvez simplement convertir en utilisant as int? .

    Si vous n’avez pas besoin de différencier ssortingng.Empty et null , vous pouvez simplement appeler .ToSsortingng() , car DBNull renverra ssortingng.Empty .

    J’ai fait quelque chose de similaire avec les méthodes d’extension. Voici mon code:

     public static class DataExtensions { ///  /// Gets the value. ///  /// The type of the data stored in the record /// The record. /// Name of the column. ///  public static T GetColumnValue(this IDataRecord record, ssortingng columnName) { return GetColumnValue(record, columnName, default(T)); } ///  /// Gets the value. ///  /// The type of the data stored in the record /// The record. /// Name of the column. /// The value to return if the column contains a DBNull.Value value. ///  public static T GetColumnValue(this IDataRecord record, ssortingng 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("Number",0) 

    si dans une DataRow la ligne [“nom_de_champ”] isDbNull la remplace par 0 sinon, obtenez la valeur décimale:

     decimal result = rw["fieldname"] as decimal? ?? 0; 
     public static class DBH { ///  /// Return default(T) if supplied with DBNull.Value ///  ///  ///  ///  public static T Get(object value) { return value == DBNull.Value ? default(T) : (T)value; } } 

    utiliser comme ça

     DBH.Get(itemRow["MyField"]) 

    J’ai IsDBNull dans un programme qui lit beaucoup de données à partir d’une firebase database. Avec IsDBNull, il charge les données en 20 secondes environ. Sans IsDBNull, environ 1 seconde.

    Donc je pense qu’il vaut mieux utiliser:

     public Ssortingng TryGetSsortingng(SqlDataReader sqlReader, int row) { Ssortingng res = ""; try { res = sqlReader.GetSsortingng(row); } catch (Exception) { } return res; }