1

Can anyone tell me what is going on in this function?? In the following code snippet, user.Id = 0, id.Value = 0 and id.SqlDbType = Int.. as expected since user.Id is an int field.

However, error.Value = null and error.SqlDbType = BigInt. What gives? If I use non-zero it detects an int and the correct value.

Note: the Value properties are the same before and after declaring the parameter direction.

public static long InsertUpdate(User user) {

    SqlParameter id = new SqlParameter("@id", user.Id);
    id.Direction = ParameterDirection.InputOutput;
    cmd.Parameters.Add(id);

    SqlParameter error = new SqlParameter("@error_code", 0);
    error.Direction = ParameterDirection.Output;
    cmd.Parameters.Add(error);

    .... other stuff
}

As well, if @SET @error_Code = 0 in the sproc, error.Value = NULL and error.SqlDbType = NVarChar AFTER the procedure runs. If I set it to an integer I get an Int type.

UPDATE: After specifying SqlDbType.Int the parameter now has the correct SqlDbType before and after the command... however the stored procedure is still setting @error_code = null when I in fact set it to 0.

UPDATE: When the sproc executes the SELECT statement the @error_code parameter is always returned as null, regardless of when or not it has been set... this only happens when there's a select statement...

Here is the procedure to reproduce:

ALTER PROCEDURE [dbo].[usp_user_insert_v1]
    @username VARCHAR(255),
    @password VARCHAR(255),
    @gender CHAR(1),
    @birthday DATETIME,
    @error_code INT OUTPUT
AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    DECLARE @default_dt DATETIME
    EXEC @default_dt = uf_get_default_date
    DECLARE @dt DATETIME = GETUTCDATE()

            INSERT INTO users(username, password, gender, birthday, create_dt, last_login_dt, update_dt, deleted)
            VALUES(@username, @password, @gender, @birthday, @dt, @default_dt, @default_dt, 0)
            SELECT * FROM users WHERE id = SCOPE_IDENTITY()
            SET @error_code = 3
            RETURN

END

SOLUTION?

http://forums.asp.net/t/1208409.aspx?Interesting+problem+with+getting+OUTPUT+parameters+from+SQL+Server+using+C+

Found this link on the ASP forums... apparently you can't read the output parameter until you have read all the results from the SqlDataReader... very unfortunate for me since I decide whether or not I even WANT to read the results based on the output param...

Cailen
  • 690
  • 1
  • 9
  • 23

3 Answers3

2

From SqlParameter.Value on MSDN

For output and return value parameters, the value is set on completion of the SqlCommand

i.e. I wouldn't rely on type inference to set the return type implicitly. I would explicitly set the type of the output parameter:

var error = new SqlParameter("@error_code", SqlDbType.Int)
{
    Direction = ParameterDirection.Output
};

Edit

After some reflection of SqlParameter:

The BigInt is easy to explain - it is the default SqlDbType, and the SqlParameter(string parameterName, object value) ctor doesn't overwrite this value.

