Click here to Skip to main content
15,914,010 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
Help!

I have a very strange problem that only occurs when the code in question is in a high load situations. My C# code calls a T-SQL stored procedure that has an OUTPUT parameter.

Under high loads, the data being returned does not make it back to the calling C# code. I have extracted all the relevant code into the example below;

Stored Procedure:
SQL
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

Database base class:
C#
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 timout 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);
                    }
                }
            }	
		}
		

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


Database Access Class
C#
/// <summary>Contains raw database commands for the Database</summary>
	public class AuthenticationDBCommands
	{
		/// <summary>Returns command object for spiAuthenticateLogin stored procedure in the Database</summary>
		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); 
		}
}


Usage:
C#
protected string GetLoginName(long loginId)
{
     SqlString loginName = SqlString.Null;

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

     return loginName.Value;
}


As you can see this is fairly standard stuff. But when the dbContext.GetLoginName() method is called by many different users in quick succession the loginName object is sometimes null.

I have tested the SQL and it always finds a value (I've inserted @LoginName into a table and it's never null). So the problem is happening after or in SqlCommand.ExecuteNonQuery();

I'm using .NET 4.5 and my database is on SQL 2008.

Has anyone seen anything like this before? Can anyone recommend any changes?

Thanks in advance,
Matt
Posted
Updated 5-Nov-14 0:10am
v7
Comments
PIEBALDconsult 31-Oct-14 12:07pm    
I suspect threading issues. Try locking the connection in ExecuteNonQuery (making a local reference doesn't help) and see whether or not that fixes it.

And you should Close the Connection when you're done with it.
I don't see how using a property to lazy-load the connection is providing any real benefit over setting the connection in the constructor -- in my opinion it just makes things worse.
And why _not_ return the result of ExecuteNonQuery?

You forgot about magic word: RETURN at the end of GetLoginName procedure.

Return Data from a Stored Procedure[^]
 
Share this answer
 
Comments
PIEBALDconsult 31-Oct-14 12:35pm    
I don't think that's the issue. He says the problem is intermittent.
Maciej Los 31-Oct-14 12:41pm    
OK. I'll wait for OP comment.
Along with my comment about locking the connection, I'd like to mention that if you run into performance issues with trying to share one connection among many threads, then you may want to support many connections. This can be as simple as changing the Connection property so it instantiates a new Connection, rather than a reference to the shared connection. The system implements connection pooling and you would be leveraging that.
But... if you wind up with so many threads (hundreds) that you exceed the pool, then you may start interfereing with other processes (I've done that).
A solution to that is to implement your own connection pool that limits how many requests you make to the built-in pool at a time (a dozen or so).

One further note -- You are getting the connectionString in the property, if you do go the route of many connections, I would get the connectionString in the constructor.
 
Share this answer
 
Comments
Maciej Los 31-Oct-14 12:41pm    
Good advice. +5!
Matt Puleston 31-Oct-14 13:28pm    
Many thanks for your suggestions. I will try closing the connection and locking method. I'll let you know the result.

Would a lock like this be effective even though the connection is assigned to the SqlCommand?

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

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

command.Connection = con;

try
{
lock (con)
{
// do it
command.ExecuteNonQuery();
}

con.Close();
}
catch (Exception)
{
// ignore the error for now
}
}
PIEBALDconsult 31-Oct-14 13:32pm    
Yes, that should be OK.
Matt Puleston 3-Nov-14 9:12am    
Hiya,

I have implemented the lock on the SQL connection when accessing the database and closing the connection when finished. However, after a load test was run the same error was generated.

I extracted the code from my effected solution and wrapped it in a WinForm project. The effected solution is a ASP.NET web form application. I'm not sure how much this changes the context of the problem.

Thanks,
Matt
PIEBALDconsult 3-Nov-14 9:47am    
That's strange. Maybe update the question with the code as it is now?
As mentioned by you, "Under high loads" you are facing this issue. I think setting the "commandtimeout" property to zero may be of help. Try doing that. In your code i was not able to find the value of the _commandtimeout variable
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900