Howto: Nullable parameters in stored procedure calls

Posts   
 
    
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39615
Joined: 17-Aug-2003
# Posted on: 22-Apr-2005 11:25:18   

As some people want to pass NULL values to stored procedure calls, I thought of a solution for those people and teh steps below should give you that solution. Please let me know if you run into problems with this. (I haven't tested this myself, but it should work).

It will change the typed parameters like int foo, float bar, to object foo, object bar.

The following example is for SqlServer, but if you're using another database, for example Oracle, you've to alter the templates in the folder of that database. This example alters both the action procedures and the retrieval procedures templates. It also alters the templates for both selfservicing and adapter. If you're using adapter, you don't need to alter the templates without 'adapter' in the name and vice versa if you're using adapter.

1) go to <llblgen pro installation folder>\Drivers\SqlServer 2) if you're using C#, use CSharpTemplateset.config otherwise VBNetTemplatset.config. Make a copy of your templateset of choice and call it CustomCSharpTemplateSet.config (or CustomVBNettemplateset.config) (or another name) 3) open the just created template set config file in a texteditor (it's XML) and look for the following template bindings and alter them as stated: (if you're using VB.NET, change C# into VB.NET in the paths below) * binding: SD_ActionProceduresAdapterTemplate. Alter filename to: C#\actionProceduresNullableAdapter.template * binding: SD_ActionProceduresTemplate. Alter filename to: C#\actionProceduresNullable.template * binding: SD_RetrievalProceduresAdapterTemplate. Alter filename to: C#\retrievalProceduresNullableAdapter.template * binding: SD_RetrievalProceduresTemplate. Alter filename to: C#\retrievalProceduresNullable.template 4) change the contents of the <name> tag in such a way that you can recognize the template set config in the generator configuration screen in LLBLGen Pro. Save the file and exit the editor.

5) (this is for C#, for VB.NET it's the same, but go into the VB.NET folder). Go into the C# folder and copy actionProceduresAdapter.template to actionProceduresNullableAdapter.template. Copy actionProcedures.template to actionProceduresNullable.template. Copy retrievalProceduresAdapter.template to retrievalProceduresNullableAdapter.template and copy retrievalProcedures.template to retrievalProceduresNullable.template. (copies are made to keep your changes when updates are installed) 6) open actionProceduresNullableAdapter.template in a texteditor and replace each <[TypeOfParameter]> statement with object. Save the file. 7) repeat 6) for actionProceduresNullable.template, retrievalProceduresNullableAdapter.template and retrievalProceduresNullable.template. 8 ) done. You now load your project in LLBLGen Pro, hit F7 and select your newly created template set from the template set drop down box. Generate code and your stored procedure call definitions should be sporting 'object' instead of the typed parameters.

Frans Bouma | Lead developer LLBLGen Pro
IowaDave
User
Posts: 83
Joined: 02-Jun-2004
# Posted on: 12-May-2005 18:34:00   

I was able to try this and it worked fine. Thanks. simple_smile

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39615
Joined: 17-Aug-2003
# Posted on: 12-May-2005 18:56:51   

Thanks for the feedback, Dave! simple_smile

(hints for search engine: Stored procedure null parameter)

Frans Bouma | Lead developer LLBLGen Pro
G.I.
User
Posts: 172
Joined: 09-Jun-2005
# Posted on: 07-Sep-2005 11:31:37   

Otis,

I have found this solution, because I needed this also, but I have a problem with my output parameters now.

When I change everything to object, I get an error message saying:

"Object must implement IConvertible."

So I changed all the ref output parameters back to

<[TypeOfParameter]>

but this still doesn't work... Can you help me with my problem with the output parameters?

Tia,

G.I.

pilotboba
User
Posts: 434
Joined: 05-Aug-2005
# Posted on: 07-Sep-2005 19:41:08   

Otis wrote:

8 ) done. You now load your project in LLBLGen Pro, hit F7 and select your newly created template set from the template set drop down box. Generate code and your stored procedure call definitions should be sporting 'object' instead of the typed parameters.

Doesn't this eliminate the advantage of Typed SP wrappers. You are going to get more runtime errors passing in the wrong types aren't you?

You might as well just have a generic SP helper that you pass in a parameter collection too.

Two suggestions...

  1. Add a boolean type for each parameter value, not pretty but it will work.
  2. Create nullable types.

If not, at the least I would recommend that you generate Asserts for each parameter to make sure ether a null or the correct type is passed. Of course, you may be doing that already using the As operator.

