Vérifier le nom de la colonne dans un objet SqlDataReader
Comment puis-je vérifier si une colonne existe dans un objet SqlDataReader
? Dans ma couche d'accès aux données, j'ai créé une méthode qui construit le même objet pour plusieurs appels de procédures stockées. L'une des procédures stockées a une colonne supplémentaire qui n'est pas utilisé par les autres procédures stockées. Je veux modifier la méthode pour accommoder chaque scénario.
ma demande est rédigée en C#.
22 réponses
L'utilisation de Exception
s pour la logique de contrôle comme dans d'autres réponses est considérée comme une mauvaise pratique et a des coûts de performance.
boucle à travers les champs peut avoir une petite performance hit si vous l'utilisez beaucoup et vous pouvez envisager de cacher les résultats""
la façon la plus appropriée de le faire est:
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;
}
}
il est beaucoup mieux d'utiliser cette fonction booléenne:
r.GetSchemaTable().Columns.Contains(field)
Un appel - pas d'exceptions. Il pourrait y avoir des exceptions à l'intérieur, mais je ne pense pas.
NOTE: dans les commentaires ci-dessous, nous avons trouvé ceci... le code correct est en fait ceci:
public static bool HasColumn(DbDataReader Reader, string ColumnName) {
foreach (DataRow row in Reader.GetSchemaTable().Rows) {
if (row["ColumnName"].ToString() == ColumnName)
return true;
} //Still here? Column not found.
return false;
}
je pense que votre meilleur pari est d'appeler GetOrdinal ("columnnname") sur votre DataReader à l'avant, et d'attraper un IndexOutOfRangeException dans le cas où la colonne n'est pas présente.
en fait, faisons une méthode d'extension:
public static bool HasColumn(this IDataRecord r, string columnName)
{
try
{
return r.GetOrdinal(columnName) >= 0;
}
catch (IndexOutOfRangeException)
{
return false;
}
}
Modifier
Ok, ce post commence à recueillir quelques votes négatifs dernièrement, et je ne peux pas le supprimer parce que c'est la réponse acceptée, donc je vais aller pour le mettre à jour et (j'espère) essayer de justifier l'utilisation de la manipulation d'exception comme flux de contrôle.
l'autre façon d'y parvenir, comme posté par Chad Grant , est de faire une boucle à travers chaque champ dans le DataReader et de faire une comparaison cas-insensible pour le nom du champ que vous recherchez. Cela fonctionne vraiment bien, et franchement effectuera probablement mieux que ma méthode ci-dessus. Certainement je n'utiliserais jamais la méthode ci-dessus à l'intérieur d'une boucle où performace a été question.
je peux penser à une situation dans laquelle la méthode try/GetOrdinal/catch fonctionnera là où la boucle ne fonctionne pas. Il s'agit, cependant, d'une situation complètement hypothétique en ce moment, donc c'est une justification très fragile. Quoi qu'il en soit, supportez-moi et voyez ce que vous en pensez.
Imaginez une base de données qui vous permet de "alias" colonnes dans une table. Imaginez que je puisse définir une table avec une colonne appelée "EmployeeName" mais aussi lui donner un alias de "EmpName"", et faire un select pour l'un ou l'autre nom retournerait les données dans cette colonne. Avec moi jusqu'à présent?
imaginez maintenant qu'il y a un ADO.NET fournisseur pour cette base de données, et ils ont codé une implémentation IDataReader pour it qui prend en compte les alias de colonne.
maintenant, dr.GetName(i)
(comme utilisé dans la réponse de Chad) ne peut retourner qu'une seule chaîne, il doit donc retourner Seulement un des" alias " sur une colonne. Toutefois, GetOrdinal("EmpName")
pourrait utiliser l'implémentation interne des champs de ce fournisseur pour vérifier l'alias de chaque colonne pour le nom que vous recherchez.
dans cette situation hypothétique de" colonnes aliasées", la méthode try/GetOrdinal/catch serait le seul moyen d'être sûr que vous vérifiez chaque variation du nom d'une colonne dans le jeu de résultats.
fragile? Assurer. Mais vaut la peine d'une pensée. Honnêtement, je préférerais une méthode" officielle " sur IDataRecord.
en une ligne, utilisez ceci après la récupération de votre DataReader:
var fieldNames = Enumerable.Range(0, dr.FieldCount).Select(i => dr.GetName(i)).ToArray();
Puis,
if (fieldNames.Contains("myField"))
{
var myFieldValue = dr["myField"];
...
Modifier
revêtement unique beaucoup plus efficace qui ne nécessite pas de charger le schéma:
var exists = Enumerable.Range(0, dr.FieldCount).Any(i => string.Equals(dr.GetName(i), fieldName, StringComparison.OrdinalIgnoreCase));
voici un échantillon de travail pour L'idée de Jasmin:
var cols = r.GetSchemaTable().Rows.Cast<DataRow>().Select
(row => row["ColumnName"] as string).ToList();
if (cols.Contains("the column name"))
{
}
ça marche pour moi:
bool hasColumnName = reader.GetSchemaTable().AsEnumerable().Any(c => c["ColumnName"] == "YOUR_COLUMN_NAME");
ce qui suit est simple et travaillé pour moi:
bool hasMyColumn = (reader.GetSchemaTable().Select("ColumnName = 'MyColumnName'").Count() == 1);
si vous avez lu la question, Michael a posé des questions sur DataReader, pas sur les gens de DataRecord. Obtenez vos objets de droit.
en utilisant un r.GetSchemaTable().Columns.Contains(field)
sur un DataRecord ne fonctionne, mais il retourne des colonnes BS (voir screenshot ci-dessous.)
pour voir si une colonne de données existe et contient des données dans un lecteur de données, utilisez les extensions suivantes:
public static class DataReaderExtensions
{
/// <summary>
/// Checks if a column's value is DBNull
/// </summary>
/// <param name="dataReader">The data reader</param>
/// <param name="columnName">The column name</param>
/// <returns>A bool indicating if the column's value is DBNull</returns>
public static bool IsDBNull(this IDataReader dataReader, string columnName)
{
return dataReader[columnName] == DBNull.Value;
}
/// <summary>
/// Checks if a column exists in a data reader
/// </summary>
/// <param name="dataReader">The data reader</param>
/// <param name="columnName">The column name</param>
/// <returns>A bool indicating the column exists</returns>
public static bool ContainsColumn(this IDataReader dataReader, string columnName)
{
/// See: /q/check-for-column-name-in-a-sqldatareader-object-13768/"RoleTemplateId")
&& !dataReader.IsDBNull("RoleTemplateId");
}
Appel r.GetSchemaTable().Columns
sur un DataReader retourne BS colonnes:
j'ai écrit pour les utilisateurs de Visual Basic:
Protected Function HasColumnAndValue(ByRef reader As IDataReader, ByVal columnName As String) As Boolean
For i As Integer = 0 To reader.FieldCount - 1
If reader.GetName(i).Equals(columnName) Then
Return Not IsDBNull(reader(columnName))
End If
Next
Return False
End Function
je pense que c'est plus puissant et l'usage est :
If HasColumnAndValue(reader, "ID_USER") Then
Me.UserID = reader.GetDecimal(reader.GetOrdinal("ID_USER")).ToString()
End If
Voici la solution de Jasmine en une ligne... (un de plus, tho simple!):
reader.GetSchemaTable().Select("ColumnName='MyCol'").Length > 0;
Voici une version linq one liner de la réponse acceptée:
Enumerable.Range(0, reader.FieldCount).Any(i => reader.GetName(i) == "COLUMN_NAME_GOES_HERE")
Hashtable ht = new Hashtable();
Hashtable CreateColumnHash(SqlDataReader dr)
{
ht = new Hashtable();
for (int i = 0; i < dr.FieldCount; i++)
{
ht.Add(dr.GetName(i), dr.GetName(i));
}
return ht;
}
bool ValidateColumn(string ColumnName)
{
return ht.Contains(ColumnName);
}
ce code corrige les problèmes que Levitikon avait avec leur code: (adapté de: [1]: http://msdn.microsoft.com/en-us/library/system.data.datatablereader.getschematable.aspx )
public List<string> GetColumnNames(SqlDataReader r)
{
List<string> ColumnNames = new List<string>();
DataTable schemaTable = r.GetSchemaTable();
DataRow row = schemaTable.Rows[0];
foreach (DataColumn col in schemaTable.Columns)
{
if (col.ColumnName == "ColumnName")
{
ColumnNames.Add(row[col.Ordinal].ToString());
break;
}
}
return ColumnNames;
}
la raison pour obtenir tous ces noms de colonne inutiles et pas le nom de la colonne de votre table... Est parce que vous recevez le nom de la colonne de schéma (i.e. les noms de colonne pour la table de schéma)
NOTE: ceci ne semble renvoyer que le nom de la première colonne...
EDIT: correction de code qui renvoie le nom de toutes les colonnes, mais vous ne pouvez pas utiliser un SqlDataReader de le faire
public List<string> ExecuteColumnNamesReader(string command, List<SqlParameter> Params)
{
List<string> ColumnNames = new List<string>();
SqlDataAdapter da = new SqlDataAdapter();
string connection = ""; // your sql connection string
SqlCommand sqlComm = new SqlCommand(command, connection);
foreach (SqlParameter p in Params) { sqlComm.Parameters.Add(p); }
da.SelectCommand = sqlComm;
DataTable dt = new DataTable();
da.Fill(dt);
DataRow row = dt.Rows[0];
for (int ordinal = 0; ordinal < dt.Columns.Count; ordinal++)
{
string column_name = dt.Columns[ordinal].ColumnName;
ColumnNames.Add(column_name);
}
return ColumnNames; // you can then call .Contains("name") on the returned collection
}
Je N'ai pas non plus obtenu GetSchemaTable
de travailler, jusqu'à ce que je trouve de cette façon .
fondamentalement je fais ceci:
Dim myView As DataView = dr.GetSchemaTable().DefaultView
myView.RowFilter = "ColumnName = 'ColumnToBeChecked'"
If myView.Count > 0 AndAlso dr.GetOrdinal("ColumnToBeChecked") <> -1 Then
obj.ColumnToBeChecked = ColumnFromDb(dr, "ColumnToBeChecked")
End If
public static bool DataViewColumnExists(DataView dv, string columnName)
{
return DataTableColumnExists(dv.Table, columnName);
}
public static bool DataTableColumnExists(DataTable dt, string columnName)
{
string DebugTrace = "Utils::DataTableColumnExists(" + dt.ToString() + ")";
try
{
return dt.Columns.Contains(columnName);
}
catch (Exception ex)
{
throw new MyExceptionHandler(ex, DebugTrace);
}
}
Columns.Contains
est pas sensible à la casse btw.
pour garder votre code robuste et propre, utilisez une seule fonction d'extension, comme ceci:
Public Module Extensions
<Extension()>
Public Function HasColumn(r As SqlDataReader, columnName As String) As Boolean
Return If(String.IsNullOrEmpty(columnName) OrElse r.FieldCount = 0, False, Enumerable.Range(0, r.FieldCount).Select(Function(i) r.GetName(i)).Contains(columnName, StringComparer.OrdinalIgnoreCase))
End Function
End Module
vous pouvez aussi appeler GetSchemaTable () sur votre DataReader si vous voulez la liste des colonnes et vous ne voulez pas avoir à obtenir une exception...
ces réponses sont déjà postées ici. Il suffit de Linq-ing un peu:
bool b = reader.GetSchemaTable().Rows
.Cast<DataRow>()
.Select(x => (string)x["ColumnName"])
.Contains(colName, StringComparer.OrdinalIgnoreCase);
//or
bool b = Enumerable.Range(0, reader.FieldCount)
.Select(reader.GetName)
.Contains(colName, StringComparer.OrdinalIgnoreCase);
le second est plus propre, et beaucoup plus rapide. Même si vous ne l'exécutez GetSchemaTable
à chaque fois dans la première approche, la recherche va être très lent.
dans votre situation particulière (toutes les procédures ont les mêmes colonnes sauf une qui a une colonne supplémentaire), il sera préférable et plus rapide de vérifier le lecteur. Propriété FieldCount pour les distinguer.
const int NormalColCount=.....
if(reader.FieldCount > NormalColCount)
{
// Do something special
}
je sais que c'est un vieux post, mais j'ai décidé de répondre pour aider les autres dans la même situation. vous pouvez également (pour des raisons de performance) mélanger cette solution avec la solution itérative solution.
ma classe d'accès aux données doit être rétrocompatible, donc je pourrais essayer d'accéder à une colonne dans une version où elle n'existe pas encore dans la base de données. Nous avons quelques ensembles de données assez grands étant retournés de sorte que je ne suis pas un grand fan d'une méthode d'extension qui doit itérer la collecte de colonne DataReader pour chaque propriété.
j'ai une classe utilitaire qui crée une liste privée de colonnes et ensuite on a une méthode générique qui tente de résoudre une valeur basée sur une nom de la colonne et type de paramètre de sortie.
private List<string> _lstString;
public void GetValueByParameter<T>(IDataReader dr, string parameterName, out T returnValue)
{
returnValue = default(T);
if (!_lstString.Contains(parameterName))
{
Logger.Instance.LogVerbose(this, "missing parameter: " + parameterName);
return;
}
try
{
if (dr[parameterName] != null && [parameterName] != DBNull.Value)
returnValue = (T)dr[parameterName];
}
catch (Exception ex)
{
Logger.Instance.LogException(this, ex);
}
}
/// <summary>
/// Reset the global list of columns to reflect the fields in the IDataReader
/// </summary>
/// <param name="dr">The IDataReader being acted upon</param>
/// <param name="NextResult">Advances IDataReader to next result</param>
public void ResetSchemaTable(IDataReader dr, bool nextResult)
{
if (nextResult)
dr.NextResult();
_lstString = new List<string>();
using (DataTable dataTableSchema = dr.GetSchemaTable())
{
if (dataTableSchema != null)
{
foreach (DataRow row in dataTableSchema.Rows)
{
_lstString.Add(row[dataTableSchema.Columns["ColumnName"]].ToString());
}
}
}
}
alors je peux appeler mon code comme ça
using (var dr = ExecuteReader(databaseCommand))
{
int? outInt;
string outString;
Utility.ResetSchemaTable(dr, false);
while (dr.Read())
{
Utility.GetValueByParameter(dr, "SomeColumn", out outInt);
if (outInt.HasValue) myIntField = outInt.Value;
}
Utility.ResetSchemaTable(dr, true);
while (dr.Read())
{
Utility.GetValueByParameter(dr, "AnotherColumn", out outString);
if (!string.IsNullOrEmpty(outString)) myIntField = outString;
}
}
bien qu'il n'existe aucune méthode exposée publiquement, une méthode existe dans la classe interne System.Data.ProviderBase.FieldNameLookup
sur laquelle SqlDataReader
se fonde.
pour y accéder et obtenir des performances natives, vous devez utiliser ILGenerator pour créer une méthode à l'exécution. Le code suivant vous donnera un accès direct à int IndexOf(string fieldName)
dans la classe System.Data.ProviderBase.FieldNameLookup
ainsi qu'effectuer la tenue de livres que SqlDataReader.GetOrdinal()
fait de sorte qu'il n'y a aucun effet secondaire. Les miroirs de code générés le SqlDataReader.GetOrdinal()
sauf qu'il appelle FieldNameLookup.IndexOf()
au lieu de FieldNameLookup.GetOrdinal()
. La méthode GetOrdinal()
appelle la fonction IndexOf()
et lance une exception si -1
est retourné, donc nous contournons ce comportement.
using System;
using System.Data;
using System.Data.SqlClient;
using System.Reflection;
using System.Reflection.Emit;
public static class SqlDataReaderExtensions {
private delegate int IndexOfDelegate(SqlDataReader reader, string name);
private static IndexOfDelegate IndexOf;
public static int GetColumnIndex(this SqlDataReader reader, string name) {
return name == null ? -1 : IndexOf(reader, name);
}
public static bool ContainsColumn(this SqlDataReader reader, string name) {
return name != null && IndexOf(reader, name) >= 0;
}
static SqlDataReaderExtensions() {
Type typeSqlDataReader = typeof(SqlDataReader);
Type typeSqlStatistics = typeSqlDataReader.Assembly.GetType("System.Data.SqlClient.SqlStatistics", true);
Type typeFieldNameLookup = typeSqlDataReader.Assembly.GetType("System.Data.ProviderBase.FieldNameLookup", true);
BindingFlags staticflags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Static;
BindingFlags instflags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance;
DynamicMethod dynmethod = new DynamicMethod("SqlDataReader_IndexOf", typeof(int), new Type[2]{ typeSqlDataReader, typeof(string) }, true);
ILGenerator gen = dynmethod.GetILGenerator();
gen.DeclareLocal(typeSqlStatistics);
gen.DeclareLocal(typeof(int));
// SqlStatistics statistics = (SqlStatistics) null;
gen.Emit(OpCodes.Ldnull);
gen.Emit(OpCodes.Stloc_0);
// try {
gen.BeginExceptionBlock();
// statistics = SqlStatistics.StartTimer(this.Statistics);
gen.Emit(OpCodes.Ldarg_0); //this
gen.Emit(OpCodes.Call, typeSqlDataReader.GetProperty("Statistics", instflags | BindingFlags.GetProperty, null, typeSqlStatistics, Type.EmptyTypes, null).GetMethod);
gen.Emit(OpCodes.Call, typeSqlStatistics.GetMethod("StartTimer", staticflags | BindingFlags.InvokeMethod, null, new Type[] { typeSqlStatistics }, null));
gen.Emit(OpCodes.Stloc_0); //statistics
// if(this._fieldNameLookup == null) {
Label branchTarget = gen.DefineLabel();
gen.Emit(OpCodes.Ldarg_0); //this
gen.Emit(OpCodes.Ldfld, typeSqlDataReader.GetField("_fieldNameLookup", instflags | BindingFlags.GetField));
gen.Emit(OpCodes.Brtrue_S, branchTarget);
// this.CheckMetaDataIsReady();
gen.Emit(OpCodes.Ldarg_0); //this
gen.Emit(OpCodes.Call, typeSqlDataReader.GetMethod("CheckMetaDataIsReady", instflags | BindingFlags.InvokeMethod, null, Type.EmptyTypes, null));
// this._fieldNameLookup = new FieldNameLookup((IDataRecord)this, this._defaultLCID);
gen.Emit(OpCodes.Ldarg_0); //this
gen.Emit(OpCodes.Ldarg_0); //this
gen.Emit(OpCodes.Ldarg_0); //this
gen.Emit(OpCodes.Ldfld, typeSqlDataReader.GetField("_defaultLCID", instflags | BindingFlags.GetField));
gen.Emit(OpCodes.Newobj, typeFieldNameLookup.GetConstructor(instflags, null, new Type[] { typeof(IDataReader), typeof(int) }, null));
gen.Emit(OpCodes.Stfld, typeSqlDataReader.GetField("_fieldNameLookup", instflags | BindingFlags.SetField));
// }
gen.MarkLabel(branchTarget);
gen.Emit(OpCodes.Ldarg_0); //this
gen.Emit(OpCodes.Ldfld, typeSqlDataReader.GetField("_fieldNameLookup", instflags | BindingFlags.GetField));
gen.Emit(OpCodes.Ldarg_1); //name
gen.Emit(OpCodes.Call, typeFieldNameLookup.GetMethod("IndexOf", instflags | BindingFlags.InvokeMethod, null, new Type[] { typeof(string) }, null));
gen.Emit(OpCodes.Stloc_1); //int output
Label leaveProtectedRegion = gen.DefineLabel();
gen.Emit(OpCodes.Leave_S, leaveProtectedRegion);
// } finally {
gen.BeginFaultBlock();
// SqlStatistics.StopTimer(statistics);
gen.Emit(OpCodes.Ldloc_0); //statistics
gen.Emit(OpCodes.Call, typeSqlStatistics.GetMethod("StopTimer", staticflags | BindingFlags.InvokeMethod, null, new Type[] { typeSqlStatistics }, null));
// }
gen.EndExceptionBlock();
gen.MarkLabel(leaveProtectedRegion);
gen.Emit(OpCodes.Ldloc_1);
gen.Emit(OpCodes.Ret);
IndexOf = (IndexOfDelegate)dynmethod.CreateDelegate(typeof(IndexOfDelegate));
}
}
Que Diriez-vous de
if (dr.GetSchemaTable().Columns.Contains("accounttype"))
do something
else
do something
Il ne serait probablement pas aussi efficace dans une boucle