1

I have a simple stored procedure that returns the type_id (int) as an OUTPUT parameter using the type_name (varchar(100)) as the input parameter. When I execute the stored procedure in SqlServer, it works fine and returns the appropriate type_id.

CREATE PROCEDURE [dbo].[intake_types_select_by_type_name]
    @type_name varchar(100),
    @type_id integer OUTPUT
AS
BEGIN
    SET NOCOUNT ON;
    SET XACT_ABORT ON

    SELECT  @type_id = type_id
    FROM    intake_types
    WHERE   type_name = @type_name
END

However, when I call the stored procedure from a function in VisualStudio the parameter @type_id returns Nothing.

Public Shared Function sp_intake_types_select_by_type_name(ByVal conn As DBConnection, ByVal caseName As String) As Integer
    Dim sp As SqlCommand = conn.GetStoredProcedure("intake_types_select_by_type_name")
    With sp.Parameters
        .Add("@type_name", SqlDbType.VarChar, ParameterDirection.Input).Value = caseName
        .Add("@type_id", SqlDbType.Int)
        .Item("@type_id").Direction = ParameterDirection.Output
    End With
    sp.ExecuteScalar()
    If Not IsDBNull(sp.Parameters("@type_id").Value) Then
        Return sp.Parameters("@type_id").Value
    Else
        Return Nothing
    End If
End Function

I have spent hours searching the web for a solution and found nothing that helps. I have a similar stored procedure and function in a different project that uses the same coding and logic and it works fine (however it returns a varchar as the OUTPUT parameter). I have compared these two projects to see if I am missing something simple but no joy.

  • 2
    Did you tried `Dim result = sp.ExecuteScalar()`? – Fabio Feb 15 '17 at 18:09
  • Every example I've seen does something like `TypeID = (Int32) sp.ExecuteScalar()`. Are you sure in your other project you are simply making a naked call to `sp.ExecuteScalar()`? – Aaron Bertrand Feb 15 '17 at 18:12
  • remove `.Item("@type_id").Direction = ParameterDirection.Output`, change `.Add("@type_id", SqlDbType.Int)` to `.Add("@type_id", SqlDbType.Int, ParameterDirection.Output)`, and change `ExecuteScalar` to `ExecuteNonQuery()`. I don't know if it will solve your problem but it's worth a try. – Zohar Peled Feb 15 '17 at 18:23
  • `ExecuteScalar` returns something ***IF*** you have a `SELECT` in your stored procedure that selects (and thus returns) exactly **one row, one column**. You on the other hand are **assigning** this value to an **output parameter** - this will ***NOT*** be returned by `ExecuteScalar`! – marc_s Feb 15 '17 at 19:31
  • @Aaron Bertrand - Yes, it is making a naked call and working, but now I'm not sure why. I will be updating this one, too. – Michael Callas Feb 15 '17 at 20:26
  • @Zohard Peled - tried that before and it didn't work. Thanks anyway. – Michael Callas Feb 15 '17 at 20:27
  • @marc_s - great information! Thanks. – Michael Callas Feb 15 '17 at 20:28
  • @AaronBertrand You have spotted the problem, unique to the default settings in VB.NET, where the availability of run-time type conversion (a.k.a. late binding) is invoked. `Option Strict On` disables that. [The many memes of meta: Option Strict On](http://meta.stackexchange.com/a/273139/232762). – Andrew Morton Feb 15 '17 at 20:41

1 Answers1

1

You really should use Option Strict On and correct the problems it points out. At the moment, implicit conversions between data types are being performed which will make your code much slower and possibly error-prone.

I suggest the following:

CREATE PROCEDURE [dbo].[intake_types_select_by_type_name]
    @type_name varchar(100)
AS
BEGIN
    SET NOCOUNT ON;
    SET XACT_ABORT ON;

    SELECT  type_id
    FROM    intake_types
    WHERE   type_name = @type_name
END

And the function to call it:

Option Strict On
' ....'

Public Shared Function sp_intake_types_select_by_type_name(ByVal conn As DBConnection, ByVal caseName As String) As Integer
    Dim sp As SqlCommand = conn.GetStoredProcedure("intake_types_select_by_type_name")
    sp.Parameters.Add("@type_name", SqlDbType.VarChar, 100).Value = caseName

    Dim result As Object = sp.ExecuteScalar()

    If result Is Nothing Then
        Return -1 ' check for -1 in the calling code '
    Else
        Return CInt(result)
    End If

End Function

It is almost always good idea to specify the size of the SQL parameter so that the database can re-use the execution plan rather than making a new execution plan for each length of the parameters.

Please note that it is generally a bad idea to use one SQL connection for everything: you should use the connection then dispose of it as soon as its immediate use is done. It seems like a good idea at first, but it is fighting against the way that it is designed (connection pooling takes care of fast re-opening of connections).

Ref: Execute Scalar to trap error in case of no records returned.

Community
  • 1
  • 1
Andrew Morton
  • 24,203
  • 9
  • 60
  • 84
  • Andrew, thank you very much for the tips, excellent explanations, _and_ a working solution. The **Option Strict On** uncovered two more implicit conversions which I have fixed. I will be looking into using/disposing SQL connection next instead of using one SQL connection for everything. I really appreciate your time and great input! – Michael Callas Feb 15 '17 at 20:19
  • @MichaelCallas You're welcome :) I strongly recommend setting Option Strict On as the default for new projects, if you haven't done that already. Regarding another of the answers in the post I linked to, I did actually test the VB snippet against a real database to confirm that `Is Nothing` works and `Is DbNull.Value` gives false results, just in case you read further. – Andrew Morton Feb 15 '17 at 20:31
  • Andrew, I have the default set for new projects but this is an inherited project so I am glad you pointed that out. I also read the link in your post so thanks for the follow-up. Good stuff to know. – Michael Callas Feb 15 '17 at 20:53