BOb

G.I.
User
Posts: 172
Joined: 09-Jun-2005
# Posted on: 08-Sep-2005 07:00:47   

I am using the as operator yes, but I still get these IConvertible errors, doesn anyone know what to do with this?

Gr.,

G.I.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39615
Joined: 17-Aug-2003
# Posted on: 08-Sep-2005 10:17:06   

G.I. wrote:

I am using the as operator yes, but I still get these IConvertible errors, doesn anyone know what to do with this?

Gr.,

G.I.

Could you paste your template here? What you could do is only make the input parameters of type 'object', and the output parameters of their normal type...

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39615
Joined: 17-Aug-2003
# Posted on: 08-Sep-2005 10:19:13   

pilotboba wrote:

Otis wrote:

8 ) done. You now load your project in LLBLGen Pro, hit F7 and select your newly created template set from the template set drop down box. Generate code and your stored procedure call definitions should be sporting 'object' instead of the typed parameters.

Doesn't this eliminate the advantage of Typed SP wrappers. You are going to get more runtime errors passing in the wrong types aren't you?

Of course, but there's no other option, if a parameter is a value type, unfortunately.

You might as well just have a generic SP helper that you pass in a parameter collection too.

Two suggestions...

  1. Add a boolean type for each parameter value, not pretty but it will work.
  2. Create nullable types.

If not, at the least I would recommend that you generate Asserts for each parameter to make sure ether a null or the correct type is passed. Of course, you may be doing that already using the As operator.

If you're modifying the template to allow nullable parameters, then yes an 'as' statement is indeed a useful addition simple_smile

Frans Bouma | Lead developer LLBLGen Pro
G.I.
User
Posts: 172
Joined: 09-Jun-2005
# Posted on: 08-Sep-2005 15:24:19   

OK, i have fixed the problem with the output parameter, under here is the template for the actionstoredprocedure selfservicing.

But I still have one problem. I can't seem to give a null value where it is expecting a GUID. And since a GUID is FK with another table and not always mandatory I need to be able to pass a NULL value to this in my Stored Procedure.

I have tried to pass a null value string instead of the GUID, which is the 0000-000-00 etc. value, but then I get the message:

"Procedure 'sp_insert_PROJECT' expects parameter '@AREA_OBJ_NO', which was not supplied."

Can you help me to fix this problem?

Thank you in advance,

G.I.


///////////////////////////////////////////////////////////////
// This is generated code. If you modify this code, be aware
// of the fact that when you re-generate the code, your changes
// are lost. If you want to keep your changes, make this file read-only
// when you have finished your changes, however it is recommended that
// you inherit from this class to extend the functionality of this generated
// class or you modify / extend the templates used to generate this code.
//
// Do not try to run this code on another version of the database than the database
// which was used to generate this code. This means that when you used f.e. SqlServer 2000
// to generate this code, it is likely that you will not be able to use that code on
// SqlServer 7 due to SQL syntax mismatches. Most code is generic code which will work
// with any database, but some code relies on a specific database type/vendor/version used. 
// This code is located in the DaoClasses which target a specific specified database. Also all
// classes target a specific specified Dynamic Query Engine (DQE) in the using/imports
// directives. 
//////////////////////////////////////////////////////////////
// Code is generated using LLBLGen Pro version: <[LLBLGenVersion]>
// Code is generated on: <[Time]>
// Code is generated using templates: <[TemplateName]>
// Templates vendor: Solutions Design.
// Templates version: <[TemplateVersion]>
//////////////////////////////////////////////////////////////
using System;
using System.Data;
using System.Data.SqlClient;

using <[RootNamespace]>.HelperClasses;

using SD.LLBLGen.Pro.ORMSupportClasses;

namespace <[RootNamespace]>.StoredProcedureCallerClasses
{
    /// <summary>
    /// Class which contains the static logic to execute action stored procedures in the database.
    /// </summary>
    public class ActionProcedures
    {
        /// <summary>
        /// private CTor so no instance can be created.
        /// </summary>
        private ActionProcedures()
        {
        }

    <[Foreach ActionStoredProcedureCall CrLf]>
        /// <summary>
        /// Delegate definition for stored procedure '<[ActualStoredProcedureName]>' to be used in combination of a UnitOfWork object.
        /// </summary>
        public delegate int <[CurrentSPCallName]>CallBack(<[Foreach InputParameter Comma]><[TypeOfParameter]> <[CaseCamel CurrentParameterName]><[NextForeach]><[If HasInputAndOutputParameters]>, <[EndIf]><[Foreach OutputParameter Comma]>ref <[TypeOfParameter]> <[CaseCamel CurrentParameterName]><[NextForeach]><[If HasParameter]>, <[EndIf]> ITransaction transactionToUse);

