SQL ParameterDirection.InputOutput / SqlCommand.ExecuteNonQuery()错误

时间:2014-11-05 15:18:56

标签: c# asp.net sql-server stored-procedures sqlcommand

我有一个非常奇怪的问题,只有当有问题的代码处于高负载情况时才会出现。我的ASP.NET Web客户端C#代码调用具有OUTPUT参数的T-SQL存储过程。

在高负载下,返回的数据有时不会返回到调用C#代码。我已将所有相关代码提取到下面的示例中;

存储过程:

CREATE PROCEDURE GetLoginName
    @LoginId BIGINT,
    @LoginName VARCHAR(50) OUTPUT
AS
   SET NOCOUNT ON

   SELECT @LoginName = LoginName
   FROM Logins
   WHERE Id = @LoginId

   SET NOCOUNT OFF
GO

数据库基类:

public class DatabaseContextBase : IDisposable
{
    private SqlConnection _connection;
    private string _connectionString;
    private SqlInt32 _returnValue;
    private int _commandTimeout;
    private static int _maxDatabaseExecuteAttempts = 3;   

    private static int s_commandTimeout = 30;

    protected DBContextBase()
    {
        // try and get the connection string off the identity first...
        string connectionString = GetConnectionStringFromIdentity();

        if(connectionString != null)
        {
            ConstructionHelper(connectionString, s_commandTimeout);
        }
        else
        {
            // use the initialised static connection string, and call the other overload
            // of the constructor
            ConstructionHelper(s_connectionString, s_commandTimeout);
        }   
    }

    private void ConstructionHelper( string connectionString, int commandTimeout ) 
    {
        // store the connection string in a member var.
        _connectionString = connectionString;

        // store the timeout in a member var.
        _commandTimeout = commandTimeout;
    }

    public static string GetConnectionStringFromIdentity()
    {
        IIdentity identity = Thread.CurrentPrincipal.Identity as wxyz.Security.wxyzIdentityBase;
        string connectionString = null;

        if(identity != null)
        {
            connectionString = ((wxyz.Security.wxyzIdentityBase) identity ).ConnectionString;
        }

        return connectionString;
    }

    public void Dispose()
    {           
        if (_connection.State != ConnectionState.Closed)
        {
            _connection.Close();
        }

        _connection.Dispose();
        _connection = null;
    }

    protected void ExecuteNonQuery(SqlCommand command)
    {
        SqlConnection con = this.Connection;

        lock (con)
        {
            if (con.State != ConnectionState.Open)
            {
                con.Open();
            }

            // don't need a try catch as this is only ever called from another method in this 
            // class which will wrap it.
            command.Connection = con;
            command.Transaction = _transaction;
            command.CommandTimeout = _commandTimeout;

            for (int currentAttempt = 1; currentAttempt == _maxDatabaseExecuteAttempts; currentAttempt++)
            {
                try
                {
                    // do it
                    command.ExecuteNonQuery();

                    // done, exit loop
                    break;
                }
                catch (SqlException sex)
                {
                    HandleDatabaseExceptions(currentAttempt, sex, command.CommandText);
                }
            }
        }   
    }

    protected void HandleDatabaseExceptions(int currentAttempt, SqlException sqlException, string sqlCommandName)
    {
        if (DataExceptionUtilities.IsDeadlockError(sqlException))
        {
            if (!this.IsInTransaction)
            {
                // Not in a transaction and a deadlock detected.
                // If we have not exceeded our maximum number of attemps, then try to execute the SQL query again.
                string deadlockMessage = string.Format("Deadlock occured in attempt {0} for '{1}':", currentAttempt, sqlCommandName);
                Logging.Write(new ErrorLogEntry(deadlockMessage, sqlException));

                if (currentAttempt == DBContextBase.MaxDatabaseExecuteAttempts)
                {
                    // This was the last attempt so throw the exception
                    throw sqlException;
                }

                // Wait for a short time before trying again
                WaitShortAmountOfTime();
            }
            else
            {
                // We're in a transaction, then the calling code needs to handle the deadlock
                string message = string.Format("Deadlock occured in transaction for '{0}':", sqlCommandName);

                throw new DataDeadlockException(message, sqlException);
            }
        }
        else if (this.IsInTransaction && DataExceptionUtilities.IsTimeoutError(sqlException))
        {
            // We're in a transaction and the calling code needs to handle the timeout 
            string message = string.Format("Timeout occured in transaction for '{0}':", sqlCommandName);

            // Raise a Deadlock exception and the calling code will rollback the transaction
            throw new DataDeadlockException(message, sqlException);
        }
        else
        {
            // Something else has gone wrong
            throw sqlException;
        }   
    }

    /// <summary>
    /// get the SqlConnection object owned by this database (already connected to db) 
    /// </summary>
    public SqlConnection Connection
    {
        get {
            // check whether we've got a connection string (from either identity or static initialise)
            if ( _connectionString == null )
            {
                throw new ArgumentNullException( "connectionString", "Connection string not set" );
            }

            if ( _connection != null )
            {
                return _connection;
            }
            else
            {
                _connection = new SqlConnection( _connectionString );
                return _connection;
            }
        }   
    }

    /// <summary>
    /// Return value from executed stored procedure
    /// </summary>
    public SqlInt32 ReturnValue
    {
        get { return _returnValue; }
        set { _returnValue = value; }
    }
}

