24 November 2006 - 10:32 PM / by Dominic Pettifer. 1 Image for Output Parameters AND Recordsets From a Stored Procedure. 9 Comments for Output Parameters AND Recordsets From a Stored Procedure.
Technical Article - Just had a frustrating past few hours with Stored Procedures in SQL Server 2005 and ASP.NET. I was trying to return both output parameters and a recordset from the same stored procedure and the caller (a .NET data access class) wasn’t having any of it! I found a solution so thought I better post it here to help others, and in case I forget.
As you may know Stored Procedures allow you to encapsulate data access logic (SELECT, INSERT statements etc.) and have them executed on the database. All major databases support them including SQL Server, MySQL, Oracle etc.
Typically one would return a recordset of data with a SELECT statement based in some input parameters sent to the SProc (Stored Procedure). In other cases you may want to INSERT a new record and return just the primary key value that gets generated, you would use an OUTPUT parameter for this to return the ID to the caller.
But what if you wanted to return both a recordset and an output parameter (or parameters)? While this is perfectly possible, there are some caveats to look out for that could leave you pulling out your hair in frustration, almost like I did.
My problem was that I was trying to write a stored procedure that retrieved the currently active Poll, (see the Poll bit on the right), and the poll choices for that poll. They were stored in two tables, 'tblPolls' and 'tblPollChoices' with a one-to-many relationship. The current Poll's fields were returned as output params and the poll choices were returned as a recordset. This is the Sproc...
CREATE PROCEDURE db_Polls_GetCurrentPoll
(
@PollID INT OUTPUT,
@PollQuestion VARCHAR(128) OUTPUT,
@DatePosted DATETIME OUTPUT
)
AS
BEGIN
SET NOCOUNT ON
SELECT TOP 1 @PollID = plsID,
@PollQuestion = plsQuestion,
@DatePosted = plsDatePosted
FROM tblPolls
WHERE plsDateEnded IS NULL
AND plsEnabled = 1
ORDER BY plsDatePosted DESC
SELECT pchID, pchChoiceText, pchVotes
FROM tblPollChoices
WHERE pchPollID = @PollID
ORDER BY pchVotes DESC
ENDWith the above Sproc you would access it like this directly via Enterprise Manager or SQL Server Management Studio, and it worked perfectly. See screenshot below. So far so good I thought.
DECLARE @PollID INT
DECLARE @PollQuestion VARCHAR(128)
DECLARE @DatePosted DATETIME
EXEC db_Polls_GetCurrentPoll
@PollID OUTPUT,
@PollQuestion OUTPUT,
@DatePosted OUTPUT
SELECT @PollID AS 'Poll ID'
SELECT @PollQuestion AS 'Poll Question'
SELECT @DatePosted AS 'Date Posted'
The alternative was to use two stored procedures, or return two recordsets, but since the first recordset would only contain a single row it didn't seem appropriate.
Another reason you might return both output params and recordsets, is for a Stored Procedure that returns records in a table using a LIMIT or ROWCOUNT() clause (records 1 – 10, 11 – 20 etc.), and a count of all the records in a table (as an output param) for implementing custom pagination, something I did in the AsisSas project for custom paging of Seed Samples.
The database access code for the above Sproc was as follows. First I would set up the SqlCommand object...
SqlCommand cmd = new SqlCommand("db_Polls_GetCurrentPoll", conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter("@PollID", SqlDbType.Int));
cmd.Parameters[“@PollID”].Direction = ParameterDirection.Output;
// Other output parameters hereThen I would first try to retrieve the output params before iterating through the recordset...
conn.Open();
SqlDataReader reader = cmd.ExecuteReader();
if (reader.HasRows)
{
poll = new Poll();
poll.Id = (int)cmd.Parameters[“@PollID”].Value;
poll.Question = (string)cmd.Parameters[“@PollQuestion”].Value;
poll.DatePosted = (DateTime)cmd.Parameters[“@DatePosted”].Value;
while (reader.Read())
{
// Get poll choices code here
}
reader.Close();
}
else
{
reader.Close();
}The problem was that cmd.Parameters[“@ParamName”].Value; code was returning NULL, and the cast was throwing an ArgumentNullException (you can't have a NULL int and DataTime because they are value/primitive types). Basically the data wasn't there.
Then after much digging (Googling) I found that you must completely process the entire recordset BEFORE retrieving the output parameters otherwise, the output params will be empty. So changing the code to the following fixed the problem.
conn.Open();
SqlDataReader reader = cmd.ExecuteReader();
if (reader.HasRows)
{
poll = new Poll();
while (reader.Read())
{
// Get poll choices code here
}
reader.Close();
// Get output params AFTER we've processed and CLOSED the SqlDataReadeer
poll.Id = (int)cmd.Parameters[“@PollID”].Value;
poll.Question = (string)cmd.Parameters[“@PollQuestion”].Value;
poll.DatePosted = (DateTime)cmd.Parameters[“@DatePosted”].Value;
}
else
{
reader.Close();
}poll.Id, poll.Question and poll.DatePosted are now being correctly filled in, no more exceptions.
So to summarise, you can retrieve both output params and a recordset (or recordsets) as long as you do the following:
[ol]
[/ol]
Notice in the code above reader.Close() is called before trying to retrieve the output params. That particular point had me stumped for ages, as I had originally called reader.Close() afterwards.
It seems the data is accessed sequentially and the client must consume all recordsets before any output or return values are sent to the client. Moral of the story: Google is your friend!
Have any questions, like to add something, or would like to correct me on something? Then please email me or post a comment for this blog. Thanks for reading!
this is what i call the pimpmackdaddy post. go on son, have a cigar.
Posted on 26 May 2010 - 5:43 PM / by Tony Arrighi
Thanks for this code! I'm about to give up till read you post!
Posted on 26 October 2009 - 1:52 AM / by reyn morfe
Very useful! I have been struggling on the same for sometime!
Posted on 15 July 2009 - 8:56 AM / by Prathyusha
Thanks buddy.
it is very useful.
i want to add one more point.....
-- you can retrieve output parameters first also.
--This can be possible if you execute sqlcommand first(sqlCmd.executequery())
--then retrieve the output params by calling sqlcmd.parameters("param").value
--then u can go for retrieving dataset by calling sqlcmd.executereader()
Posted on 29 May 2009 - 1:05 PM / by sivakumar arani
Hi,
You wouldn't know how helpful your post was to me!
Thanks a lot.
Evgeny
Posted on 19 May 2008 - 5:57 AM / by Evgeny
Most thankfull for your post!
Posted on 31 March 2008 - 12:52 PM / by ppiglets
Thanks a lot for this post. I found the answer I was looking for in this post.
Posted on 19 December 2007 - 3:44 AM / by Sreejith k
Hi,
Regarding your article about returning outputparams and records (recordset)... to make a long story short... you may find SET NOCOUNT ON useful.
Some sort of explanation is here:
http://www.eggheadcafe.com/software/aspnet/30519061/retrieving-an-output-para.aspx
If you feel like it, please comment if it solves your problem.
Thanx for your article
Andrej
Posted on 20 September 2007 - 12:01 PM / by Andrej
Dominic
You could also investigate the ability in SQL Server to handle multiple active result sets (MARS). Basically, this technology allows you to query for data on a connection that you already have open, so you could open your datareader and then as you iterate over the reader, you could retrieve any sub-data from SQL Server on the same connection.
Posted on 27 November 2006 - 7:50 PM / by Pete O'Hanlon
Dominic
You could also investigate the ability in SQL Server to handle multiple active result sets (MARS). Basically, this technology allows you to query for data on a connection that you already have open, so you could open your datareader and then as you iterate over the reader, you could retrieve any sub-data from SQL Server on the same connection.
Result of Stored Procedure using output params and a recordset (from the blog Output Parameters AND Recordsets From a Stored Procedure )
@andrewmy I'm thinking of using Git myself, what Windows client do you use? Is TortoiseGit the defacto standard?
about 5 hours ago from EchofonThe new Google Logo is awesome, almost as good as Pacman
about 7 hours ago from Echofon"Normalization is from the devil" - do you agree? http://ayende.com/Blog/archive/2010/09/06/normalization-is-from-the-devil.aspx
12:48 PM September 6th from Echofon@ellieemptylemon Thanks for those, although company I work for is looking to send us on training courses.
11:19 AM September 6th from Echofon