I have this code in Visual Basic, every time I have a new insert:
Private _conn As SqlConnection
Public Function Include(ByVal pSql As String, Optional timeout As Integer = 120) As Boolean
Try
Dim SQL_Str = "my string of conection... with database. not put on this example"
_conn = New SqlConnection(SQL_Str)
_conn.Open()
_adapter = New SqlDataAdapter
cmd.CommandTimeout = timeout
cmd.Connection = _conn
cmd.CommandText = pSql
cmd.CommandType = CommandType.Text
_adapter.InsertCommand = cmd
_adapter.InsertCommand.ExecuteNonQuery()
Catch ex As Exception
InputBox("New Error on Sql cmd: ", , pSql)
End Try
_conn.Close()
_conn.Dispose()
_conn = Nothing
_adapter.Dispose()
_adapter = Nothing
End Function
Ok this is a straightforward way to update the database.
But supose I have 1000 connections at the same time, do the application would support this kind of approach?
Do this method support simultaneous threads acessing the _conn object?
Is it really necessary to create a pool of connections to handle data?
Do a pool of connections will really improve something?
E.g. with this I'm overloading the application instead of the database?
If so, how would I do it on VbNet/Visual Basic?
Yes, pooled connections really are faster. They keep you from needing to continually re-negotiate login and protocol information. Even better, this is already built into the SqlConnection type, and it's done in a reasonably thread-safe way (where the existing code is not).
The short version is you really do want to create a brand new connection object for most queries, and you do not want to try to share the same connection variable throughout an application or session.
Given that, I see several big problems in that code:
Treating a class-level _conn variable as if it were local, making it impossible to share instances of this class safely across threads.
No mechanism to clean up the connection if an exception is thrown (needs a Finally or Using block. Just closing after the Catch isn't good enough.
No way to pass separate query parameters in the function signature. This will force you to write horribly insecure code elsewhere that's crazy-vulnerable to sql injection attacks. It's the kind of thing where you wake up one morning to find out you were hacked over a year ago, and IMO borders on professional malpractice.
Mixing UI code with utility code.
You want something more like this:
Private cnString As String = "my string of conection... with database. not put on this example"
Public Sub Include(pSql As String, parameters() As SqlParamter, Optional timeout As Integer = 120)
Using conn As New SqlConnectioN(cnString), _
cmd As New SqlCommand(pSql, conn)
If parameters IsNot Nothing AndAlso parameters.Length > 0 Then
cmd.Parameters.AddRange(parameters)
End If
conn.Open()
cmd.ExecuteNonQuery()
End Using
End Sub
And you might call it like this (assuming type or instance name is DB):
Dim pSql As String = "INSERT INTO [ExampleTable] (FirstName, LastName, CreationDate) VALUES (#FirstName, #LastName, #CreationDate)"
Dim parameters = {
New SqlParameter("#FirstName", SqlDbType.NVarChar, 20),
New SqlParameter("#LastName", SqlDbType.NVarChar, 20),
New SqlParameter("#CreationDate", SqlDbType.DateTime)
}
parameters(0).Value = "John"
parameters(1).Value = "Smith"
parameters(2).Value = DateTime.Now
Try
DB.Include(pSql, parameters)
Catch ex As Exception
MessageBox.Show(String.Format("New Error on Sql cmd:{0}{1}{0}{0}Message:{2}",vbCrLf, pSql, ex.Message)
End Try
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 using simple DataReader commands very often in my project.
To simplify it, I've created a function:
Public Function DataReaderFromCommand(ByRef uCn As SQLite.SQLiteConnection, ByVal uCommandText As String) As SQLite.SQLiteDataReader
Dim nCmdSel As SQLite.SQLiteCommand = uCn.CreateCommand
With nCmdSel
.CommandText = uCommandText
End With
Dim r As SQLite.SQLiteDataReader = nCmdSel.ExecuteReader
Return r
End Function
In my project I use it like this:
Using r As SQLite.SQLiteDataReader = DataReaderFromCommand(cnUser, "SELECT * FROM settings")
Do While r.Read
'do something
Loop
End Using'this should close the DataReader
In one case, I need to delete my database. However this fails with the error "File is locked by another process".
I tried to isolate the problem, and the locking occurs because of the function "DataReaderFromCommand".
Does anybody see what I'm doing wrong / what keeps the database locked?
I thought that after "End Using" of the datareader, the SQLiteCommand would also be disposed, so there are no further reference to the database.
You should probably be trying to do it this way:
Public Sub UsingDataReader(ByVal connectionString As String, ByVal commandText As String, ByVal action As Action(Of SQLite.SQLiteDataReader))
Using connection As New SQLite.SQLiteConnection(connectionString)
Using command As New SQLite.SQLiteCommand(commandText, connection)
Using reader = command.ExecuteReader()
action(reader)
End Using
End Using
End Using
End Sub
Then you can call the code like this:
UsingDataReader("/* your connection string here */", "SELECT * FROM settings", _
Sub (r)
Do While r.Read
'do something
Loop
End Sub)
This ensures that all of the disposable references are closed when the Sub has completed.
The first problem is that not all the disposables are being disposed of. We are assured that the connection passed to that helper is in a Using block, but the command also needs to be disposed of as it has a reference to the connection:
Dim cmd As New SQLiteCommand(sql, dbcon)
Even if you dont use the overloaded constructor, in order to work, somewhere you set the connection property. This illustrates one of the problems with such "DB helper" methods: the DBConnection, DBCommand and DBReader objects work together very closely, but they are created in different methods with different scopes and you can't normally see if everything is being cleaned up properly.
The code posted will always fail because that DBCommand object - and by extension the DBConnection - are not disposed. But even if you clean up properly, pooling will keep the DBConnection alive for a while as jmcilhinney explains. Here are 2 fixes:
Clear the Pool
Using dbcon As New SQLiteConnection(LiteConnStr),
cmd As New SQLiteCommand(sql, dbcon)
dbcon.Open()
Dim n As Int32 = 0
Using rdr = cmd.ExecuteReader
While rdr.Read
' == DoSomething()
Console.WriteLine("{0} == {1}", n, rdr.GetString(0))
n += 1
End While
End Using
' Clears the connection pool associated with the connection.
' Any other active connections using the same database file will be
' discarded instead of returned to the pool when they are closed.
SQLiteConnection.ClearPool(dbcon)
End Using
File.Delete(sqlFile)
The dbCon and cmd objects are "stacked" into one Using statement to reduce indentation.
This will close and discard any and all connections in the pool, provided they have been Disposed - as well as any objects which reference them. If you use Dim cmd ... you will need to explicitly dispose of it.
Force Garbage Collection
I think this is much more ham-fisted, but it is included for completeness.
Using dbcon As New SQLiteConnection(LiteConnStr),
cmd As New SQLiteCommand(sql, dbcon)
...
Using rdr = cmd.ExecuteReader
...
End Using
End Using
GC.WaitForPendingFinalizers()
File.Delete(sqlFile)
This also works as long as everything has been properly disposed of. I prefer not to mess with GC unless absolutely necessary. The issue here is that clean up will not be limited to DBProvider objects but anything which has been disposed and is awaiting GC.
Yet a third workaround would be to turn off pooling, but you would still have to dispose of everything.
You are going to need to also close your cnUser connection to the database.
Closing/disposing the reader does not necessarily close/dispose the open connection.
Can you please tell me what's wrong with this code?
Do I need to use DataAdapter to insert into a table?
I know the connectionString is ok, because I tested it on the Server Explorer.
Dim mydao As New Connection
Dim connectionString As String = mydao.GetConnectionString()
Dim connection As New SqlConnection(connectionString)
Dim cmd As New SqlCommand
Public Function add(ByVal area As String, ByVal user As String) As Integer
cmd.CommandText = "INSERT into Area (Area, user) VALUES ('" + area + "','" + user + "')"
Try
connection.Open()
Dim cant As Integer = cmd.ExecuteNonQuery()'it throws exception here
connection.Close()
Return cant
Catch ex As Exception
Console.WriteLine(ex.Message)
Return 0
End Try
End Function
The above code fails just after ExecuteNonQuery() and canĀ“t figure why.
TARGET FIELDS (SQL Server 2008):
AREA varchar(100) NOT NULL ,
USER varchar(100) NOT NULL
The exception I receive is: Connection property has not initialized
There's a few issues with this code.
The most significant is that you aren't setting the Command's Connection property, so the command has no way of knowing how to connect to the database.
I would also strongly recommend utilizing using, and also parameterizing your query:
Finally, don't declare the connection and command outside of the function unless you need to. You should only keep the connection and command around for as long as you need them.
So your function would end up looking like:
Public Function add(ByVal area As String, ByVal user As String) As Integer
Dim mydao As New Connection
Using connection As New SqlConnection(mydao.ConnectionString())
Using command As New SqlCommand()
' Set the connection
command.Connection = connection
' Not necessary, but good practice
command.CommandType = CommandType.Text
' Example query using parameters
command.CommandText = "INSERT into Area (Area, user) VALUES (#area, #user)"
' Adding the parameters to the command
command.Parameters.AddWithValue("#area", area)
command.Parameters.AddWithValue("#user", user)
connection.Open()
Return command.ExecuteNonQuery()
End Using ' Dispose Command
End Using ' Dispose (and hence Close) Connection
End Function
Note that currently, you will be returning 0 all the time. Rather than having to check the value returned from the function, the above example will simply throw an exception. This makes for slightly cleaner code (as the caller would have to understand that 0 is an error condition), and, if you needed to handle the exception, simply wrap the call to this function in a Try-Catch block
I have an ASP.Net 2.0 Web Forms Application using SQL Server 2008. The application has a UI layer and Data Access Layer. I use Enterprise Libray 5.0 to persist the data.
Recently my site has been running very slow, especially on pages where there are maybe 15-20 separate reads from the database. I am very concerned that my SqlDataReader database connections are not being closed properly. Below is an example of how code works. Please take a look and let me know if you see any issues with it in terms of leaking connections.
Data Access Class
Public Class DataAccess
Private db As Database = DatabaseFactory.CreateDatabase()
Public Function ExecuteDataReader(ByVal params() As SqlParameter, ByVal SProc As String) As SqlDataReader
Dim i As Integer
Dim dr As SqlDataReader = Nothing
Dim cmd As DbCommand
cmd = db.GetStoredProcCommand(SProc)
cmd.CommandTimeout = 120
For i = 0 To params.Length - 1
db.AddInParameter(cmd, params(i).ParameterName.ToString, params(i).DbType, params(i).Value)
Next
dr = TryCast(DirectCast(db.ExecuteReader(cmd), RefCountingDataReader).InnerReader, SqlDataReader)
Return dr
End Function
UI Code Behind Page
Dim drSource As SqlDataReader = Nothing
Try
Dim params(0) As SqlParameter
params(0) = New SqlParameter("#applicant_id", Session("ApplicantID"))
drSource = DataAccess.ExecuteDataReader(params, "sp_get_date_last_login")
If drSource.HasRows Then
drSource.Read()
'Do code
End If
Finally
If Not (drSource Is Nothing) Then
drSource.Close()
End If
End Try
I tried to put the code below into my ExecuteDataReader method, but this then closes the SqlDataReader before it gets a chance to do a Read
if (cmd.Connection.State == ConnectionState.Open)
cmd.Connection.Close();
Can someone please look at the code above and let me know how to properly close my database connections, or maybe I am already doing it?
Thanks for your help folks.
Have you tried getting the underlying call to ExecuteReader to take the CommandBehavior.CloseConnection parameter? At least this will ensure that the connection is closed when the DataReader is also closed. This will rely on consumers of the DataReader passed back from ExecuteDataReader() to close it explicitly or dispose via a Using block.
Alternatively, try adding the following to the code behind after the drSource.Close() line:
drSource.Connection.Close()
I am familiar with the VB6 ADO way of dealing with SQL queries and looping through the record set results.
However, what is the correct way to query a server, cycle through the results, and dispose of my query in VB.Net? All the ways I have been using seem to be unstable and crash randomly.
I have been using the following code:
Public Function GetSQLTable(ByVal strSQL As String) As DataTable
Dim table As New DataTable
Dim adapt As SqlDataAdapter
Try
adapt = New SqlDataAdapter(strSQL, gconIntegration)
adapt.Fill(table)
Catch ex As Exception
LogError("GetSQLTable: " & ex.ToString(), "SQL: " & strSQL)
End Try
Return table
End Function
And using it like this:
Dim dt As DataTable
Dim lngRow As Long
Dim current As DataRow
Dim lngContact As long
Try
dt = GetSQLTable(strSQL)
For lngRow = 0 To dt.Rows.Count - 1
current = dt.Rows.Item(lngRow)
lngContact = current.Item("indvid")
DoSomething(lngContact)
Next
Catch ex As Exception
LogError("FindContact: " & ex.ToString(), "SQL: " & strSQL)
lngContact = -1
Finally
current = nothing
dt = nothing
I suspect the problem has to do with how you manage your gconIntegration connection. You're trying too hard to keep using that same connection. It would be helpful to see where it lives.
Better to get "new" connections from the pool and let .Net worry about it for you.
Also, your generic "GetSQLTable" code is missing an important part: it makes no allowance for setting parameters, which tells me you're building them directly into your query strings. That's a recipe for disaster: it will lead to Sql injection security holes.
One more thing: don't set objects to Nothing in .Net. Either dispose them if needed or let them fall out of scope on their own.
Here's my normal method for pulling back a datatable from a datatable:
Function GetSomeData(ByVal Table2ID As Integer)
Dim result As New DataTable
Dim sql As String = "SELECT Column1,Column2 FROM [Table1] WHERE Table2ID= #Table2ID"
Using cn As New SqlConnection( GetConnectionString() ), _
Using cmd As New SqlCommand(sql, cn)
cmd.Parameters.Add("#Table2ID", SqlDbType.Int).Value = Table2ID
Using rdr As SqlDataReader = cmd.ExecuteReader()
result.Load(rdr)
End Using
End Using
return result
End Function
Some notes on that code:
The Using statement will guarantee that the associated object is disposed at the corresponding End Using.
The query parameters are kept strongly typed, and never substituted directly into the query string, even when they are transmitted to the server. Sql Data and Sql Code never mix.
You do need a separate function for each query you need to send. This is really a good thing, as it leads into building a strongly-typed interface for your database. Ideally all of these functions are in the same class, and the GetConnectionString function is private to that class. No database access happens outside of this data layer.