数据库访问类:

public class AuthenticationDBCommands
{
    public static SqlCommand GetLoginName()
    {
        System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand("GetLoginName");
        cmd.CommandType = CommandType.StoredProcedure;

        cmd.Parameters.Add(new SqlParameter("@RETURN_VALUE", System.Data.SqlDbType.Int, 0, System.Data.ParameterDirection.ReturnValue, false, 0, 0, "RETURN_VALUE", System.Data.DataRowVersion.Current, SqlInt32.Null));

        cmd.Parameters.Add(new SqlParameter("@LoginId", SqlDbType.BigInt, 8, ParameterDirection.Input, false, 0, 0, "LoginId", DataRowVersion.Current, SqlInt64.Null));

        cmd.Parameters.Add(new SqlParameter("@LoginName", SqlDbType.VarChar, 50, ParameterDirection.InputOutput,false, 0, 0, "LoginName", DataRowVersion.Current, SqlString.Null));

        return cmd;
    }
}

public class AuthenticationDBContext : DatabaseContextBase
{
    public AuthenticationDBContext() : base()
    {
    } 

    public void GetLoginName(SqlInt64 LoginId, ref SqlString LoginName)
    {
        SqlCommand cmd = AuthenticationDBCommands.GetLoginName();
        cmd.Parameters[1].Value = LoginId;
        cmd.Parameters[2].Value = LoginName;

        base.ExecuteNonQuery(cmd);
        base.ReturnValue = (SqlInt32) cmd.Parameters[0].Value; 
        LoginName = (SqlString)(cmd.Parameters[2].Value); 
    }
}

因此,当它在ASP.NET Web客户端中使用时,它看起来像这样:

protected string GetLoginName(long loginId)
{
    SqlString loginName = SqlString.Null;

    using (AuthenticationDBContext dbc = new AuthenticationDBContext())
    {
      dbc.GetLoginName(loginId, ref loginName);
    }

    return loginName.Value;
}

正如您所看到的,这是相当标准的东西。但是当许多不同的用户快速连续调用AuthenticationDBContext.GetLoginName()方法时,loginName对象有时 null。

当SqlCommand.ExecuteNonQuery()无法返回任何数据时,它不会抛出异常。

我测试了SQL,它总是找到一个值(我已经将@LoginName插入到表中,它永远不会为null)。所以问题发生在SqlCommand.ExecuteNonQuery();

之后或之中

正如我所说,这是它发生的一个例子。实际上,许多不同的存储过程都没有返回数据。

同样值得一提的是,当有问题的Web客户端负载很重时,在IIS中共享应用程序池的其他Web客户端不会受到影响。

我正在使用.NET 4.5,我的数据库在SQL Server 2008上。

以前有人见过这样的事吗? 任何人都可以推荐任何改变吗?

提前致谢,

马特

更新。谢谢你们的评论。我对DatabaseContextBase类进行了以下更改。

                private void ExecuteNonQueryImpl(SqlCommand command)
            {
                object _lockObject = new object();

                lock (_lockObject)
                {
                    SqlConnection con = this.GetConnection();
                    if (con.State != ConnectionState.Open)
                    {
                        con.Open();
                    }

                    // don't need a try catch as this is only ever called from another method in this 
                    // class which will wrap it.
                    command.Connection = con;
                    command.Transaction = _transaction;
                    command.CommandTimeout = _commandTimeout;

                    for (int currentAttempt = 1; currentAttempt <= _maxDatabaseExecuteAttempts; currentAttempt++)
                    {
                        try
                        {
                            // do it
                            command.ExecuteNonQuery();

                            // done, exit loop
                            break;
                        }
                        catch (SqlException sex)
                        {
                            HandleDatabaseExceptions(currentAttempt, sex, command.CommandText);
                        }
                    }

                    if (!this.IsInTransaction)
                    {
                        con.Close();
                    }
                }
            }

            public SqlConnection GetConnection()
            {
                if (this.IsInTransaction)
                {
                    return this.Connection;
                }
                else
                {
                    // check whether we've got a connection string (from either identity or static initialise)
                    if ( _connectionString == null )
                    {
                        string exceptionMessage = Language.Translate("DbContextNotInitialized");

                        throw new ArgumentNullException( "connectionString", exceptionMessage );
                    }

                    return new SqlConnection(_connectionString);
                }
            }

但是,在负载测试中,数据有时仍会返回null。 Web客户端不在事务中工作,因此每次调用时都会创建,打开和关闭新的SqlConnection对象。 (还有其他共享DatabaseContextBase类的代码区域,它们在事务中起作用,因此需要原始连接属性)

我想再次提一下,我确信存储过程正常,因为我已将@LoginName值插入表中,并且它永远不会为空。

谢谢, 马特

1 个答案:

答案 0 :(得分:0)

你的&#34; for循环&#34;定义不正确。

for (int currentAttempt = 1; currentAttempt == _maxDatabaseExecuteAttempts; currentAttempt++)

这会将currentAttempt初始化为1,运行循环,递增currentAttempt,然后检查currentAttempt是否等于3,它不是,并退出循环。我想你想要的是

for (int currentAttempt = 1; currentAttempt <= _maxDatabaseExecuteAttempts; currentAttempt++)