public enum SqlDbType
{
  BigInt = 0,
  ...

Re: @error_code is returned as NULL

The only thing I can think of is that the PROC fails to complete cleanly. Try moving the SET @error_code = 0 above the EXEC @default_dt = uf_get_default_date ?

Edit

Confirmed, @Damien's point is correct

SqlParameter error = new SqlParameter("@error_code", 0);

Actually calls this ctor:

public SqlParameter(string parameterName, SqlDbType dbType)

whereas

SqlParameter error = new SqlParameter("@error_code", 1234);

calls

public SqlParameter(string parameterName, object value)

Reason : 0 is implicitly castable to enum.

Community
  • 1
  • 1
StuartLC
  • 104,537
  • 17
  • 209
  • 285
  • Maybe this should be a different question, but do you have any idea why the SqlDbType is an NVarChar when my sql procedure declares '@error_code INT OUTPUT' and does SET '@error_code = 0' at the start of the procedure? It seems like it should AT LEAST be setting it to the right datatype after the command executes according to your link. – Cailen Jan 28 '14 at 03:39
  • Going to open a new question for why my output parameter is being nulled in the sproc (I've verified this happens when a SELECT statement is executed in the procedure, and the output parameter gets set to null regardless of 0 or nonzero). Def +1 for reminding me of object initializers though. – Cailen Jan 28 '14 at 04:28
  • 1
    The `(string,object)` constructor isn't being called at all, for `error`. – Damien_The_Unbeliever Jan 28 '14 at 07:36
  • @Damien well done - indeed, the (string, object) ctor isn't called for zero, but is called for any other INT. [Wow.](http://stackoverflow.com/questions/2043554/method-overload-resolution-unexpected-behavior) – StuartLC Jan 28 '14 at 07:51
  • I like the rest of your answer though so +1. – Damien_The_Unbeliever Jan 28 '14 at 07:56
1

Both of the current answers are slightly incorrect because they're based on the assumption that the constructor being called for your error object is the (string,object) one. This is not the case. A literal 0 can be converted to any enum type1, and such a conversion would be preferred over a conversion to object. So the constructor being called is the (string,SqlDbType) constructor.

So the type is set to BigInt because that's the 0 value for the SqlDbType enumeration, and the Value is null because you have no code that attempts to set the value.

SqlParameter error = new SqlParameter("@error_code", (object)0);

should cause it to select the correct overload.


Demo:

using System;
using System.Data;

namespace ConsoleApplication
{
    internal class Program
    {
        private static void Main()
        {
            var a = new ABC("ignore", 0);
            var b = new ABC("ignore", (object)0);
            var c = new ABC("ignore", 1);
            int i = 0;
            var d = new ABC("ignore", i);
            Console.ReadLine();
        }

    }

    public class ABC
    {
        public ABC(string ignore, object value)
        {
            Console.WriteLine("Object");
        }

        public ABC(string ignore, SqlDbType value)
        {
            Console.WriteLine("SqlDbType");
        }
    }
}

Prints:

SqlDbType
Object
Object
Object

1From the C# Language specification, version 5, section 1.10 (that is, just in the introduction to the language, not buried deep down in the language lawyery bits):

In order for the default value of an enum type to be easily available, the literal 0 implicitly converts to any enum type. Thus, the following is permitted.

Color c = 0;

I'd have also thought this important enough to be in the Language Reference on MSDN but haven't found a definitive source yet.

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448
  • Can you explain why SqlParamter("@id", user.Id) creates an Int and not SqlParameter("@error_code", 0)? user.Id is an int field set to 0. Also, SqlParamter("@error_code", 2) correctly creates sets the SqlDataParamter to an int... so I don't think that it's selecting the SqlDataType(name, SqlDbType) constructor when passed an integer literal. – Cailen Jan 28 '14 at 13:24
  • `user.Id` isn't an integer **literal** in any circumstance. And it's the integer literal `0` that has this special case that allows it to be converted to an `enum` value. And, if you don't believe me, try adding more cases to the sample code I included in my answer, which I did so that you can *see* which overload is chosen for different circumstances. – Damien_The_Unbeliever Jan 28 '14 at 13:45
  • Sorry, literal was totally the wrong word. I see your point, strange that other integer literals aren't interpreted as SqlDbType enums. – Cailen Jan 28 '14 at 14:00
  • @Cailen - have you seen my updates that I've been adding? - it's a specific feature of the language, specifically for `0`. – Damien_The_Unbeliever Jan 28 '14 at 14:00
  • Thanks for the update! That explains my last question. – Cailen Jan 28 '14 at 14:01
0

Well, it looks like the most reliable way of doing it is by using this overload:

SqlParameter error = new SqlParameter("@error_code", SqlDBType.Int);
error.Value = 0;

The overload you're using takes an object as a parameter, and for some reason which I can't divine, it's not picking the right type.

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501