【问题标题】:SqlDataReader Index Out Of Range Exception when correct column count is returned返回正确列数时的 SqlDataReader 索引超出范围异常
【发布时间】:2016-12-08 09:15:50
【问题描述】:

我有一个返回正确列数的存储过程,但下面的代码大部分时间都有效,但 RANDOMLY 在下面抛出异常。我们最近升级到 .NET 4.6,之后我们注意到异常。

问题:

1 异常发生的位置和原因?

2 根据底部的源码,SQL客户端怎么可能从服务器端接收到空的metaData?

存储过程GetUser:

CREATE PROCEDURE [dbo].[GetUser]    
    @UserID int
AS
BEGIN

    SET NOCOUNT ON;

    DECLARE @UserIDChar NVARCHAR(255);
    SET @UserIDChar = convert(nvarchar(255), @UserID);

    SELECT TOP 1
        A.Value1 As FirstName, 
        A.Value2 As LastName
       -- three more columns omitted here
    FROM dbo.Activity as A      
    WHERE A.ActivityValue = @UserIDChar  --ActivityValue is NVARCHAR(255) type
    ORDER BY A.DateCreated DESC 

    SET NOCOUNT OFF;

END 

C#网络层:

using (var cn = new SqlConnection(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString))
{
    SqlCommand cmd = new SqlCommand();
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.CommandText = "GetUser";  //the proc returns one row that consists of two columns
    cmd.Connection = cn;
    cmd.Parameters.AddWithValue("@UserID", userId);

    cn.Open();

    using (IDataReader dr = cmd.ExecuteReader(CommandBehavior.SingleResult))
    {
        if (dr.Read())  //check if row is available
        {
            string firstName = (string)dr[0];
            string lastName = (string)dr[1];                        
            // three more columns omitted here
            return firstName + " " + lastName;
        }
    }
}

错误:

异常类型:System.IndexOutOfRangeException
消息:索引超出了数组的范围。
数据:System.Collections.ListDictionaryInternal
TargetSite: Void CheckDataIsReady(Int32, Boolean, Boolean, System.String)
来源:System.Data

源码如下:

private void CheckDataIsReady(int columnIndex, bool allowPartiallyReadColumn = false, bool permitAsync = false, string methodName = null) {
            if (_isClosed) {
                throw ADP.DataReaderClosed(methodName ?? "CheckDataIsReady");
            }
            if ((!permitAsync) && (_currentTask != null)) {
                throw ADP.AsyncOperationPending();
            }
            Debug.Assert(!_sharedState._dataReady || _metaData != null, "Data is ready, but there is no metadata?");
            if ((!_sharedState._dataReady) || (_metaData == null)) {
                throw SQL.InvalidRead();
            }
            if ((columnIndex < 0) || (columnIndex >= _metaData.Length)) {
                throw ADP.IndexOutOfRange();
            }
            if ((IsCommandBehavior(CommandBehavior.SequentialAccess)) &&                                    // Only for sequential access
                ((_sharedState._nextColumnDataToRead > columnIndex) || (_lastColumnWithDataChunkRead > columnIndex) ||   // Read past column
                ((!allowPartiallyReadColumn) && (_lastColumnWithDataChunkRead == columnIndex)) ||           // Partially read column
                ((allowPartiallyReadColumn) && (HasActiveStreamOrTextReaderOnColumn(columnIndex))))) {      // Has a Stream or TextReader on a partially-read column
                    throw ADP.NonSequentialColumnAccess(columnIndex, Math.Max(_sharedState._nextColumnDataToRead, _lastColumnWithDataChunkRead + 1));
            }
        }

http://referencesource.microsoft.com/#System.Data/System/Data/SqlClient/SqlDataReader.cs,577d642dce99ed0d

【问题讨论】:

  • TOP 没有明确的 ORDER BY 是没有意义的 - 你希望得到哪个 TOP 1 行?如果没有明确的排序,它是任意的——你只会从你的表中取回一个或多或少随机选择的行......
  • 您的存储过程中可能需要SET NOCOUNT ON。可能发生的情况是返回的记录数而不是 SELECT 的结果。
  • @Kendo - dr.Read() 在那种情况下不会返回 false。
  • 请为GetUser发布您实际完整的存储过程脚本;您的 C# 代码看起来不错
  • 我相信你最后不需要SET NOCOUNT OFF