        /// <summary>
        /// Calls stored procedure '<[ActualStoredProcedureName]>'.<br/><br/>
        /// <[Foreach CustomProperty ActionStoredProcedureCall]>
        /// <[CustomPropertyName]>: <[CustomPropertyValue]><br/><[NextForeach]>
        /// </summary>
<[If HasParameter]><[Foreach Parameter CrLf]>       /// <param name="<[CaseCamel CurrentParameterName]>"><[CurrentParameterDirection]> parameter of stored procedure</param><[NextForeach]>
<[EndIf]>       /// <returns>Amount of rows affected, if the database / routine doesn't surpress rowcounting.</returns>
        public static int <[CurrentSPCallName]>(<[Foreach InputParameter Comma]><[TypeOfParameter]> <[CaseCamel CurrentParameterName]><[NextForeach]><[If HasInputAndOutputParameters]>, <[EndIf]><[Foreach OutputParameter Comma]>ref <[TypeOfParameter]> <[CaseCamel CurrentParameterName]><[NextForeach]>)
        {
            // create parameters
            SqlParameter[] parameters = new SqlParameter[<[AmountOfParameters]>];
<[Foreach InputParameter CrLf]>         parameters[<[ParameterIndex]>] = new SqlParameter("<[ActualParameterName]>", SqlDbType.<[TypeOfActualParameter]>, <[ParameterSize]>, ParameterDirection.Input, true, <[ParameterPrecision]>, <[ParameterScale]>, "",  DataRowVersion.Current, <[CaseCamel CurrentParameterName]>);<[NextForeach]>
<[Foreach OutputParameter CrLf]>            parameters[<[ParameterIndex]>] = new SqlParameter("<[ActualParameterName]>", SqlDbType.<[TypeOfActualParameter]>, <[ParameterSize]>, ParameterDirection.InputOutput, true, <[ParameterPrecision]>, <[ParameterScale]>, "",  DataRowVersion.Current, <[CaseCamel CurrentParameterName]>);<[NextForeach]>
            // Call the stored proc.
            int toReturn = DbUtils.CallActionStoredProcedure("<[If HasMultipleCatalogs]>[<[SourceCatalogName]>].<[EndIf]>[<[SourceSchemaName]>].[<[ActualStoredProcedureName]>]", parameters, null);
<[Foreach OutputParameter CrLf]>            if(parameters[<[ParameterIndex]>].Value!=System.DBNull.Value)
            {
                <[CaseCamel CurrentParameterName]> = (<[TypeOfParameter]>)parameters[<[ParameterIndex]>].Value;
            }<[NextForeach]>
            return toReturn;
        }
        
        
        /// <summary>
        /// Calls stored procedure '<[ActualStoredProcedureName]>'.<br/><br/>
        /// <[Foreach CustomProperty ActionStoredProcedureCall]>
        /// <[CustomPropertyName]>: <[CustomPropertyValue]><br/><[NextForeach]>
        /// </summary>
<[If HasParameter]><[Foreach Parameter CrLf]>       /// <param name="<[CaseCamel CurrentParameterName]>"><[CurrentParameterDirection]> parameter of stored procedure</param><[NextForeach]>
<[EndIf]>       /// <param name="returnValue">Return value of the stored procedure</param>
        /// <returns>Amount of rows affected, if the database / routine doesn't surpress rowcounting.</returns>
        public static int <[CurrentSPCallName]>(<[Foreach InputParameter Comma]><[TypeOfParameter]> <[CaseCamel CurrentParameterName]><[NextForeach]><[If HasInputAndOutputParameters]>, <[EndIf]><[Foreach OutputParameter Comma]>ref <[TypeOfParameter]> <[CaseCamel CurrentParameterName]><[NextForeach]><[If HasParameter]>, <[EndIf]>ref System.Int32 returnValue)
        {
            // create parameters. Add 1 to make room for the return value parameter.
            SqlParameter[] parameters = new SqlParameter[<[AmountOfParameters]> + 1];
<[Foreach InputParameter CrLf]>         parameters[<[ParameterIndex]>] = new SqlParameter("<[ActualParameterName]>", SqlDbType.<[TypeOfActualParameter]>, <[ParameterSize]>, ParameterDirection.Input, true, <[ParameterPrecision]>, <[ParameterScale]>, "",  DataRowVersion.Current, <[CaseCamel CurrentParameterName]>);<[NextForeach]>
<[Foreach OutputParameter CrLf]>            parameters[<[ParameterIndex]>] = new SqlParameter("<[ActualParameterName]>", SqlDbType.<[TypeOfActualParameter]>, <[ParameterSize]>, ParameterDirection.InputOutput, true, <[ParameterPrecision]>, <[ParameterScale]>, "",  DataRowVersion.Current, <[CaseCamel CurrentParameterName]>);<[NextForeach]>
            // return value parameter
            parameters[<[AmountOfParameters]>] = new SqlParameter("RETURNVALUE", SqlDbType.Int, 0, ParameterDirection.ReturnValue, true, 10, 0, "",  DataRowVersion.Current, returnValue);

            // Call the stored proc.
            int toReturn = DbUtils.CallActionStoredProcedure("<[If HasMultipleCatalogs]>[<[SourceCatalogName]>].<[EndIf]>[<[SourceSchemaName]>].[<[ActualStoredProcedureName]>]", parameters, null);
<[Foreach OutputParameter CrLf]>            if(parameters[<[ParameterIndex]>].Value!=System.DBNull.Value)
            {
                <[CaseCamel CurrentParameterName]> = (<[TypeOfParameter]>)parameters[<[ParameterIndex]>].Value;
            }<[NextForeach]>
            returnValue = (int)parameters[<[AmountOfParameters]>].Value;
            return toReturn;
        }


