How to use parameters in Visual Studio 2022 with an Access Database - vb.net

I have created a database in Access 2019
I have created a basic form to display the data from the above table
I would like to filter the data to show only certain countries – like below
The where clause is hard code and so my question is how can I dynamically change the filter clause say from ‘Aus’ to ‘UK’.
a) I have tried using a parameter ‘CountryName’ as see in the Fill, GetData (CountryName), but I am unable to use the parameter in the Query Builder. How can this be done if possible?
b) Is there a way to change the Fill Query Property (CommandText) by code as I am unable to see the correct properties to use – see below

It sounds like you are creating a typed DataSet. In that case, just leave the default query as it is for each table adapter. You can then call Fill or GetData on a table adapter to get all the data in the corresponding table. If you want to be able to filter the data, add a new query with method names that reflect the filter, e.g. if you want to filter by the CountryName column then name the methods FillByCountryName and GetDataByCountryName. In the Query Builder, you have to use ? as parameter placeholders rather than names like #CountryName, e.g.
SELECT * FROM MyTable WHERE CountryName = ?
In code, you would then do something like this:
Dim countryName = "UK"
myTableAdapter.FillByCountryName(myDataSet.MyTable, countryName)

You can try this:
DECLARE #CountryName AS NVARCHAR(50) = 'uk'
SELECT CountryName FROM MyTable
WHERE CountryName = #CountryName
How this would translate to code:
Public Sub GetCountry()
DefaultCatalog = "MyTable"
Dim selectStatement = "SELECT CountyrName FROM MyTable WHERE CountyrName = #CountryName"
Using cn As New SqlConnection With {.ConnectionString = ConnectionString}
Using cmd As New SqlCommand With {.Connection = cn, .CommandText = selectStatement}
cmd.Parameters.AddWithValue("#CountryName", "uk")
cn.Open()
Dim reader = cmd.ExecuteReader()
If reader.HasRows Then
reader.Read()
Console.WriteLine(reader.GetString(0))
End If
End Using
End Using
End Sub

Related

VB.NET - Best practice for handling complex SQL queries with optional parameters

