다음을 통해 공유


VB.NET Windows Forms Create SQL WHERE IN from delimited values with custom controls

Introduction

A fundamental operation in data-centric Window Forms application is filtering and searching for data. This article focuses on working with SQL-Server WHERE IN for a SELECT statement to return a container, in this case, the container is a DataTable of data where the user enters a delimited string of values to search a specific field in a custom TextBox control located in a class library.

 Microsoft Access database uses the same syntax for a WHERE IN (see documentation). In the code for the TextBox, there are several places where casting is performed which would not be compatible with Microsoft Access but there are suitable replacement functions.

SELECT * 
FROM Orders 
WHERE ShipRegion In ('Avon','Glos','Som')

TextBoxWhere custom TextBox

This TextBox inherits from the standard Windows Form TextBox control with added functionality to create a SQL WHERE IN clause using ColumnName property (string) to specify which column to use for the WHERE IN with the property DataType (Enum) to specify the data type of the field in the ColumnName property. The SelectStatement property (string) is for the SELECT statement excluding the WHERE clause which the TextBoxWhere control creates.

SQL Basics

There are literally countless scenarios for performing a WHERE IN from querying a single table to many tables using joins while the base concept to perform a WHERE IN remains the same, the difference is proper aliasing tables. 

User Interface Basics

Performing a WHERE IN from a user interface may be done with pre-defined base SELECT statements to providing a user interface which allows users to construct a SELECT statement from ComboBox, ListBox, and similar controls to piece together a SELECT statement which is out of the scope of this article. In this article each SELECT statement is pre-defined.

Below are the SELECT statements used in the sample code.

Private ReadOnly  _selectStatementForStrings As String  =
            <SQL>
                SELECT 
                    supplier_id,
                    supplier_name,
                    city,
                    [state] 
                FROM WhereInSimple
            </SQL>.Value
 
