检查SqlDataReader对象中的列名称

问题描述 投票:200回答:23

如何检查SqlDataReader对象中是否存在列?在我的数据访问层中,我创建了一个方法,为多个存储过程调用构建相同的对象。其中一个存储过程具有另一个列,其他存储过程不使用该列。我想修改方法以适应每个场景。

我的应用程序是用C#编写的。

c# .net sqldatareader
23个回答
318
投票

像其他答案一样使用Exceptions作为控制逻辑被认为是不好的做法,并且具有性能成本。

如果你经常使用它,那么在字段中循环会有很小的性能损失,你可能想考虑缓存结果

更合适的方法是:

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

5
投票

这里来自Jasmine的解决方案在一行......(还有一个,简单!):

reader.GetSchemaTable().Select("ColumnName='MyCol'").Length > 0;

4
投票

这是一个接受答案的单线版linq版本:

Enumerable.Range(0, reader.FieldCount).Any(i => reader.GetName(i) == "COLUMN_NAME_GOES_HERE")

3
投票
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);
    }

2
投票

此代码纠正了Levitikon对其代码的问题:(改编自:[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;
}

获取所有这些无用的列名而不是表中列的名称的原因...是因为您获得了schema列的名称(即Schema表的列名)

注意:这似乎只返回第一列的名称......

编辑:更正的代码,返回所有列的名称,但您不能使用SqlDataReader来执行此操作

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
}

1
投票

我没有得到GetSchemaTable工作,直到我找到this way

基本上我这样做:

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

1
投票
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是不区分大小写的。


1
投票

为了保持代码的健壮性和清洁性,请使用单个扩展函数,如下所示:

    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

0
投票

如果你想要列的列表,你也可以在你的DataReader上调用GetSchemaTable()而你不想得到异常......


0
投票

这些答案已经发布在这里。只是Linq-ing有点:

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

第二个更干净,速度更快。即使你没有在第一种方法中每次都运行GetSchemaTable,查找也会非常慢。


0
投票

在您的特定情况下(所有过程具有相同的列,除了1,其中有1列),检查阅读器会更好更快。 FieldCount属性来区分它们。

const int NormalColCount=.....
if(reader.FieldCount > NormalColCount)
{
// Do something special
}

我知道这是一个老帖子,但我决定回答在同样的情况下帮助其他人。您也可以(出于性能原因)将此解决方案与解决方案迭代解决方案相结合。


64
投票

使用这个布尔函数要好得多:

r.GetSchemaTable().Columns.Contains(field)

一个电话 - 没有例外。它可能会在内部抛出异常,但我不这么认为。

注意:在下面的评论中,我们想出了......正确的代码实际上是这样的:

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

0
投票

我的数据访问类需要向后兼容,因此我可能尝试访问数据库中尚不存在的版本中的列。我们有一些相当大的数据集被返回,所以我不是一个扩展方法的忠实粉丝,它必须迭代每个属性的DataReader列集合。

我有一个实用程序类,它创建一个列的私有列表,然后有一个泛型方法,尝试根据列名和输出参数类型解析值。

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

然后我可以像这样调用我的代码

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

0
投票

虽然没有公开暴露的方法,但System.Data.ProviderBase.FieldNameLookup所依赖的内部类SqlDataReader中确实存在一种方法。

要访问它并获得本机性能,必须使用ILGenerator在运行时创建方法。以下代码将允许您直接访问int IndexOf(string fieldName)类中的System.Data.ProviderBase.FieldNameLookup,并执行保存SqlDataReader.GetOrdinal()does的书籍,以便没有副作用。生成的代码镜像现有的SqlDataReader.GetOrdinal(),除了它调用FieldNameLookup.IndexOf()而不是FieldNameLookup.GetOrdinal()GetOrdinal()方法调用IndexOf()函数并在返回-1时抛出异常,因此我们绕过该行为。

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

}

0
投票

这项工作对我来说

public static class DataRecordExtensions
{
        public static bool HasColumn(IDataReader dataReader, string columnName)
        {
            dataReader.GetSchemaTable().DefaultView.RowFilter = $"ColumnName= '{columnName}'";
            return (dataReader.GetSchemaTable().DefaultView.Count > 0);
        }
}

-1
投票

怎么样

if (dr.GetSchemaTable().Columns.Contains("accounttype"))
   do something
else
   do something

