J'utilise un SQLdatareader pour construire des POCO à partir d'une base de données. Le code fonctionne sauf lorsqu'il rencontre une valeur null dans la base de données. Par exemple, si la colonne Prénom de la base de données contient une valeur null, une exception est levée.
employee.FirstName = sqlreader.GetString(indexFirstName);
Quel est le meilleur moyen de gérer les valeurs NULL dans cette situation?
Vous devez vérifier IsDBNull
:
if(!SqlReader.IsDBNull(indexFirstName))
{
employee.FirstName = sqlreader.GetString(indexFirstName);
}
C'est votre seul moyen fiable de détecter et de gérer cette situation.
J'ai encapsulé ces choses dans des méthodes d'extension et j'ai tendance à renvoyer une valeur par défaut si la colonne est bien null
:
public static string SafeGetString(this SqlDataReader reader, int colIndex)
{
if(!reader.IsDBNull(colIndex))
return reader.GetString(colIndex);
return string.Empty;
}
Maintenant, vous pouvez l'appeler comme ceci:
employee.FirstName = SqlReader.SafeGetString(indexFirstName);
et vous n'aurez plus jamais à vous soucier d'une exception ou d'une valeur null
.
Vous devez utiliser l'opérateur as
combiné à l'opérateur ??
pour les valeurs par défaut. Les types de valeur devront être lus comme nuls et donner un défaut.
employee.FirstName = sqlreader[indexFirstName] as string;
employee.Age = sqlreader[indexAge] as int? ?? default(int);
L'opérateur as
gère le casting, y compris le contrôle de DBNull.
Pour une chaîne, vous pouvez simplement transtyper la version de l'objet (accessible à l'aide de l'opérateur array) et terminer avec une chaîne null pour les valeurs null:
employee.FirstName = (string)sqlreader[indexFirstName];
ou
employee.FirstName = sqlreader[indexFirstName] as string;
Pour les entiers, si vous convertissez en un int nullable, vous pouvez utiliser GetValueOrDefault ()
employee.Age = (sqlreader[indexAge] as int?).GetValueOrDefault();
ou l'opérateur de coalescence nulle (??
).
employee.Age = (sqlreader[indexAge] as int?) ?? 0;
IsDbNull(int)
est généralement beaucoup plus lent que d'utiliser des méthodes telles que GetSqlDateTime
et de comparer à DBNull.Value
. Essayez ces méthodes d’extension pour SqlDataReader
.
public static T Def<T>(this SqlDataReader r, int ord)
{
var t = r.GetSqlValue(ord);
if (t == DBNull.Value) return default(T);
return ((INullable)t).IsNull ? default(T) : (T)t;
}
public static T? Val<T>(this SqlDataReader r, int ord) where T:struct
{
var t = r.GetSqlValue(ord);
if (t == DBNull.Value) return null;
return ((INullable)t).IsNull ? (T?)null : (T)t;
}
public static T Ref<T>(this SqlDataReader r, int ord) where T : class
{
var t = r.GetSqlValue(ord);
if (t == DBNull.Value) return null;
return ((INullable)t).IsNull ? null : (T)t;
}
Utilisez-les comme ceci:
var dd = r.Val<DateTime>(ords[4]);
var ii = r.Def<int>(ords[0]);
int nn = r.Def<int>(ords[0]);
Une façon de le faire est de vérifier les db nulls:
employee.FirstName = (sqlreader.IsDBNull(indexFirstName)
? ""
: sqlreader.GetString(indexFirstName));
Je ne pense pas qu'il existe une valeur de colonneNULLlorsque les lignes sont renvoyées dans un datareader à l'aide du nom de colonne.
Si vous faites datareader["columnName"].ToString();
, cela vous donnera toujours une valeur qui peut être une chaîne vide (String.Empty
si vous devez comparer).
Je voudrais utiliser ce qui suit et ne vous inquiétez pas trop:
employee.FirstName = sqlreader["columnNameForFirstName"].ToString();
reader.IsDbNull(ColumnIndex)
fonctionne comme le dit beaucoup de réponses.
Et je tiens à mentionner que si vous travaillez avec des noms de colonne, il peut être plus confortable de comparer des types.
if(reader["TeacherImage"].GetType() == typeof(DBNull)) { //logic }
Cette solution dépend moins du fournisseur et fonctionne avec un lecteur SQL, OleDB et MySQL:
public static string GetStringSafe(this IDataReader reader, int colIndex)
{
return GetStringSafe(reader, colIndex, string.Empty);
}
public static string GetStringSafe(this IDataReader reader, int colIndex, string defaultValue)
{
if (!reader.IsDBNull(colIndex))
return reader.GetString(colIndex);
else
return defaultValue;
}
public static string GetStringSafe(this IDataReader reader, string indexName)
{
return GetStringSafe(reader, reader.GetOrdinal(indexName));
}
public static string GetStringSafe(this IDataReader reader, string indexName, string defaultValue)
{
return GetStringSafe(reader, reader.GetOrdinal(indexName), defaultValue);
}
Ce que j'ai tendance à faire est de remplacer les valeurs nulles dans l'instruction SELECT par quelque chose de approprié.
SELECT ISNULL(firstname, '') FROM people
Ici, je remplace chaque null par une chaîne vide. Votre code ne jettera pas d'erreur dans ce cas.
Vérifiez sqlreader.IsDBNull(indexFirstName)
avant d'essayer de le lire.
Vous pouvez écrire une fonction générique pour vérifier Null et inclure la valeur par défaut quand il s'agit de NULL. Appelez-le lors de la lecture de Datareader
public T CheckNull<T>(object obj)
{
return (obj == DBNull.Value ? default(T) : (T)obj);
}
Lors de la lecture de l'utilisation de Datareader
while (dr.Read())
{
tblBPN_InTrRecon Bpn = new tblBPN_InTrRecon();
Bpn.BPN_Date = CheckNull<DateTime?>(dr["BPN_Date"]);
Bpn.Cust_Backorder_Qty = CheckNull<int?>(dr["Cust_Backorder_Qty"]);
Bpn.Cust_Min = CheckNull<int?>(dr["Cust_Min"]);
}
Je pense que vous voudriez utiliser:
SqlReader.IsDBNull(indexFirstName)
comment créer des méthodes d'assistance
Pour cordes
private static string MyStringConverter(object o)
{
if (o == DBNull.Value || o == null)
return "";
return o.ToString();
}
Usage
MyStringConverter(read["indexStringValue"])
Pour Int
private static int MyIntonverter(object o)
{
if (o == DBNull.Value || o == null)
return 0;
return Convert.ToInt32(o);
}
Usage
MyIntonverter(read["indexIntValue"])
Pour la date
private static DateTime? MyDateConverter(object o)
{
return (o == DBNull.Value || o == null) ? (DateTime?)null : Convert.ToDateTime(o);
}
Usage
MyDateConverter(read["indexDateValue"])
Remarque: pour DateTime, déclarez varialbe en tant que
DateTime? variable;
Nous utilisons une série de méthodes statiques pour extraire toutes les valeurs de nos lecteurs de données. Donc, dans ce cas, nous appellerions DBUtils.GetString(sqlreader(indexFirstName))
L'avantage de créer des méthodes statiques/partagées est que vous n'avez pas à faire les mêmes vérifications, encore et encore ...
Les méthodes statiques contiendraient du code pour vérifier les valeurs NULL (voir les autres réponses sur cette page).
Vous pouvez utiliser l'opérateur conditionnel:
employee.FirstName = sqlreader["indexFirstName"] != DBNull.Value ? sqlreader[indexFirstName].ToString() : "";
Ancienne question mais peut-être que quelqu'un a encore besoin d'une réponse
en vrai je travaillais autour de cette question comme ça
Pour int:
public static object GatDataInt(string Query, string Column)
{
SqlConnection DBConn = new SqlConnection(ConnectionString);
if (DBConn.State == ConnectionState.Closed)
DBConn.Open();
SqlCommand CMD = new SqlCommand(Query, DBConn);
SqlDataReader RDR = CMD.ExecuteReader();
if (RDR.Read())
{
var Result = RDR[Column];
RDR.Close();
DBConn.Close();
return Result;
}
return 0;
}
la même chose pour la chaîne, il suffit de retourner "" au lieu de 0 car "" est une chaîne vide
afin que vous puissiez l'utiliser comme
int TotalPoints = GatDataInt(QueryToGetTotalPoints, TotalPointColumn) as int?;
et
string Email = GatDatastring(QueryToGetEmail, EmailColumn) as string;
très flexible afin que vous puissiez insérer n'importe quelle requête pour lire n'importe quelle colonne et que ça ne reviendra jamais avec une erreur
et/ou utiliser un opérateur ternaire avec affectation:
employee.FirstName = rdr.IsDBNull(indexFirstName))?
String.Empty: rdr.GetString(indexFirstName);
remplace la valeur par défaut (lorsque null) en fonction de chaque type de propriété ...
private static void Render(IList<ListData> list, IDataReader reader)
{
while (reader.Read())
{
listData.DownUrl = (reader.GetSchemaTable().Columns["DownUrl"] != null) ? Convert.ToString(reader["DownUrl"]) : null;
//没有这一列时,让其等于null
list.Add(listData);
}
reader.Close();
}
En complément de la réponse de marc_s, vous pouvez utiliser une méthode d’extension plus générique pour obtenir des valeurs à partir de SqlDataReader:
public static T SafeGet<T>(this SqlDataReader reader, int col)
{
return reader.IsDBNull(col) ? default(T) : reader.GetFieldValue<T>(col);
}
J'utilise le code ci-dessous pour gérer les cellules nulles dans une feuille Excel lue dans un datatable.
if (!reader.IsDBNull(2))
{
row["Oracle"] = (string)reader[2];
}
Cette méthode dépend de indexFirstName, qui doit être l'ordinal de colonne de base zéro.
if(!sqlReader.IsDBNull(indexFirstName))
{
employee.FirstName = sqlreader.GetString(indexFirstName);
}
Si vous ne connaissez pas l'index de la colonne mais ne voulez pas vérifier un nom, vous pouvez utiliser cette méthode d'extension à la place:
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;
}
}
Et utilisez la méthode comme ceci:
if(sqlReader.HasColumn("FirstName"))
{
employee.FirstName = sqlreader["FirstName"];
}
En influençant depuis la réponse de getpsyched , j'ai créé une méthode générique qui vérifie la valeur d'une colonne par son nom
public static T SafeGet<T>(this System.Data.SqlClient.SqlDataReader reader, string nameOfColumn)
{
var indexOfColumn = reader.GetOrdinal(nameOfColumn);
return reader.IsDBNull(indexOfColumn) ? default(T) : reader.GetFieldValue<T>(indexOfColumn);
}
Usage:
var myVariable = SafeGet<string>(reader, "NameOfColumn")
Il y a beaucoup de réponses ici avec des informations utiles (et des informations erronées), j'aimerais les rassembler.
La réponse courte à la question est de vérifier DBNull - presque tout le monde est d'accord sur ce point :)
Plutôt que d'utiliser une méthode d'assistance pour lire les valeurs nullables par type de données SQL, une méthode générique nous permet de résoudre ce problème avec beaucoup moins de code. Cependant, vous ne pouvez pas avoir une seule méthode générique à la fois pour les types de valeur nullable et les types de référence, cela est décrit en détail dans Type nullable comme paramètre générique possible? et C # contrainte de type générique pour tout nullable .
Donc, à la suite des réponses de @ZXX et @getpsyched, nous nous retrouvons avec ceci, 2 méthodes pour obtenir des valeurs nullables et j'ai ajouté une 3ème pour les valeurs non-nulles (cela complète l'ensemble en fonction du nommage de méthode).
public static T? GetNullableValueType<T>(this SqlDataReader sqlDataReader, string columnName) where T : struct
{
int columnOrdinal = sqlDataReader.GetOrdinal(columnName);
return sqlDataReader.IsDBNull(columnOrdinal) ? (T?)null : sqlDataReader.GetFieldValue<T>(columnOrdinal);
}
public static T GetNullableReferenceType<T>(this SqlDataReader sqlDataReader, string columnName) where T : class
{
int columnOrdinal = sqlDataReader.GetOrdinal(columnName);
return sqlDataReader.IsDBNull(columnOrdinal) ? null : sqlDataReader.GetFieldValue<T>(columnOrdinal);
}
public static T GetNonNullValue<T>(this SqlDataReader sqlDataReader, string columnName)
{
int columnOrdinal = sqlDataReader.GetOrdinal(columnName);
return sqlDataReader.GetFieldValue<T>(columnOrdinal);
}
J'utilise généralement des noms de colonnes, modifiez-les si vous utilisez des index de colonnes. Sur la base de ces noms de méthodes, je peux dire si je m'attends à ce que les données soient nulles ou non, ce qui est très utile lorsque vous examinez du code écrit il y a longtemps.
Conseils;
Enfin, tout en testant les méthodes ci-dessus sur tous les types de données SQL Server, j'ai découvert que vous ne pouvez pas obtenir directement un caractère [] d'un SqlDataReader. Si vous voulez un caractère [], vous devrez obtenir une chaîne et utiliser ToCharArray ().
Voici la classe d'assistance que les autres peuvent utiliser s'ils ont besoin de la réponse de @marc_s:
public static class SQLDataReaderExtensions
{
public static int SafeGetInt(this SqlDataReader dataReader, string fieldName)
{
int fieldIndex = dataReader.GetOrdinal(fieldName);
return dataReader.IsDBNull(fieldIndex) ? 0 : dataReader.GetInt32(fieldIndex);
}
public static int? SafeGetNullableInt(this SqlDataReader dataReader, string fieldName)
{
int fieldIndex = dataReader.GetOrdinal(fieldName);
return dataReader.GetValue(fieldIndex) as int?;
}
public static string SafeGetString(this SqlDataReader dataReader, string fieldName)
{
int fieldIndex = dataReader.GetOrdinal(fieldName);
return dataReader.IsDBNull(fieldIndex) ? string.Empty : dataReader.GetString(fieldIndex);
}
public static DateTime? SafeGetNullableDateTime(this SqlDataReader dataReader, string fieldName)
{
int fieldIndex = dataReader.GetOrdinal(fieldName);
return dataReader.GetValue(fieldIndex) as DateTime?;
}
public static bool SafeGetBoolean(this SqlDataReader dataReader, string fieldName)
{
return SafeGetBoolean(dataReader, fieldName, false);
}
public static bool SafeGetBoolean(this SqlDataReader dataReader, string fieldName, bool defaultValue)
{
int fieldIndex = dataReader.GetOrdinal(fieldName);
return dataReader.IsDBNull(fieldIndex) ? defaultValue : dataReader.GetBoolean(fieldIndex);
}
}
Convertir les poignées DbNull de manière sensée.
employee.FirstName = Convert.ToString(sqlreader.GetValue(indexFirstName));