        /// <summary>
        /// Calls stored procedure '<[ActualStoredProcedureName]>'.<br/><br/>
        /// <[Foreach CustomProperty ActionStoredProcedureCall]>
        /// <[CustomPropertyName]>: <[CustomPropertyValue]><br/><[NextForeach]>
        /// </summary>
<[If HasParameter]><[Foreach Parameter CrLf]>       /// <param name="<[CaseCamel CurrentParameterName]>"><[CurrentParameterDirection]> parameter of stored procedure</param><[NextForeach]>
<[EndIf]>       /// <param name="transactionToUse">the transaction to use, or null if no transaction is available.</param>
        /// <returns>Amount of rows affected, if the database / routine doesn't surpress rowcounting.</returns>
        public static int <[CurrentSPCallName]>(<[Foreach InputParameter Comma]><[TypeOfParameter]> <[CaseCamel CurrentParameterName]><[NextForeach]><[If HasInputAndOutputParameters]>, <[EndIf]><[Foreach OutputParameter Comma]>ref <[TypeOfParameter]> <[CaseCamel CurrentParameterName]><[NextForeach]><[If HasParameter]>, <[EndIf]> ITransaction transactionToUse)
        {
            // create parameters
            SqlParameter[] parameters = new SqlParameter[<[AmountOfParameters]>];
<[Foreach InputParameter CrLf]>         parameters[<[ParameterIndex]>] = new SqlParameter("<[ActualParameterName]>", SqlDbType.<[TypeOfActualParameter]>, <[ParameterSize]>, ParameterDirection.Input, true, <[ParameterPrecision]>, <[ParameterScale]>, "",  DataRowVersion.Current, <[CaseCamel CurrentParameterName]>);<[NextForeach]>
<[Foreach OutputParameter CrLf]>            parameters[<[ParameterIndex]>] = new SqlParameter("<[ActualParameterName]>", SqlDbType.<[TypeOfActualParameter]>, <[ParameterSize]>, ParameterDirection.InputOutput, true, <[ParameterPrecision]>, <[ParameterScale]>, "",  DataRowVersion.Current, <[CaseCamel CurrentParameterName]>);<[NextForeach]>
            // Call the stored proc.
            int toReturn = DbUtils.CallActionStoredProcedure("<[If HasMultipleCatalogs]>[<[SourceCatalogName]>].<[EndIf]>[<[SourceSchemaName]>].[<[ActualStoredProcedureName]>]", parameters, transactionToUse);
<[Foreach OutputParameter CrLf]>            if(parameters[<[ParameterIndex]>].Value!=System.DBNull.Value)
            {
                <[CaseCamel CurrentParameterName]> = (<[TypeOfParameter]>)parameters[<[ParameterIndex]>].Value;
            }<[NextForeach]>
            return toReturn;
        }
        
        
        /// <summary>
        /// Calls stored procedure '<[ActualStoredProcedureName]>'.<br/><br/>
        /// <[Foreach CustomProperty ActionStoredProcedureCall]>
        /// <[CustomPropertyName]>: <[CustomPropertyValue]><br/><[NextForeach]>
        /// </summary>
<[If HasParameter]><[Foreach Parameter CrLf]>       /// <param name="<[CaseCamel CurrentParameterName]>"><[CurrentParameterDirection]> parameter of stored procedure</param><[NextForeach]>
<[EndIf]>       /// <param name="returnValue">Return value of the stored procedure</param>
        /// <param name="transactionToUse">the transaction to use, or null if no transaction is available.</param>
        /// <returns>Amount of rows affected, if the database / routine doesn't surpress rowcounting.</returns>
        public static int <[CurrentSPCallName]>(<[Foreach InputParameter Comma]><[TypeOfParameter]> <[CaseCamel CurrentParameterName]><[NextForeach]><[If HasInputAndOutputParameters]>, <[EndIf]><[Foreach OutputParameter Comma]>ref <[TypeOfParameter]> <[CaseCamel CurrentParameterName]><[NextForeach]><[If HasParameter]>, <[EndIf]>ref System.Int32 returnValue, ITransaction transactionToUse)
        {
            // create parameters. Add 1 to make room for the return value parameter.
            SqlParameter[] parameters = new SqlParameter[<[AmountOfParameters]> + 1];
<[Foreach InputParameter CrLf]>         parameters[<[ParameterIndex]>] = new SqlParameter("<[ActualParameterName]>", SqlDbType.<[TypeOfActualParameter]>, <[ParameterSize]>, ParameterDirection.Input, true, <[ParameterPrecision]>, <[ParameterScale]>, "",  DataRowVersion.Current, <[CaseCamel CurrentParameterName]>);<[NextForeach]>
<[Foreach OutputParameter CrLf]>            parameters[<[ParameterIndex]>] = new SqlParameter("<[ActualParameterName]>", SqlDbType.<[TypeOfActualParameter]>, <[ParameterSize]>, ParameterDirection.InputOutput, true, <[ParameterPrecision]>, <[ParameterScale]>, "",  DataRowVersion.Current, <[CaseCamel CurrentParameterName]>);<[NextForeach]>
            // return value parameter
            parameters[<[AmountOfParameters]>] = new SqlParameter("RETURNVALUE", SqlDbType.Int, 0, ParameterDirection.ReturnValue, true, 10, 0, "",  DataRowVersion.Current, returnValue);

            // Call the stored proc.
            int toReturn = DbUtils.CallActionStoredProcedure("<[If HasMultipleCatalogs]>[<[SourceCatalogName]>].<[EndIf]>[<[SourceSchemaName]>].[<[ActualStoredProcedureName]>]", parameters, transactionToUse);
<[Foreach OutputParameter CrLf]>            if(parameters[<[ParameterIndex]>].Value!=System.DBNull.Value)
            {
                <[CaseCamel CurrentParameterName]> = (<[TypeOfParameter]>)parameters[<[ParameterIndex]>].Value;
            }<[NextForeach]>
            returnValue = (int)parameters[<[AmountOfParameters]>].Value;
            return toReturn;
        }
    <[NextForeach]>
    