标签: c# asp.net .net sql-server tsql


【解决方案1】:

缺少架构名称可能会惹恼你。

另外,下面的 C# 代码中的一些调试技巧。

  1. 为存储过程提供架构名称。

    IF EXISTS (
                SELECT * FROM INFORMATION_SCHEMA.ROUTINES
                WHERE ROUTINE_TYPE = N'PROCEDURE' and ROUTINE_SCHEMA = N'dbo' and ROUTINE_NAME = N'GetUser'  
            )   
    BEGIN
        DROP PROCEDURE [dbo].[GetUser]
    END
    
    GO
    
    CREATE PROCEDURE dbo.GetUser()
    
    AS 
    
    SELECT TOP 1
        FirstName, LastName
    FROM 
        User
    --omitted WHERE userid = @id
    
    
    GO
    
  2. 将你的 c# 更改为:

       using (var cn = new SqlConnection(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString))
      {
           SqlCommand cmd = new SqlCommand();
           cmd.CommandType = CommandType.StoredProcedure;
           cmd.CommandText = "dbo.GetUser";  
           cmd.Connection = cn;
    
           cn.Open();
    
           using (IDataReader dr = cmd.ExecuteReader())
           {
           while (dr.Read()) 
                               string temp = string.Empty;
    
        int fc = dr.FieldCount;    
        if (fc>0)
        {
                        object val2 = dr[0];
                        temp = val1.GetType().ToString();
        }
    
        if (fc>1)
        {
                        object val2 = dr[1];
                        temp = val2.GetType().ToString();
        }
    
    
    
        temp = "did I get here?";
    
        string firstName = (string)dr[0];
        string lastName = (string)dr[1];                        
    
        return firstName + " " + lastName;
    }
           }
           }
    

【讨论】:

  • 只存在一种模式,即dbo
  • 我需要知道为什么会发生异常。
  • 您是否运行了 c# 代码(我的版本).. 没有架构名称?
  • 注意,我使用重载的 .ExecuteReader() 方法。代码正在查看 FieldCount 和返回行的数据类型。如果你让上面的代码工作(我认为它会)......那么你需要专注于重载。 System.Data.Commandbehavior.SingleRow 将是我的下一步。
【解决方案2】:

我认为这一行在你的存储过程中搞砸了:

A.Value2 广告姓氏

应该是: A.Value2 作为 LastName

【讨论】:

  • 我认为这是一个错字
  • 这是一个像 ~techspider 所说的错字
【解决方案3】:

你没有在C#代码中指定参数userid请使用CommandParameter然后尝试做

【讨论】:

  • 如评论中一样,为简单起见已将其注释掉。对此感到抱歉。
  • 您是否再次尝试检查删除 CommandBehavior.SingleResult 后显示的结果可能是元数据包含 0 值
  • 如果再次抛出异常,那么您必须重新定义表的架构,因为您的代码看起来不错
  • 为什么在CommandBehavior.SingleResult被删除后元数据包含0值?
  • 为什么我必须重新定义表的架构?请告诉我这背后的原因。
【解决方案4】:

试试看
可能无法修复,但你会得到信息,至少它会优雅地失败

try
{ 
    cn.Open();
    string firstName = "not";
    string lastName = "found";
    using (SQLDataReader dr = cmd.ExecuteReader(CommandBehavior.SingleResult))
    {
        if (dr.Read())  //if there is a row, there are two columns. Thus index is used below
        {
            Debug.WriteLine(dr.FieldCount);
            firstName = rdr.IsDBNull(0) ? string.Empty : rdr.GetString(0);
            lastName = rdr.IsDBNull(1) ? string.Empty : rdr.GetString(1);                         
        }
    }
} 
catch (SqlException ex)
{
    Debug.WriteLine("GetUser " + Environment.NewLine + ex.Message);
}
catch (Exception ex)
{
    Debug.WriteLine("GetUser" + Environment.NewLine + ex.Message);
    throw ex;
}
finally
{
    cn.Close();
}
return firstName + " " + lastName;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-08-16
    • 2011-03-16
    • 1970-01-01
    • 1970-01-01
    • 2022-10-14
    相关资源
    最近更新 更多