Private ReadOnly  _selectStatementForDateTime As String  =
            <SQL>
                SELECT 
                    supplier_id,
                    supplier_name, 
                    CAST(some_Date AS DATE) AS Registered 
                FROM WhereInSimple
            </SQL>.Value

 The above syntax is much better to create an SQL statement than using string concatenation. Create the statement in either SSMS (SQL-Server Management Studio) or create a new SQL statement from Visual Studio Server Explorer, ensure the statement executes then insert into a XML Literals (C# does not have this) as done above. 

Embedded expressions may also be used in tangent with XML Literals as shown below. Caveat, generally in cases such as the one below pTableName (string) should be checked for spaces and apostrophes that are properly escaped with the appropriate escape token e.g. Order Details would be [Order Details]. 
Figure 1

Dim selectStatement =
        <SQL>
            SELECT COLUMN_NAME 
            FROM INFORMATION_SCHEMA.COLUMNS
            WHERE TABLE_NAME = '<%= pTableName %>'
        </SQL>.Value

Using the TextBox

Working from the following code sample,

  • Provides values for the WHERE IN clause by setting the Text property while in an application the user would enter these values with a separator between each value.
  • Provide a valid SELECT statement without a WHERE clause, this will be added by the TextBox function CreateWhereStatement.
  • Select the column for the WHERE IN clause. Note there is code in DataOperations.vb to get a list of column names in ColumnNames method and to compliment this method there is TableNames method to get table names for the desired table.
  • Set the separator (char) if a comma is not acceptable as a comma is the default separator.
  • Call CreateWhereStatement() method to create the WHERE clause.
  • To validate the SELECT statement is valid to use IsValid property of the TextBox.
  • If the statement is valid to pass the statement to a method to query the database (see figure 3 for an example from this article's source code).

Figure 2

Private Sub  ValidButton1_Click(sender As Object, e As  EventArgs) Handles  ValidButton1.Click
 
    TextBoxWhere1.Text = "Google,Kimberly-Clark,Tyson Foods"
    TextBoxWhere1.SelectStatement = _selectStatementForStrings
    TextBoxWhere1.DataType = DataTypes.String
    TextBoxWhere1.Separator = ","c
    TextBoxWhere1.ColumnName = "supplier_name"
 
    TextBoxWhere1.CreateWhereStatement()
 
    If TextBoxWhere1.IsValid Then
 
        DataGridView1.DataSource = _dataOperations.PopulateDataDataGridView(TextBoxWhere1.Statement)
 
        _dataOperations.TableNames()
 
        If Not  _dataOperations.IsSuccessFul Then
            MessageBox.Show(_dataOperations.LastExceptionMessage)
        End If
 
    Else
        DataGridView1.DataSource = Nothing
    End If
 
End Sub

Figure 3
This method uses the SQL SELECT statement generated by the custom TextBox to return a DataTable to the caller. A DataTable is one option while other options may be to create a List(Of T).

 has exception, mLastException, and Connection string are part of a NuGet package BaseConnectionLibrary, the full source is available at the following repository.

Public Function  PopulateDataDataGridView(pSelectStatement As String) As  DataTable
    mHasException = False
 
    Dim dt As New  DataTable
 
    Using cn As  New SqlClient.SqlConnection With {.ConnectionString = ConnectionString}
 
        Using cmd As  New SqlClient.SqlCommand With {.Connection = cn}
            cmd.CommandText = pSelectStatement
            cn.Open()
            Try
                dt.Load(cmd.ExecuteReader)
            Catch ex As Exception
                mHasException = True
                mLastException = ex
            End Try
        End Using
    End Using
 
    Return dt
 
End Function

Predefined code samples

In the Visual Studio solution, within the form project several scenarios are setup. The TextBox is the custom TextBox where the buttons below feed off the TextBox to create valid and invalid SQL statements for strings, dates and numerics.  By running the form project and reviewing the underlying code will allow ease of learning how to use this TextBox in your project.

Using in your project

Copy the class project FormsControl into a Visual Studio solution followed by adding a reference to FormsControl class project to a form project. Build the Visual Studio solution followed by opening a form to use the TextBox which will be displayed at the top of the IDE ToolBox.

Alternatives

There are many other ways to perform a WHERE IN for a SELECT statement where in some cases not much different than the custom TextBox presented here. One example is to use a custom control, specifically, a custom ComboBox with a dropdown of checked items as many times providing searching and filtering can take up a good deal of space on a form. If space is not limited a CheckedListBox would be a good choice other a custom ComboBox..

The first part needed is code to dynamically create a WHERE IN without regards to the type of values but instead be generic as done in BuildWhereInClause and AddParamsToCommand.

The following code module resides in the same class project as the custom TextBox which performs WHERE IN. The code within this code module can be seen either as very simple or complex depending on the knowledge of the developer. If changes are desired they always can be reverted by copying the original from the GitHub repository.

To use the custom ComboBox, install via NuGet Package Manager console.

Install-Package Common.CheckComboBox -Version 1.0.0

Once installed the ComboBox will appear in the IDE Toolbox, place one on a form as with any standard control. Next populate the Items, for example, here the Items are populated with a list of contact types read from a SQL-Server table.

Source code is taken from the GitHub repository for this article

''' <summary>
''' Get a list of all contact types
''' </summary>
''' <returns></returns>
Public Function  ContactTypeList() As  List(Of ContactType)
    mHasException = False
 
    Dim contactTypes As New  List(Of ContactType)
 
    Using cn As  New SqlClient.SqlConnection With {.ConnectionString = ConnectionString}
        Using cmd As  New SqlClient.SqlCommand With {.Connection = cn}
            cmd.CommandText = "SELECT ContactTypeIdentifier, ContactTitle FROM dbo.ContactType"
 
            Try
 
                cn.Open()
                Dim reader = cmd.ExecuteReader()
 
                While reader.Read()
                    contactTypes.Add(New ContactType() With
                                        {
                                            .ContactTypeIdentifier = reader.GetInt32(0),
                                            .ContactTitle = reader.GetString(1)
                                        })
                End While
 
            Catch ex As Exception
                mHasException = True
                mLastException = ex
            End Try
        End Using
    End Using
 
    Return contactTypes
 
End Function

Set the Items in Form load or shown event.

ContactTypesCheckedComboBox1.Items.AddRange(
    _dataOperations.ContactTypeList().Select(Function(data) data.ContactTitle).ToArray())

The following method accepts a list of string representing contact types obtained from the custom ComboBox

''' <summary>
''' Get data 
''' </summary>
''' <param name="pContactTitleList">List of contact titles</param>
''' <returns>Populated DataTable or empty DataTable for runtime error</returns>
Public Function  ReadCustomersByContactType(pContactTitleList As List(Of String)) As  DataTable
 
    mHasException = False
 
    ' field which the WHERE IN will use
    Dim parameterPrefix = "CT.ContactTitle"
 
    ' Base SELECT Statement
    Dim selectStatement =
            <SQL>
            SELECT C.CustomerIdentifier ,
                   C.CompanyName ,
                   C.ContactName ,
                   C.ContactTypeIdentifier ,
                   FORMAT(C.ModifiedDate, 'MM-dd-yyyy', 'en-US') AS ModifiedDate,
                   CT.ContactTitle
            FROM   dbo.Customers AS C
            INNER JOIN dbo.ContactType AS CT ON C.ContactTypeIdentifier = CT.ContactTypeIdentifier
            WHERE <%= parameterPrefix %> IN ({0}) 
            ORDER BY C.CompanyName
            </SQL>.Value
 
    ' Builds the SELECT statement minus values
    Dim CommandText = BuildWhereInClause(selectStatement, parameterPrefix, pContactTitleList)
 
    Dim dt As New  DataTable
 
    Using cn As  New SqlClient.SqlConnection With {.ConnectionString = ConnectionString}
 
        Using cmd As  New SqlClient.SqlCommand With {.Connection = cn}
 
            cmd.CommandText = CommandText
 
            '
            ' Add values for command parameters
            '
            cmd.AddParamsToCommand(parameterPrefix, pContactTitleList)
 
            Try
                cn.Open()
 
                dt.Load(cmd.ExecuteReader)
                dt.Columns("ContactTypeIdentifier").ColumnMapping = MappingType.Hidden
 
            Catch ex As Exception
                mHasException = True
                mLastException = ex
            End Try
 
        End Using
    End Using
 
    Return dt
 
End Function

To call the above method in a Button click event.

Private Sub  ReadFromDatabaseButton_Click(sender As Object, e As  EventArgs) Handles  ReadFromDatabaseButton.Click
    BindingSource.DataSource = Nothing
 
    If Not  String.IsNullOrWhiteSpace(ContactTypesCheckedComboBox1.Text) Then
 
        Dim contactTypes = ContactTypesCheckedComboBox1.CheckedItems.Cast(Of String).ToList()
        BindingSource.DataSource = _dataOperations.ReadCustomersByContactType(contactTypes)
 
        If Not  _dataOperations.IsSuccessFul Then
            MessageBox.Show(_dataOperations.LastExceptionMessage)
        End If
 
    Else
        MessageBox.Show("Must select at least one contact type")
    End If
 
End Sub

Example using the above

Summary

This article has presented two ways to create SQL SELECT WHERE IN statements using a custom TextBox and a custom ComboBox to use in Visual Studio, VB.NET Windows Forms solutions. The TextBox resides in a class project ready to use in any Windows Forms project while the ComboBox requires a NuGet package install. Within these code samples also presented is a library to assist with creating connections to databases and a uniform method to see if any runtime exceptions are thrown along with methods to check for runtime errors and return errors via an Exception object or simply an exception message.

See also

Writing SQL for your application
.NET: Defensive data programming
Working with parameterized SQL operations 
SQL-Server dynamic C#: Dynamic WHERE IN conditions in C#for SQL Server 

Source code

Source code resides in the following GitHub repository created with Visual Studio 2015 and used in a Visual Studio 2017 class project.