        #region Included Code
<# Custom_ActionProceduresTemplate #>
        #endregion
    }
}

G.I.
User
Posts: 172
Joined: 09-Jun-2005
# Posted on: 08-Sep-2005 15:44:38   

Ok, I figured one way out how to do it, and that is by checking if the Guid is Guid.Empty, if so I call the actionstoredprocedure function with passing a DBNull.Value, if it's not empty I can call it with the Guid itself.

The only thing now is, that when I have a SP with 4 Guid, I have to write MANY different function calls depending on the Guid values.

with 4 Guids it's already 12 123*4 = 24 different situations, and that is a little bit unneccessary I think.

Does anyone know some way around this?

Gr.,

G.I.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39615
Joined: 17-Aug-2003
# Posted on: 08-Sep-2005 22:09:50   

You can always define the parameter as object and first check if for null, if so, set the parameter to DBNull.Value, otherwise set it to the casted value. That should work for GUID's as well as it does for ints.

Maybe I don't understand the problem in full, I'll look at it more closely tomorrow.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39615
Joined: 17-Aug-2003
# Posted on: 09-Sep-2005 10:55:47   

I might be missing something, but your template doesn't use 'object' for input parameter types in the method signatures, so it's effectively the same as the original, or am I missing something? confused

Frans Bouma | Lead developer LLBLGen Pro