In my application, the user should have the option to browse for orders that have been placed by customers - and it should be possible to apply several filters to the search. That means that I need a dynamic SQL query where a variable amount of parameters can be applied.
In a standard WinForms application, what would be the best way to handle this?
So far I've been working with TableAdapters and stored procedures but as far as I know, I can not use these with optional parameters. So for example if a user wants to see all customer orders, this is no problem. But it should also be possible to say for example "Show all orders that have been placed in the last 2 weeks and where at least one product contains the word 'gift code'". So date and product-name would be optional parameters but if I leave those empty in a stored procedure, I get an error.
To fix this, I started building my own queries in a separate class using SqlCommands and parameters. I dynamically generate the commandText for each command depending on the parameters passed into the function, then I add parameters to the SqlCommand, execute it and loop through the SqlDataReader to build a list of items that I will return to my program.
For example (simplified):
Dim cmd As New SqlCommand With {.Connection = con}
cmd.commandText = "SELECT o.id, o.customer_name, o.date, p.productName FROM orders o JOIN order_positions p ON o.id = p.order_id WHERE o.date >= #pDate"
cmd.Parameters.Add("#pDate", SqlDbType.DateTime).Value = searchDate
Dim reader As SqlDataReader = cmd.ExecuteReader()
Dim lstOrderItems As New List(Of OrderDisplayItem)
while reader.read
dim orderId as Integer = reader.Item(0)
dim customerName as String = reader.Item(1)
dim date as Date = reader.Item(2)
dim productName as String = reader.Item(3)
lstOrderItems.add(New OrderDisplayItem With{.id = orderId, .customerName = customerName, .date = date, .productName = productName})
End While
return lstOrderItems
Now obviously this is just to show how I proceed. In reality, I have to create additional loops because one order might contain one or multiple products etc.
My question would be: is this the right way to handle this? It feels like this whole class will grow really big because I have other queries too like looking up invoices, store sales and so on - and for every query I have to write these reader loops which I would have to modify all over again if a slight thing in my database changes.
Is it really not possible to handle this within Visual Studio tableAdapters?
That means that I need a dynamic SQL query where a variable amount of parameters can be applied.
No it doesn't. You can use a single query with a single set of parameters and simply provide NULL for those parameters you want to ignore if you structure your SQL like this:
SELECT *
FROM MyTable
WHERE (#Column1 IS NULL OR Column1 = #Column1)
AND (#Column2 IS NULL OR Column2 = #Column2)
Your VB code might then look something like this:
Using connection As New SqlConnection("connection string here"),
command As New SqlCommand(query, connection)
command.Parameters.Add("#Column1", SqlDbType.VarChar, 50).Value = If(TextBox1.TextLength = 0, CObj(DBNull.Value), TextBox1.Text)
command.Parameters.Add("#Column2", SqlDbType.VarChar, 50).Value = If(TextBox2.TextLength = 0, CObj(DBNull.Value), TextBox2.Text)
'...
End Using
When you provide NULL for a parameter, that effectively matches every record and that parameter is effectively ignored. You can do that with as many parameters as you like of whatever data type that you like.

How do I retrieve a value from an SQL query and store it in a variable in VB.NET?

I am trying to find the max product ID and store the value in a local variable "MaxID" and return this value. I am trying to convert the result of the query into an Integer type but I am not able to do it. Below is the code:
Public Function GetMaxID(ByVal TableName As String, ByVal ID As String) As Integer
Dim MaxID As Integer
Dim sqlquery As SqlCommand
Dim field_name As String = ID
Dim con As SqlConnection
con = New SqlConnection()
con.ConnectionString = "Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename='D:\Docs Dump\Work\Srinath\SrinathDB.mdf';Integrated Security=True;Connect Timeout=30"
con.Open()
Try
sqlquery = New SqlCommand("SELECT MAX( #field ) FROM #table ", con)
sqlquery.Parameters.AddWithValue("#field", field_name)
sqlquery.Parameters.AddWithValue("#table", TableName)
MaxID = CInt(sqlquery.ToString)
con.Close()
Return MaxID
Catch ex As Exception
Return 0
Exit Function
con.Close()
End Try
End Function
End Class
MaxID = CInt(sqlquery.ExecuteScalar())
You also should know about SqlCommand.ExecuteReader(), SqlCommand.ExecuteNonQuery() (for inserts/updates/deletes), and SqlDataAdapter.Fill().
Where you'll still have a problem is you can't use a parameter value for the table name or column name. The Sql Server engine has a "compile" step, where it has to be able to work out an execution plan, including permissions/security, at the beginning of the query, but variable names like #table and #field aren't resolved until later. It's not what actually happens, but think of it as if you had string literals in those places; imagine trying to run this:
SELECT MAX('ID') FROM 'MyTable'
MAX('ID') will always return the string value ID, and not anything from an ID column in any rows. But the MyTable part is not the correct place for a string literal, and such a query wouldn't even compile.
I also see people here from time to time try to create functions like GetMaxId(), and it's almost always misguided in the first place. If the intended use for this function is the same as what I usually see, you're setting up a major race condition issue in your application (one that probably won't show up in any testing, too). Sql Server gives you features like identity columns, sequences, and the scope_identity() function. You should be using those in such a way that new IDs are resolved on the server as they are created, and only (and immediately) then returned to your application code.
But that issue aside, here's a better way to structure this function:
Public Class DB
Private conString As String = "Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename='D:\Docs Dump\Work\Srinath\SrinathDB.mdf';Integrated Security=True;Connect Timeout=30"
'You want a separate method per-table that already knows the table and column names
Public Function GetMyTableMaxID() As Integer
Dim sql As String = "SELECT MAX(ID) FROM MyTable"
Using con As New SqlConnection(conString), _
sqlQuery As New SqlCommand(sql, con)
'Parameters would go here.
'Do NOT use AddWithValue()! It creates performance issues.
' Instead, use an Add() overload where you provide specific type information.
'No exception handling at this level. The UI or business layers are more equipped to deal with them
con.Open()
Return CInt(sqlQuery.ExecuteScalar())
End Using
'No need to call con.Close()
'It was completely missed in the old code, but handled by the Using block here
End Function
End Class

String.Format vs Parameter Values SQL Query

I'm trying to figure out if there is a better way to do this
Dim cmd As New SqlCommand
Dim sel As String
Dim obj As New DataHandler
sel = String.Format("SELECT * FROM Customers WHERE Country LIKE '{0}%'", txt_Input.Text)
cmd.CommandText = sel
Me.dgv_Customers.DataSource = obj.SqlDataRetriever(cmd)
Basically what im trying to do is have a textbox that whenever I type a letter, the grid refreshes itself by sending a Query to my SQL server searching for whatever its in the textbox using the LIKE() from SQL. I've been reading about SQL injection and so far everyone suggests to use parameter values (#value) for user input, but if I try to replace the {0} with that it doesn't work. I just wanna make sure that this is a valid way of doing this.
Thanks
Instead just concatenate the string like below. You should consider using parameterized query to avoid SQL Injection.
sel = "SELECT * FROM Customers WHERE Country LIKE '" + txt_Input.Text + "%'";
Use a parameterized query rather. See This Post
Dim cmd as New SqlCommand("SELECT * FROM Customers WHERE Country LIKE #param")
cmd.Parameters.Add("#param", txt_Input.Text +"%")

Pass parameter to a query from another query in Access

I have a parameterized query GET_CUSTOMER:
SELECT * FROM Customer WHERE id = [customer_id]
I want to call this query from another query and pass it a parameter:
SELECT * FROM GET_CUSTOMER(123)
Note the above code is not valid, it is here to give you an idea of what I'm trying to do. Is it possible to do this in MS Access?
UPDATE 1:
The queries I posted are for example. The actual queries are much more complex. I know I can use table joins, but in my specific case it would be much easier if I could run parameterized queries inside other queries (that are parameterized as well). I can't use access forms because I'm using access with my .NET application.
This is how I end up solving this with help of https://stackoverflow.com/a/24677391/303463 . It turned out that Access shares parameters among all queries so there is no need to specifically pass parameters from one query to another.
Query1:
SELECT * FROM Customer WHERE ID > [param1] AND ID < [param2]
Query2:
SELECT * FROM Query1
VB.NET code:
Dim ConnString As String = "Provider=Microsoft.Jet.OleDb.4.0;Data Source=Database.mdb"
Dim SqlString As String = "Query2"
Using Conn As New OleDbConnection(ConnString)
Using Cmd As New OleDbCommand(SqlString, Conn)
Cmd.CommandType = CommandType.StoredProcedure
Cmd.Parameters.AddWithValue("param1", "1")
Cmd.Parameters.AddWithValue("param2", "3")
Conn.Open()
Using reader As OleDbDataReader = Cmd.ExecuteReader()
While reader.Read()
Console.WriteLine(reader("ID"))
End While
End Using
End Using
End Using
You can build the SQL on the fly.
MyID = prompt or get from user some ID
strSQl = "Select * from tblCustomer where ID in " & _
"(select * from tblTestCustomers where id = " & MyID
So you can nest, or use the source of one query to feed a list of ID to the second query.

VB.NET Multiple Selects at once using SQL Server CE

I have an array list which contains ids for some items. I would like to perform a multiple select at once from a SQL Server CE database and using my array list which contains what items id to be selected, something similar when doing for example multiple update in oracle (ODP.NET) as explained here: Oracle bulk updates using ODP.NET
where you can pass an array as a parameter.
I would like to do the same but for a multiple select instead in case of SQL Server CE. Is it possible?
DRAFT about what I would like to do:
SqlCeCommand = SqlCeConnection.CreateCommand()
SqlCeCommand.CommandText = "SELECT * FROM MyTable WHERE Id=:ids"
SqlCeCommand.CommandType = CommandType.Text
SqlCeCommand.Parameters.Add(":ids", DbType.Int32, ArrayListOfIds, ParameterDirection.Input)
Using reader As System.Data.SqlServerCe.SqlCeDataReader = SqlCeCommand.ExecuteReader()
Using targetDb As Oracle.DataAccess.Client.OracleBulkCopy = New Oracle.DataAccess.Client.OracleBulkCopy(con.ConnectionString)
targetDb.DestinationTableName = "MyTable"
targetDb.BatchSize = 100
targetDb.NotifyAfter = 100
targetDb.BulkCopyOptions = Oracle.DataAccess.Client.OracleBulkCopyOptions.UseInternalTransaction
AddHandler targetDb.OracleRowsCopied, AddressOf OnOracleRowsCopied targetDb.WriteToServer(reader)
targetDb.Close()
End Using
reader.Close()
End Using
You should try this approach by constructing your "IN" clause and adding each parameter in a for each loop:
SqlCeCommand = SqlCeConnection.CreateCommand()
SqlCeCommand.CommandType = CommandType.Text
Dim sb As New StringBuilder()
Dim i As Integer = 1
For Each id As Integer In ArrayListOfIds
' IN clause
sb.Append("#Id" & i.ToString() & ",")
' parameter
SqlCeCommand.Parameters.Add("#Id" & i.ToString(), DbType.Int32, id, ParameterDirection.Input)
i += 1
Next
If you're calling a Stored Procedure, you can do this:
Serialize the array to a string of XML, like this: https://stackoverflow.com/a/6937351/734914
Call the stored procedure, passing in the string parameter
Parse the string of XML into a local table variable containing the ID's, like this: https://stackoverflow.com/a/8046830/734914
Execute whatever queries you need to using the ID's
The links that I referenced might not be the best examples on the web, but the concept of "serialize to XML, pass string parameter, deserialize XML" should work here