它可能在循环中不那么有效


32
投票

我认为你最好的选择是在你的DataReader上预先调用GetOrdinal("columnName"),并在列不存在时捕获IndexOutOfRangeException。

实际上,让我们做一个扩展方法:

public static bool HasColumn(this IDataRecord r, string columnName)
{
    try
    {
        return r.GetOrdinal(columnName) >= 0;
    }
    catch (IndexOutOfRangeException)
    {
        return false;
    }
}

编辑

好吧,这篇文章最近开始收集一些下注,我不能删除它,因为它是公认的答案,所以我要更新它,并且(我希望)试图证明使用异常处理是合理的。控制流。

实现这一目标的另一种方法,如posted by Chad Grant,是遍历DataReader中的每个字段,并对您正在寻找的字段名称进行不区分大小写的比较。这将非常有效,并且真实地可能比我上面的方法表现得更好。当然,我绝不会在性能问题的循环中使用上面的方法。

我可以想到一种情况,其中try / GetOrdinal / catch方法将在循环不起作用的情况下工作。然而,这是一个完全假设的情况,所以这是一个非常脆弱的理由。无论如何,忍受我,看看你的想法。

想象一个允许您在表中“别名”列的数据库。想象一下,我可以使用名为“EmployeeName”的列定义一个表,但也给它一个别名“EmpName”,并且对任一名称执行select会返回该列中的数据。和我一起到目前为止?

现在想象一下,该数据库有一个ADO.NET提供程序,并且它们为它编写了一个IDataReader实现,它将列别名考虑在内。

现在,dr.GetName(i)(在Chad的答案中使用)只能返回一个字符串,因此它必须只返回列中的一个“别名”。但是,GetOrdinal("EmpName")可以使用此提供程序字段的内部实现来检查每个列的别名,以查找您要查找的名称。

在这个假设的“别名列”情况下,try / GetOrdinal / catch方法将是确保您在结果集中检查列名称的每个变体的唯一方法。

劣质的?当然。但值得一想。老实说,我更倾向于IDataRecord上的“官方”HasColumn方法。


26
投票

在一行中,在DataReader检索后使用它:

var fieldNames = Enumerable.Range(0, dr.FieldCount).Select(i => dr.GetName(i)).ToArray();

然后,

if (fieldNames.Contains("myField"))
{
    var myFieldValue = dr["myField"];
    ...

编辑

更高效的单行程,不需要加载模式:

var exists = Enumerable.Range(0, dr.FieldCount).Any(i => string.Equals(dr.GetName(i), fieldName, StringComparison.OrdinalIgnoreCase));

19
投票

以下是Jasmin的想法的工作示例:

var cols = r.GetSchemaTable().Rows.Cast<DataRow>().Select
    (row => row["ColumnName"] as string).ToList(); 

if (cols.Contains("the column name"))
{

}

13
投票

这对我有用:

bool hasColumnName = reader.GetSchemaTable().AsEnumerable().Any(c => c["ColumnName"] == "YOUR_COLUMN_NAME");

11
投票

以下内容很简单,对我有用:

 bool hasMyColumn = (reader.GetSchemaTable().Select("ColumnName = 'MyColumnName'").Count() == 1);

9
投票

如果您阅读了这个问题,Michael会询问DataReader,而不是DataRecord的人。让你的对象正确。

在DataRecord上使用r.GetSchemaTable().Columns.Contains(field)确实有效,但它返回BS列(见下面的截图)。

要查看数据列是否存在并包含DataReader中的数据,请使用以下扩展名:

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: http://stackoverflow.com/questions/373230/check-for-column-name-in-a-sqldatareader-object/7248381#7248381
        try
        {
            return dataReader.GetOrdinal(columnName) >= 0;
        }
        catch (IndexOutOfRangeException)
        {
            return false;
        }
    }
}

用法:

    public static bool CanCreate(SqlDataReader dataReader)
    {
        return dataReader.ContainsColumn("RoleTemplateId") 
            && !dataReader.IsDBNull("RoleTemplateId");
    }

在DataReader上调用r.GetSchemaTable().Columns会返回BS列:


7
投票

我为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

我认为这更强大,用法是:

If HasColumnAndValue(reader, "ID_USER") Then
    Me.UserID = reader.GetDecimal(reader.GetOrdinal("ID_USER")).ToString()
End If
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.