HOW TO: Implement Helper Class to use a SqlParameter with a list of values in a IN clause

Article translations Article translations
Article ID: 555167 - View products that this article applies to.
Author: William Ryan MVP
Expand all | Collapse all

SYMPTOMS

You are trying to pass in multiple values separated by a delimiter, to a SQL Statement or Stored Procedure which will use the list in  a IN Statement and you are passing in this list as a single SqlParameter.

CAUSE

This is actually caused by a feature of ADO.NET that makes Parameters beneficial to use in the first place - they escape the values inserted in to them and pass them in as one literal.  So if you set @SomeValue = 1,2,3,4,5  and used it in SELECT * FROM [TableName] WHERE [ColumnName] IN @SomeValue, you would not match values in [ColumnName] that were equal to 1,2,3,4 or 5.  Instead you'd only match values that were exactly "1,2,3,4,5".  So if this is our procedure:
<CODE>
CREATE PROCEDURE up_ProcedureHelper
     @Tokens AS VARCHAR(50)--or whatever length you'll need
AS
SELECT * FROM [TableName]
WHERE [FieldName] IN (@Tokens)
</CODE>
This code will fail:
<CODE>
string cs = ConfigurationSettings.AppSettings("ConnectString"));
using(SqlConnection cn = new SqlConnection(cs)){
      SqlCommand cmd = new SqlCommand("up_ProcedureHelper", ConnectionName);
      cmd.CommandType = CommandType.StoredProcedure;
      cmd.Parameters.Add("@Token", "1,2,3,4,5);
      cn.Open();//In reality Connection.Open and ExecuteReader should be wrapped in Try/Catch/Finally
      dr = cmd.ExecuteReader(CommandBehavior.CloseConnection);    
      Debug.Assert(dr.HasRows, "We should have Rows but Don't"); //Fails because ColumnName is an Integer field and won't hold "1,2,3,4,5"

}</CODE>

RESOLUTION

Change your Stored Procedure  to Accommodate new Methodology
  One easy way to solve this problem is to create a Temporary table using Sql Server 2000's Temporary table feature.  We'll use a simple loop to parse through the variable we've passed in that's separated by a given delimiter.  At each pass through the loop, we'll insert each variable into the temporary table.  Finally, we'll use a subquery to match the values in the LIKE clause:
<CODE>
CREATE PROCEDURE up_ProcedureHelper
     @Tokens AS VARCHAR(50)--or whatever length you'll need
AS
DECLARE @Temp  AS VARCHAR(50)
CREATE TABLE #HOLDER(TokenValues VARCHAR(50))
WHILE LEN(@Tokens) > 0
    BEGIN
       IF CHARINDEX(',', @Tokens) =0
          BEGIN
             SET @TEMP = @Tokens  
             SET @Tokens = ''
             INSERT INTO #Holder (TokenValues) VALUES(@Temp)
          END
       ELSE
          BEGIN
             SET @Temp = LEFT(@Tokens, CHARINDEX(',', @Tokens)-1)     
             INSERT INTO #Holder (TokenValues) VALUES(@Temp)
             SET @Tokens = RIGHT(@Tokens, LEN(@Tokens)-LEN(@Temp)-1)
       END
    END
SELECT * FROM [TableName]
WHERE [FieldName] IN (SELECT TokenValues FROM #Holder)
</CODE>
 
So, assuming the parameter "1,2,3,4,5", we'll have a Temp table named #Holder with 5 rows and the values 1,2,3,4 & 5 respectively.  This table will exist as long as the connection that it was created under is live.  As soon as we close it the table will be removed automatically.  In ordinary circumstances it is absolutely imperative that you remember to close your connections.  You do not want to take any chances here and hope that the connection gets closed, so use a finallyblock in your procedures and call the .Close() method from there.   If you are using C# , you can wrap your block in a using statement in conjunction with your finally block and ensure that the SqlConnectionis closed as well as disposed of (the same principle applies with all other connection types).
<CODE>
string cs = ConfigurationSettings.AppSettings("ConnectString"));
using(SqlConnection cn = new SqlConnection(cs)){
      SqlCommand cmd = new SqlCommand("up_ProcedureHelper", ConnectionName);
      cmd.CommandType = CommandType.StoredProcedure;
      cmd.Parameters.Add("@Token", "1,2,3,4,5");
      cn.Open();//In reality Connection.Open and ExecuteReader should be wrapped in Try/Catch/Finally
      dr = cmd.ExecuteReader(CommandBehavior.CloseConnection);    
      Debug.Assert(dr.HasRows, "We should have Rows but Don't"); //Now the assertion succeeds - and we have 5 rows.
}
</CODE>

MORE INFORMATION

Calling a parameterized stored procedure in SQL Server
A good discussion of Dynamic Sql and when it's a good solution
A good discussion on why to Parameterize  queries
How to programmatically convert Dynamic SQL Statements to Parameterized queries

Properties

Article ID: 555167 - Last Review: July 24, 2004 - Revision: 1.0
APPLIES TO
  • Microsoft ADO.NET 1.1
  • Microsoft ADO.NET 1.0
Keywords: 
kbpubtypecca kbpubmvp kbhowto KB555167
COMMUNITY SOLUTIONS CONTENT DISCLAIMER
MICROSOFT CORPORATION AND/OR ITS RESPECTIVE SUPPLIERS MAKE NO REPRESENTATIONS ABOUT THE SUITABILITY, RELIABILITY, OR ACCURACY OF THE INFORMATION AND RELATED GRAPHICS CONTAINED HEREIN. ALL SUCH INFORMATION AND RELATED GRAPHICS ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. MICROSOFT AND/OR ITS RESPECTIVE SUPPLIERS HEREBY DISCLAIM ALL WARRANTIES AND CONDITIONS WITH REGARD TO THIS INFORMATION AND RELATED GRAPHICS, INCLUDING ALL IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, WORKMANLIKE EFFORT, TITLE AND NON-INFRINGEMENT. YOU SPECIFICALLY AGREE THAT IN NO EVENT SHALL MICROSOFT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, INDIRECT, PUNITIVE, INCIDENTAL, SPECIAL, CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF USE, DATA OR PROFITS, ARISING OUT OF OR IN ANY WAY CONNECTED WITH THE USE OF OR INABILITY TO USE THE INFORMATION AND RELATED GRAPHICS CONTAINED HEREIN, WHETHER BASED ON CONTRACT, TORT, NEGLIGENCE, STRICT LIABILITY OR OTHERWISE, EVEN IF MICROSOFT OR ANY OF ITS SUPPLIERS HAS BEEN ADVISED OF THE POSSIBILITY OF DAMAGES.

Give Feedback

 

Contact us for more help

Contact us for more help
Connect with Answer Desk for expert help.
Get more support from smallbusiness.support.microsoft.com