Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed last month.
The community reviewed whether to reopen this question last month and left it closed:
Original close reason(s) were not resolved
Improve this question
Can some help me give some sample code how to call this method?
Public Shared Function ReadObjects(Of T)(ByVal query As String, ByVal func As Func(Of IDataReader, T)) As IEnumerable(Of T)
Dim outbound As New List(Of T)
Using connection As OleDbConnection = NewConnection()
connection.Open()
Using command As OleDbCommand = New OleDbCommand(query, connection)
command.CommandType = CommandType.Text
command.CommandTimeout = 0
Dim reader As OleDbDataReader = command.ExecuteReader
While reader.Read()
outbound.Add(func.Invoke(reader))
End While
End Using
connection.Close()
End Using
Return outbound
End Function
Had no idea how to go about this one :)
First of all, I would write the function like this:
Public Shared Iterator Function ReadObjects(Of T)(query As String, transform As Func(Of IDataRecord, T), parameters As Action(Of OleDbParameterCollection)) As IEnumerable(Of T)
Using connection As OleDbConnection = NewConnection()
Using command As OleDbCommand = New OleDbCommand(query, connection)
If parameters IsNot Nothing Then parameters(command.Parameters)
command.CommandTimeout = 0
connection.Open()
Dim reader As OleDbDataReader = command.ExecuteReader()
While reader.Read()
Yield transform(reader)
End While
End Using
End Using
End Function
There are several changes here, but the most important is the ability to accept query parameters separate from the command string. Otherwise, the prior code would have forced you put HUGE GAPING WIDE SECURITY VULNERABILITIES into your application.
That out of the way, it sounds like you're asking how to call the method, and it's likely the Func(Of IDataRecord, T) argument is the confusing part... and now also the Action(Of OleDbParameterCollection).
Here's how to do it.
First, let's get an example. Say you have an Employee table with columns for ID, LastName, and FirstName. You also have a class named Employee, with properties that match the column names. You have a variable named lastName, and you want to run a query to give you every record from the table where the value of the LastName column matches the value of the lastName variable. Then we loop through the results and write each Employee to the Console:
That would look like this:
Dim lastName As String = "Bilazon"
Dim SQL As String = "SELECT * FROM Employee WHERE LastName = ?"
Dim employees = ReadObjects(SQL,
Function(e) New Employee() With {ID = e("ID"), LastName = e("LastName"), FirstName = e("FirstName")},
Sub(p) p.Add("?", OleDbType.VarWChar, 25).Value = lastName)
For Each emp As Employee In employees
Console.WriteLine($"ID: {emp.ID}{vbTab}Last Name: {emp.LastName}{vbTab}First Name: {emp.FirstName}")
Next
The inline Function and Sub expressions above are called Lambda Expressions.
I might futher make this code a little easier to write and understand by adding a Shared method called FromDataRecord() to my Employee class:
Public Shared Function FromDataRecord(row As IDataRecord) As Employee
Return New Employee() With {
ID = row("ID"),
LastName = row("LastName"),
FirstName = row("FirstName")
}
End Function
This is especially valuable as the class adds more properties or gets more complex. Now the function call can also be simplified a bit:
Dim lastName As String = "Bilazon"
Dim SQL As String = "SELECT * FROM Employee WHERE LastName = ?"
Dim employees = ReadObjects(SQL, Employee.FromDataRecord,
Sub(p) p.Add("?", OleDbType.VarWChar, 25).Value = lastName)
For Each emp As Employee In employees
Console.WriteLine($"ID: {emp.ID}{vbTab}Last Name: {emp.LastName}{vbTab}First Name: {emp.FirstName}")
Next
The next level up is moving the ReadObjects() method to it's own Module (and marking it private), so you also write a public method in the new Module for each of the queries you want to run.
Related
I've heard that "everyone" is using parameterized SQL queries to protect against SQL injection attacks without having to vailidate every piece of user input.
How do you do this? Do you get this automatically when using stored procedures?
So my understanding this is non-parameterized:
cmdText = String.Format("SELECT foo FROM bar WHERE baz = '{0}'", fuz)
Would this be parameterized?
cmdText = String.Format("EXEC foo_from_baz '{0}'", fuz)
Or do I need to do somethng more extensive like this in order to protect myself from SQL injection?
With command
.Parameters.Count = 1
.Parameters.Item(0).ParameterName = "#baz"
.Parameters.Item(0).Value = fuz
End With
Are there other advantages to using parameterized queries besides the security considerations?
Update: This great article was linked in one of the questions references by Grotok.
http://www.sommarskog.se/dynamic_sql.html
The EXEC example in the question would NOT be parameterized. You need parameterized queries (prepared statements in some circles) to prevent input like this from causing damage:
';DROP TABLE bar;--
Try putting that in your fuz variable (or don't, if you value the bar table). More subtle and damaging queries are possible as well.
Here's an example of how you do parameters with Sql Server:
Public Function GetBarFooByBaz(ByVal Baz As String) As String
Dim sql As String = "SELECT foo FROM bar WHERE baz= #Baz"
Using cn As New SqlConnection("Your connection string here"), _
cmd As New SqlCommand(sql, cn)
cmd.Parameters.Add("#Baz", SqlDbType.VarChar, 50).Value = Baz
Return cmd.ExecuteScalar().ToString()
End Using
End Function
Stored procedures are sometimes credited with preventing SQL injection. However, most of the time you still have to call them using query parameters or they don't help. If you use stored procedures exclusively, then you can turn off permissions for SELECT, UPDATE, ALTER, CREATE, DELETE, etc (just about everything but EXEC) for the application user account and get some protection that way.
Definitely the last one, i.e.
Or do I need to do somethng more extensive ...? (Yes, cmd.Parameters.Add())
Parametrized queries have two main advantages:
Security: It is a good way to avoid SQL Injection vulnerabilities
Performance: If you regularly invoke the same query just with different parameters a parametrized query might allow the database to cache your queries which is a considerable source of performance gain.
Extra: You won't have to worry about date and time formatting issues in your database code. Similarly, if your code will ever run on machines with a non-English locale, you will not have problems with decimal points / decimal commas.
You want to go with your last example as this is the only one that is truly parametrized. Besides security concerns (which are much more prevalent then you might think) it is best to let ADO.NET handle the parametrization as you cannot be sure if the value you are passing in requires single quotes around it or not without inspecting the Type of each parameter.
[Edit] Here is an example:
SqlCommand command = new SqlCommand(
"select foo from bar where baz = #baz",
yourSqlConnection
);
SqlParameter parameter = new SqlParameter();
parameter.ParameterName = "#baz";
parameter.Value = "xyz";
command.Parameters.Add(parameter);
Most people would do this through a server side programming language library, like PHP's PDO or Perl DBI.
For instance, in PDO:
$dbh=pdo_connect(); //you need a connection function, returns a pdo db connection
$sql='insert into squip values(null,?,?)';
$statement=$dbh->prepare($sql);
$data=array('my user supplied data','more stuff');
$statement->execute($data);
if($statement->rowCount()==1){/*it worked*/}
This takes care of escaping your data for database insertion.
One advantage is that you can repeat an insert many times with one prepared statement, gaining a speed advantage.
For instance, in the above query I could prepare the statement once, and then loop over creating the data array from a bunch of data and repeat the ->execute as many times as needed.
Your command text need to be like:
cmdText = "SELECT foo FROM bar WHERE baz = ?"
cmdText = "EXEC foo_from_baz ?"
Then add parameter values. This way ensures that the value con only end up being used as a value, whereas with the other method if variable fuz is set to
"x'; delete from foo where 'a' = 'a"
can you see what might happen?
Here's a short class to start with SQL and you can build from there and add to the class.
MySQL
Public Class mysql
'Connection string for mysql
Public SQLSource As String = "Server=123.456.789.123;userid=someuser;password=somesecurepassword;database=somedefaultdatabase;"
'database connection classes
Private DBcon As New MySqlConnection
Private SQLcmd As MySqlCommand
Public DBDA As New MySqlDataAdapter
Public DBDT As New DataTable
Public BindSource As New BindingSource
' parameters
Public Params As New List(Of MySqlParameter)
' some stats
Public RecordCount As Integer
Public Exception As String
Function ExecScalar(SQLQuery As String) As Long
Dim theID As Long
DBcon.ConnectionString = SQLSource
Try
DBcon.Open()
SQLcmd = New MySqlCommand(SQLQuery, DBcon)
'loads params into the query
Params.ForEach(Sub(p) SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value))
'or like this is also good
'For Each p As MySqlParameter In Params
' SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value)
' Next
' clears params
Params.Clear()
'return the Id of the last insert or result of other query
theID = Convert.ToInt32(SQLcmd.ExecuteScalar())
DBcon.Close()
Catch ex As MySqlException
Exception = ex.Message
theID = -1
Finally
DBcon.Dispose()
End Try
ExecScalar = theID
End Function
Sub ExecQuery(SQLQuery As String)
DBcon.ConnectionString = SQLSource
Try
DBcon.Open()
SQLcmd = New MySqlCommand(SQLQuery, DBcon)
'loads params into the query
Params.ForEach(Sub(p) SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value))
'or like this is also good
'For Each p As MySqlParameter In Params
' SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value)
' Next
' clears params
Params.Clear()
DBDA.SelectCommand = SQLcmd
DBDA.Update(DBDT)
DBDA.Fill(DBDT)
BindSource.DataSource = DBDT ' DBDT will contain your database table with your records
DBcon.Close()
Catch ex As MySqlException
Exception = ex.Message
Finally
DBcon.Dispose()
End Try
End Sub
' add parameters to the list
Public Sub AddParam(Name As String, Value As Object)
Dim NewParam As New MySqlParameter(Name, Value)
Params.Add(NewParam)
End Sub
End Class
MS SQL/Express
Public Class MSSQLDB
' CREATE YOUR DB CONNECTION
'Change the datasource
Public SQLSource As String = "Data Source=someserver\sqlexpress;Integrated Security=True"
Private DBCon As New SqlConnection(SQLSource)
' PREPARE DB COMMAND
Private DBCmd As SqlCommand
' DB DATA
Public DBDA As SqlDataAdapter
Public DBDT As DataTable
' QUERY PARAMETERS
Public Params As New List(Of SqlParameter)
' QUERY STATISTICS
Public RecordCount As Integer
Public Exception As String
Public Sub ExecQuery(Query As String, Optional ByVal RunScalar As Boolean = False, Optional ByRef NewID As Long = -1)
' RESET QUERY STATS
RecordCount = 0
Exception = ""
Dim RunScalar As Boolean = False
Try
' OPEN A CONNECTION
DBCon.Open()
' CREATE DB COMMAND
DBCmd = New SqlCommand(Query, DBCon)
' LOAD PARAMS INTO DB COMMAND
Params.ForEach(Sub(p) DBCmd.Parameters.Add(p))
' CLEAR PARAMS LIST
Params.Clear()
' EXECUTE COMMAND & FILL DATATABLE
If RunScalar = True Then
NewID = DBCmd.ExecuteScalar()
End If
DBDT = New DataTable
DBDA = New SqlDataAdapter(DBCmd)
RecordCount = DBDA.Fill(DBDT)
Catch ex As Exception
Exception = ex.Message
End Try
' CLOSE YOUR CONNECTION
If DBCon.State = ConnectionState.Open Then DBCon.Close()
End Sub
' INCLUDE QUERY & COMMAND PARAMETERS
Public Sub AddParam(Name As String, Value As Object)
Dim NewParam As New SqlParameter(Name, Value)
Params.Add(NewParam)
End Sub
End Class
I'm trying to write a function that searches records in my database for the object of those that match the SearchCriteria. The functions parameters look like so:
RetrieveObject(SearchCriteria) As String (SearchCritera is a string aswell)
Right now for testing purposes I am using a console app that asks the user to search by first name.
Console.Writeline("Search by first name: ")
Dim firstName = Console.Readline()
I then use my function: RetrieveObject(firstName)
I want my function to show all the values (lastname, titlename, state, zip) for that particular person that was passed to the RetrieveObject function.
The problem I am having is that I cannot seem to understand how I'm going to match what the user enters with the value in the database.
If anyone could just put me in the right direction to help me understand how to accomplish this, I'd be so grateful!
Here is my code so far:
Private Function RetrieveObject(SearchCriteria As String) As String
Dim cn As OdbcConnection = New OdbcConnection(myCon)
Dim myQuery = "SELECT * FROM Applicant WHERE [strFirstName] LIKE '%son'"
Using com As New OdbcCommand(myQuery)
cn.Open()
com.Connection = cn
com.CommandType = CommandType.Text
com.CommandText = myQuery
com.Parameters.AddWithValue("#strFirstName", SearchCriteria)
Try
com.ExecuteReader()
Catch ex As Exception
MsgBox(ex.Message.ToString())
End Try
End Using
Return SearchCriteria
End Function
Thanks again!
To create a WHERE condition you need to provide (at the most basic level) three informations to the database engine. The first bit is the column name that you want to search for, the second piece is the operator that you want to use for matching records, and finally the value to search for
SELECT * FROM table WHERE FieldName = 'AStringValue'
Of course we could have a lot of variations with operators and field datatype but this answer is limited to your actual situation.
It is important to note that your query could return more than one record (in particular if you add wildcards operators as LIKE, so you cannot simply return a single value with this query, but instead you return a DataTable where you can loop over the Rows collection to see all the records returned by the query
So your code should be changed to
Private Function RetrieveObject(SearchCriteria As String) As DataTable
Dim myQuery = "SELECT * FROM Applicant WHERE [strFirstName] LIKE ?"
Try
Using cn = New OdbcConnection(myCon)
Using da = new OdbcDataAdapter(myQuery, cn)
da.SelectCommand.Parameters.Add("?", OdbcType.VarChar).Value = SearchCriteria
Dim dt = new DataTable()
da.Fill(dt)
return dt
End Using
End Using
Catch ex As Exception
MsgBox(ex.Message.ToString())
return Nothing
End Try
End Function
Now you could call this method with something like
Dim table = RetrieveObject("John%")
if table IsNot Nothing Then
For Each row in table.Rows
Console.WriteLine(row["strFirstName"].ToString())
Next
End If
If you really need to return a json string with all the FirstName matched then you could add this to the last lines of the code above
.....
da.Fill(dt)
Dim names = dt.AsEnumerable().Select(Function(x) x.Field(Of String)("strFirstName")).ToArray()
string output = JsonConvert.SerializeObject(names);
return output;
And of course change again the return value to be a string.
You can also pass your search criteria into function which returns dataset as shown below , one more thing ,you can use the function in textbox textchange event in forms
Also while search include LIKE as LIKE '%" & #strFirstName & "%' which can help you narrow search results if needed
Public Function Search(ByVal Criteria As String) As DataSet
Try
Dim ds As New DataSet
Using sqlCon As New SqlConnection(connStr)
stQuery="SELECT * FROM Applicant WHERE [strFirstName]
LIKE '%" & #strFirstName & "%'"
Dim sqlCmd As New SqlCommand(stQuery, sqlCon)
Dim sqlAda As New SqlDataAdapter(sqlCmd)
sqlCmd.CommandType = CommandType.Text
sqlCmd .Parameters.AddWithValue("#strFirstName", Criteria )
sqlAda.Fill(ds)
End Using
Return ds
Catch ex As Exception
MsgBox(ex.Message.ToString())
End Try
End Function
I have a view in SQL Server which gives me varbinary(MAX) data for a PDF copy of a scanned application form (don't ask). Column names in the view include [FileBinary], [Size] and [OriginalFileName].
I use DatabaseFactory throughout the project to pull out data and have not had a problem with types until now. The code to get data from this particular view is:
Public Shared Function GetAppFormPDF(studentRefNo As String) As List(Of ProSolutionReportsAppForm)
Dim db = DatabaseFactory.CreateDatabase(Constants.DataBaseConnectionStringProSolutionReports)
Dim sql As String = String.Format("SELECT * FROM [ProSolutionReports].[dbo].[MC_AppFormSSRS_FromPDFScan] WHERE RefNo LIKE '{0}%'", studentRefNo.Trim())
Return db.ExecuteSqlStringAccessor(Of ProSolutionReportsAppForm)(sql).ToList()
End Function
The model it gets pulled into is declared as:
Public Property FileBinary As System.Data.Linq.Binary
Public Property Size As Long
Public Property OriginalFileName As String
When I hit GetAppFormPDF(), I get told that "The value from column 'FileBinary' cannot be converted to property 'FileBinary' of type 'System.Data.Linq.Binary'."
I created an SQL to Linq DataContext to see how the typing is done there, but (pretty obviously) that's typed to System.Data.Linq.Binary as well and that one works. I also tried typing FileBinary as Byte(), which gives me the data from the rest of the table but sets FileBinary = Nothing.
What do I do to get the varbinary(MAX) data into my model?
After 4 hours of investigation I decided that DataFactory is the wrong way to go about this. Instead I did it the way I should have in the first place:
Dim appForms = New List(Of ProSolutionReportsAppForm)
Dim conn = New SqlConnection(ConfigurationManager.ConnectionStrings(Constants.DataBaseConnectionStringProSolutionReports).ConnectionString)
Dim sql As String = "SELECT * FROM [ProSolutionReports].[dbo].[MC_AppFormSSRS_FromPDFScan] WHERE RefNo = #RefNo"
Dim command As New SqlCommand(sql, conn)
command.Parameters.AddWithValue("#RefNo", studentRefNo.Trim())
conn.Open()
Dim reader As SqlDataReader = command.ExecuteReader()
While reader.Read()
appForms.Add(New ProSolutionReportsAppForm With {.FileBinary = reader("FileBinary"), _
.Size = reader("Size"), _
.ApplicationID = reader("ApplicationID"), _
.RefNo = studentRefNo,
.FirstForename = reader("FirstForename"), _
.Surname = reader("Surname"), _
.OriginalFileName = reader("OriginalFileName")})
End While
reader.Close()
conn.Close()
Return appForms
I didn't write the function for the AutoCompleteExtender so I am not quite sure how to change it without screwing it up, so I figured I would ask here. Recently, it was requested that the AutoComplete show a product name & the date of launch of that specific product. I do not know how to add the date to the AutoComplete.
The reason this is needed is because the webinars we give out are shown again in the future, so there are multiple webinars with the same name in the database. It is a little difficult to choose one webinar when searching when there are 3 with the exact same name, so if it shows the name and the date, I would imagine it would be easier to choose the right one!
The way this is written right now is incorrect. I get a squiggly line underneath the word launchdate in the line Dim item As String = AjaxControlToolkit...... and the error is: Too many arguments to 'Public Shared Function CreateAutoCompleteItem(text As String, value As String) As String'
Any help is greatly appreciated! Like I said, I didn't write this, so I don't even know if this is best practice. I understand if you want to criticize the code, and I will change it if it needs it, but I would really like to know how to add the extra field too. Thanks!
Public Function GetProducts(ByVal prefixText As String, ByVal count As Integer) As String()
Dim ProductSql As String = "Select DISTINCT ProductID, ProductName, LaunchDate
FROM Product
WHERE ProductName LIKE '%' + #prefixText + '%'
AND LaunchDate IS NOT NULL
ORDER BY ProductName ASC"
Using sqlConn As New SqlConnection
(System.Configuration.ConfigurationManager.ConnectionStrings("LocalSqlServer").ConnectionString)
sqlConn.Open()
Dim myCommand As New SqlCommand(ProductSql, sqlConn)
myCommand.Parameters.Add("#prefixText", SqlDbType.VarChar, 50).Value = prefixText
Dim myReader As SqlDataReader = myCommand.ExecuteReader()
Dim myTable As New DataTable
myTable.TableName = "ProductSearch"
myTable.Load(myReader)
sqlConn.Close()
Dim items As String() = New String(myTable.Rows.Count - 1) {}
Dim i As Integer = 0
For Each dr As DataRow In myTable.Rows
Dim id As String = dr("ProductID").ToString()
Dim name As String = dr("ProductName").ToString()
Dim launchdate As String = dr("LaunchDate").ToString()
Dim item As String =
AjaxControlToolkit.AutoCompleteExtender.CreateAutoCompleteItem(name, id, launchdate)
items.SetValue(item, i)
i += 1
Next
Return items
End Using
End Function
Try this..
AjaxControlToolkit.AutoCompleteExtender.CreateAutoCompleteItem(name & " " & launchdate, id)
I am trying to debug my code, which involves a simple webservice that passes parameters to a data class, which calls a stored proc, and returns one row of record. I am not getting any error messages, but I am not getting any records back either.
My webservice code is:
<WebMethod()> _
Public Function GetDataValues(ByVal AutoVIN As String, ByVal OrderID As String, ByVal StatusCode As String, ByVal DivisionID As String) As String
Dim oGetHeaderValue As New clsGetHeaderValue
Dim GetHeaderValues As String = ""
Dim strAutoVIN = Trim(AutoVIN)
Dim strOrderID = Trim(OrderID)
Dim strStatusCode = Trim(StatusCode)
Dim strDivisionID = Trim(DivisionID)
Try
'Validate user entries if they are the correct length;
If Not IsNumeric(strAutoVIN) Or Len(Trim(strAutoVIN)) <> 3 Then
Throw New SoapException("Invalid Auto VI Number", SoapException.ClientFaultCode)
End If
'OrderID could be 12 digits in length
If Not IsNumeric(strOrderID) Then
Throw New SoapException("Invalid Order ID", SoapException.ClientFaultCode)
End If
'************************Verify if the order exists "Order Number not in requested status" in table Auto.orderHeader
'************************Validate entries against db table;
'Call Stored Proc and pass parameters;
Dim retDataSet As Data.DataSet
retDataSet = oGetHeaderValue.GetHeaderValue(strAutoVIN, strOrderID, strStatusCode, strDivisionID)
GetHeaderValues = retDataSet.ToString()
Return GetHeaderValues
Catch ex As Exception
Return "<ERR>" & ex.Message.ToString & "</ERR>"
End Try
End Function
My database class code is:
Public Function GetGetHeaderValue(ByVal sAutoVIN As String, ByVal sOrderID As String, ByVal sStatusCode As String, ByVal sDivisionID As String) As DataSet
Dim sqlDA As New SqlDataAdapter
Dim ds As New DataSet
Dim sqlcmd As New SqlCommand
Dim conn As New SqlConnection(strCN_Auto)
sqlDA = New SqlDataAdapter("uspExportOrders", conn)
sqlDA.SelectCommand.CommandType = CommandType.StoredProcedure
sqlDA.SelectCommand.Parameters.Add(New SqlParameter("#AutoVIN", SqlDbType.VarChar, 3))
sqlDA.SelectCommand.Parameters("#AutoVIN").Value = sAutoVIN
sqlDA.SelectCommand.Parameters.Add(New SqlParameter("#OrderID", SqlDbType.VarChar, 12))
sqlDA.SelectCommand.Parameters("#OrderID").Value = sOrderID
sqlDA.SelectCommand.Parameters.Add(New SqlParameter("#StatusCode", SqlDbType.VarChar, 10))
sqlDA.SelectCommand.Parameters("#StatusCode").Value = sStatusCode
sqlDA.SelectCommand.Parameters.Add(New SqlParameter("#DivisionID", SqlDbType.VarChar, 3))
sqlDA.SelectCommand.Parameters("#DivisionID").Value = sDivisionID
sqlDA.Fill(ds) 'Fill the DataSet with the rows returned.
Return ds
sqlDA.Dispose()
conn.Close()
End Function
I'm basing this on the assumption that your stored procedure only returns a single string value. I don't think your problem is in your procedure not returning any data.
Change the following:
GetHeaderValues = retDataSet.ToString()
to
Dim dr As DataRow
dr = retDataSet.Tables(0).Rows(0)
GetHeaderValues = dr(0).ToString()
Here's why. If all you want out of this procedure in your database is a single string, you don't have to return and entire dataset, but that's OK. The dataset is a little more complicated than just getting the string value out of it. It can contain many tables (which is why it is normally used) and could allow you to change the data (which you are not doing). You may want to research using the command's .ExecuteScalar. I've assumed yours has only one, so you can refer to it as .Table(0) or you could replace the zero with the name of the table (Which you don't have.). In each table, there are rows and columns. Again only one row and one column so we can reference them as row(0) and column(0).