For the last 2 hours I was trying figure out why the parameter could not be bound (Well I know I was not using the "using" block. And I know System.Data.OracleClient is deprecated.) Please help me see what's wrong with the following code:
Dim nCount As Integer
sSQL = " SELECT COUNT(*) FROM USERS WHERE USER_ID = :UID "
Dim conn As OracleConnection = New OracleConnection(ConfigurationSettings.AppSettings("connString"))
conn.Open()
Dim cmd As OracleCommand = New OracleCommand(sSQL, conn)
cmd.CommandType = CommandType.Text
With cmd
.Parameters.Add(New OracleParameter(":UID", txtUserID.Text))
End With
Try
nCount = cmd.ExecuteScalar()
Catch ex As Exception
End Try
I have tried all variations I can find online: with or without colon in the Parameters.Add, Add or AddWithValue, Add in a parenthesis or create a new OracleParameter object then add it...Nothing seems to work.
But if I just hard-code the USER_ID in the query, remove the parameter.Add, it would return a value.
A HA!
UID is actually a reserved word in Oracle. Change your UID variable to something that is not a reserved word.
For me it seems that you missed something, while experimenting with different combinations.
This variant must work:
Dim nCount As Integer
sSQL = "SELECT COUNT(*) FROM USERS WHERE USER_ID = :UID"
Dim conn As OracleConnection = New OracleConnection(ConfigurationSettings.AppSettings("connString"))
conn.Open()
Dim cmd As OracleCommand = New OracleCommand(sSQL, conn)
cmd.CommandType = CommandType.Text
cmd.Parameters.Add("UID", OracleType.VarChar).Value = txtUserID.Text
nCount = cmd.ExecuteScalar()
Please try it ...
Do yourself a favor and at least look into ODP from Oracle. You'll need it with Microsoft finally pulls the plus on its OracleClient. The switch over to ODP is very easy.
In your situation, I'd leave off the parameter name. You're binding by position anyway.
The SQL syntax is also a little different in the Microsoft implementation. Use a ? to act as each placeholder. See http://msdn.microsoft.com/en-us/library/system.data.oracleclient.oracleparameter.aspx for further information.
Related
I will try to keep this as brief as possible.
I have a function called GetData(ByVal query As String) whose sole purpose is to populate a data table multiple times based on certain conditions. As you can see, the function accepts a string variable where the SQL statement resides. What I am trying to do is add a scalar variable, "#date" in my case, and no matter where I try to add this variable it throws an error stating "Must declare scalar variable #date.
Edit: I should mention that it is throwing the "must declare variable" error on the sda.Fill(dt) line.
GetData Function
Private Shared Function GetData(ByVal query As String) As DataTable
Dim constr As String = ConfigurationManager.ConnectionStrings("WarrantyConnectionString").ConnectionString
Using con As SqlConnection = New SqlConnection(constr)
Using cmd As SqlCommand = New SqlCommand(query)
Dim dt As DataTable = New DataTable()
cmd.Parameters.Add("#date", SqlDbType.Date).Value = Date.Today
Using sda As SqlDataAdapter = New SqlDataAdapter(query, con)
cmd.Parameters.AddWithValue("#date", Date.Today)
sda.Fill(dt)
End Using
Return dt
End Using
End Using
End Function
I am calling the function in a procedure that has the query and handles all of the conditions I need.
Procedure
Dim queryStart As String = "SELECT ( SELECT SUM(DealerNet) FROM Agreement WHERE VoidDate IS NULL "
Dim queryAlias As String = "AS Actual, "
Dim queryStart2 As String = "(SELECT SUM(Amount) FROM AccountingUS.dbo.ProjectedSales "
Dim queryAlias2 As String = "AS Projected "
If chart = "pmtd" Then
Dim queryCondition As String = "AND IssueDate BETWEEN (SELECT DATEADD(MONTH, DATEDIFF(MONTH, 0, #date)-1, 0)) AND #date) "
Dim queryCondition2 As String = "WHERE [Month] = MONTH(#date) AND [Year] = YEAR(#date)) "
Dim query As String = queryStart + queryCondition + queryAlias + queryStart2 + queryCondition2 + queryAlias2
Dim xMember1 As String = "Actual"
Dim xMember2 As String = "Projected"
Dim dt As DataTable = GetData(query)
pmtdChart.DataSource = dt
The variable in question is the #date variable in the strings within the "If" statement, the only value it holds is todays date. Currently, I have tried to use "cmd.Parameters.Add("#date", SqlDbType.Date).Value = Date.Today in the GetData function, however, I still receive the same "Must declare scalar variable" error. I have also tried replacing the #date variable with simply "" + Date.Today + "" or a variable that holds todays date, but upon doing so I receive an operand error about "Operand Clash: Date is incompatible with Int"
Any help regarding this issue would be greatly appreciated, I am relatively new to programming and would appreciate any tips or criticisms regarding best practices. If you need any additional information or clarification regarding this issue I would be happy to provide what I can. Thank you in advance.
Ok, a few things:
I would actually pass a command object to that get data routine.
And your issue is you feeding the query to the "adaptor", but NOT supplying the #date parameter to that "sda"
this:
Using sda As SqlDataAdapter = New SqlDataAdapter(query, con)
cmd.Parameters.AddWithValue("#date", Date.Today)
sda.Fill(dt)
End Using
In other words, you NOT EVEN using the cmd object!!!
So, you would need to add the parameter's to the sda object!!
eg this:
Public Function GetData(ByVal query As String) As DataTable
Dim dt As DataTable = New DataTable()
Dim constr As String =
ConfigurationManager.ConnectionStrings("WarrantyConnectionString").ConnectionString
Using con As SqlConnection = New SqlConnection(constr)
Using sda As SqlDataAdapter = New SqlDataAdapter(query, con)
sda.SelectCommand.Parameters.Add("#date", SqlDbType.Date).Value = Date.Today()
sda.Fill(dt)
End Using
End Using
Return dt
End Function
So, yes, you WILL get that error about "#date" not being declared, since you NOT using the cmd object to fill the table, but are using the data adaptor.
So, as a future suggest?
Pick one way, or the other way.
I MUCH over the years have decided that I will use/have/adopt and cookie cut over and over the SqlCommand object.
I find the Sql cmd object better, since:
it has the parameters.
it has a connection object (if you want to use)
it has a data reader built in
So, what this means?
I suggest this code for get data:
Private Shared Function GetData(ByVal query As String) As DataTable
Dim constr As String =
ConfigurationManager.ConnectionStrings("WarrantyConnectionString").ConnectionString
Dim dt As DataTable = New DataTable()
Using con As SqlConnection = New SqlConnection(constr)
Using cmd As SqlCommand = New SqlCommand(query, con))
con.Open()
cmd.Parameters.Add("#date", SqlDbType.Date).Value = Date.Today
dt.Load(cmd.ExecuteReader)
End Using
End Using
Return dt
End Function
So, we don't need a data adaptor. In fact, you only need a adaptor if you going to update the resulting table (think a "adaptive" table to remember this). You not going to update the data, so really, no need to use a "adaptor" at all here. (and sadly, far too many examples use a "adaptor" anyway. They are for ALLOWING update of the data table, and you not doing that!
So, use a command object. Do note that you ALWAYS must then open the confection, but since we have "using" blocks, it will ALWAYS be closed for you.
And note how then we don't create to "use" the "reader" from the adaptor, nor a fill command. (so, we eliminated one whole confusing object!!).
So, in your example, you created a SQL command object, correctly added the parameter to the command object, but THEN DON'T use it, and then decided to create a data adaptor, and use that!!!
So, you could/can leave your code as you had with the sda "prameter " fix I posted above.
However, but I think your better off to use a sql command object.
Note even better?
Pass the command object to the GetData routine.
I have a global "general" purpose routine called MyRstP(), and I pass it a command object, even for just plain jane sql.
but, if you decide to add parameter's, you can!
Do note that parameter's can be added 100% independent of the SQL string, and they can be added before, or after you set the sql string.
And you can add parameter's WITHOUT a valid working connection (or have created one just yet). So, "parameters" are just a colleciton - it does not care about the SQL (well, at least not yet!!).
So, here is my RstP, and I dumped this into a plain jane "module1" which VB has (this means you don't have to create a static class, and this works then just like VB6, or VBA.
So, this:
Public Function MyRstP(cmdSQL As SqlCommand, ByVal Optional strCon As String = "") As DataTable
If strCon = "" Then
strCon = My.Settings.TEST4
End If
Dim rstData As New DataTable
Using conn As New SqlConnection(strCon)
Using (cmdSQL)
cmdSQL.Connection = conn
conn.Open()
rstData.Load(cmdSQL.ExecuteReader)
End Using
End Using
Return rstData
End Function
So, now to say fill a grid view, I use this:
Dim strSQL As String =
"SELECT id, HotelName, City FROM tblHotelsA"
Dim cmdSQL As New SqlCommand(strSQL)
GridView1.DataSource = MyRstP(cmdSQL)
GridView1.DataBind()
or say a given date of some such:
How about all hotel visit dates from start of year.
So, this:
Dim strSQL As String =
"SELECT id, HotelName, City FROM tblHotelsA
WHERE VisitDate >= #dtStart"
Dim dtStart As DateTime
dtStart = DateSerial(DateTime.Today.Year, 1, 1)
Dim cmdSQL As New SqlCommand(strSQL)
cmdSQL.Parameters.Add("#dtStart", SqlDbType.DateTime).Value = dtStart
GridView1.DataSource = MyRstP(cmdSQL)
GridView1.DataBind()
note then how I have that MyRstP (like your get data), but I can pass it quite much anything I want, including parameter's from the "calling" code, NOT in that general routine.
Anyway, the above use and adding the parameter's to the "adaptor" will fix this, but I would change over to using just a command object and a connection - the adaptor really not required, and as noted, they really are to be used WHEN you actually want to update the data table, and then send it back to the database in one shot.
If you look closely, you setup a cmd command, but you never actually pass it to the DataTable. So it doesn't know anything about your params.
How about this instead (copied untested from Trying to pass SqlCommand in SqlDataAdapter as parameters):
DataTable dt = new DataTable();
using (SqlConnection con = new SqlConnection(ConfigurationManager.ConnectionStrings("WarrantyConnectionString").ConnectionString))
{
con.Open();
using (SqlCommand cmd = con.CreateCommand())
{
cmd.CommandText = query;
cmd.CommandType = CommandType.Text;
cmd.Parameters.Add("#date", SqlDbType.Date)
cmd.Parameters.AddWithValue("#date", Date.Today)
using (SqlDataAdapter adp = new SqlDataAdapter(cmd))
{
adp.Fill(dt);
return dt;
}
}
}
Dim dt as new DataTable()
using db as new SqlConnection(ConfigurationManager.ConnectionStrings("WarrantyConnectionString").ConnectionString)
db.Open();
using cmd as New SqlCommand(query, con)
cmd.Parameters.Add("#date", SqlDbType.Date).value = Date.Today
//cmd.Parameters.AddWithValue("#date", Date.Today)
using adp as new SqlDataAdapter(cmd)
adp.Fill(dt)
return dt
End using
End using
End using
So I have a label which shows the username of the user. I've used this value to return their ID which I then attach to a label. I used execute scalar to do this because I wasn't sure how else to get a single value on a label.
This works fine. I then use the ID from the label and put it in another table. I can do this twice and then the page crashes saying...
"Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression."
However I don't understand. I don't pull anything from the second table on the page. I don't know why it would affect it. I feel like I've tried everything. Taking out the line that posts the ID to the label lets the page run but I need it there.
Label2.Text = User.Identity.Name
Dim connetionString As String
Dim cnn As SqlConnection
Dim cmd As SqlCommand
Dim sql As String
connetionString = "Data Source=(LocalDB)\v11.0;AttachDbFilename=|DataDirectory|\FYPMS_DB.mdf;Integrated Security=True"
sql = "SELECT SupID FROM Supervisor WHERE (Email = #Email)"
cnn = New SqlConnection(connetionString)
Try
cnn.Open()
cmd = New SqlCommand(sql, cnn)
cmd.Parameters.Add(New SqlParameter("#Email", User.Identity.Name))
Dim supid1 As Int32 = Convert.ToInt32(cmd.ExecuteScalar())
cmd.Dispose()
cnn.Close()
Label1.Text = supid1.ToString
Catch ex As Exception
MsgBox("Can not open connection ! ")
End Try
End Sub
This should return the first result for you. Also, it's a good idea to employ Using blocks for objects such as connections, commands, and readers.
Using cn = New SqlConnection("Data Source=(LocalDB)\v11.0;AttachDbFilename=|DataDirectory|\FYPMS_DB.mdf;Integrated Security=True")
cn.Open()
Using cmd = New SqlCommand("SELECT SupID FROM Supervisor WHERE Email = #Email", cn)
cmd.Parameters.AddWithValue("#Email", User.Identity.Name)
Using dr = cmd.ExecuteReader
If dr.Read Then
Label1.Text = CInt(dr("SupID"))
End If
End Using
End Using
End Using
If you are not sure there are multiple rows for same email in that table, you can change the query to following, that will work for you with executescalar.
SELECT TOP 1 SupID FROM Supervisor WHERE (Email = #Email)
Horribly sorry! But yes you were right! There was another query going on in the background that I never noticed that was affecting it all. So sorry
I have the below code which is populating a dropdownlist in ASP.NET. When I use a single value, everything works like a charm, but I want the DataTextField to use two fields coming from the database, not one. Any assistance would be greatly appreciated. I have tried several ways, but nothing seems to work :(
Dim connstr As String = Session("ConnStrEP")
Using con As New SqlConnection(connstr)
Using cmd As New SqlCommand()
cmd.CommandType = CommandType.StoredProcedure
cmd.CommandText = "GetWaivers"
cmd.Connection = con
con.Open()
Dim dr As SqlDataReader = cmd.ExecuteReader()
dr.Read()
Code.DataSource = dr
Code.DataTextField = String.Format("{0}, {1}", Code.SelectedValue("tblWVCode").ToString(), Code.SelectedValue("tblWVDesc").ToString())
Code.DataValueField = "tblWVDesc"
Code.DataBind()
dr.Close()
con.Close()
End Using
End Using
UPDATE:
I generated the below SQL, but I am receiving an error when I execute the SQL Server 2008 Stored Procedure. "Invalid operator for data type. Operator equals add, type equals ntext.
"
SELECT TblWvCode, TblWvDesc, (TblWvCode + ' - ' + TblWvDesc) As FullList FROM EP.dbo.WaiverVarianceTbl
Modify the stored proc to concatenate the tblWVCode and tblWVDesc values and return them in a new field, you can then use that field for the DataTextField
You can't do that, you'll need to change your SQL query to return the field on the format you need. DataTextField must be a field or property name.
Your query should looks like this
SELECT
TblWvCode,
TblWvDesc,
(
CAST(TblWvCode as nvarchar(max)) + ', ' + CAST(tblWVDesc as nvarchar(max))
) as FullList
FROM EP.dbo.WaiverVarianceTbl
and then your VB code would be something like this
Code.DataTextField = "FullList"
Code.DataValueField = "tblWVDesc"
I am using Sql Server 2005 and Vb Studios 2010 on a .net Framework of 4.0. I am trying to get the exact number of rows from a database and populate a datatable then have a label show the number of rows.
Dim comm2 = db.selectcommand(db.conn, "*", "Tablename", " columnname = '" & Session(sessionvariable) & "' AND columnname = 'Unread '")
Dim sqlda2 As New SqlDataAdapter(comm2)
Dim dt2 As New DataTable
sqlda2.Fill(dt2)
Dim recordcount As Integer = dt2.Rows.Count
messagecountlbl.Text = recordcount
this will always return the value of 1 and I know for a fact that I have multiple values for the data I am trying to pull. I have atleast 50 and the label should be displaying that amount.
I have also tried the Select Count statement and it does the same thing.
Fix I have added this to the post since there is a 24 hr wait to answer question:
I have found a quick and simple fix that I will shorten later in a class file that I have written but this should help alot of people out.
Dim sqlresult As Object
Dim constring As String = "Connection string goes here"
Dim dbcon As SqlConnection = New SqlConnection(constring)
Dim sql As New SqlCommand
dbcon.Open()
sql.Connection = dbcon
sql.CommandText = "SELECT COUNT (*) FROM Tablename WHERE Columnname = 'Unread' AND columnname = '" & Session("sessionvariable") & "'"
sqlresult = sql.ExecuteScalar
messagecountlbl.Text = sqlresult
Aren't you missing quotes around Unread?
Also... you're susceptible to SQL Injection. Use Parameters instead
Also... if all you are doing is getting the number of rows, your code is overkill
I just noticed.... you Dimmed comm2 but your adapter uses comm
Your updated question was the route that I would have gone. This was why i said your initial code was overkill (in terms of memory usage...). DataAdapters and DataTables for one value is in no way efficient.
You might want to change your updated code to the following...
Again, look up SQL Injection to see why you should never (or at least try not to) build a sql string like that
Dim sqlresult As Object
Dim constring As String = "Connection string goes here"
Dim dbcon As SqlConnection = New SqlConnection(constring)
Dim sql As New SqlCommand
dbcon.Open()
sql.Connection = dbcon
sql.CommandText = "SELECT COUNT (*) FROM Tablename WHERE Columnname = 'Unread' AND columnname = #param"
sql.Parameters.AddWithValue("#param", Session("sessionvariable"))
sqlresult = sql.ExecuteScalar
messagecountlbl.Text = sqlresult
It looks like you're missing a single quote at the beginning of the word "Unread".
...& "' AND columnname = 'Unread'")
I note that your first line declares a variable called comm2 but you don't use it later - instead you're using simply comm.
With that in mind, the fact that you've omitted the quotes around Unread will still be relevant.
Fix I have added this to the post since there is a 24 hr wait to answer question: I have found a quick and simple fix that I will shorten later in a class file that I have written but this should help alot of people out.
Dim sqlresult As Object
Dim constring As String = "Connection string goes here"
Dim dbcon As SqlConnection = New SqlConnection(constring)
Dim sql As New SqlCommand
dbcon.Open()
sql.Connection = dbcon
sql.CommandText = "SELECT COUNT (*) FROM Tablename WHERE Columnname = 'Unread' AND columnname = '" & Session("sessionvariable") & "'"
sqlresult = sql.ExecuteScalar
messagecountlbl.Text = sqlresult
I have this code
UPDATE OPENQUERY (db,'SELECT * FROM table WHERE ref = ''"+ Ref +"'' AND bookno = ''"+ Session("number") +"'' ')
How would I prevent SQL Injections on this?
UPDATE
Here's what i'm trying
SqlCommand cmd = new SqlCommand("Select * from Table where ref=#ref", con);
cmd.Parameters.AddWithValue("#ref", 34);
For some reason everything I try and add it doesn't seem to work I keep getting SQL Command mentioned below.
The error is this
'SqlCommand' is a type and cannot be used as an expression
I'm taking over someone else's work so this is all new to me and I would like do things the right way so if anyone can provide any more help on how to make my query above safe from SQL injections then please do.
UPDATE NO 2
I added in the code as VasilP said like this
Dim dbQuery As [String] = "SELECT * FROM table WHERE ref = '" & Tools.SQLSafeString(Ref) & "' AND bookno = '" & Tools.SQLSafeString(Session("number")) & "'"
But I get an error Tools is not declared do I need to specify a certain namespace for it to work?
UPDATE
Has anyone got any ideas on the best of getting my query safe from SQL injection without the errors that i'm experiencing?
UPDATE
I now have it so it work without the parameters bit here's my updated source code any idea why it won't add the parameter value?
Dim conn As SqlConnection = New SqlConnection("server='server1'; user id='w'; password='w'; database='w'; pooling='false'")
conn.Open()
Dim query As New SqlCommand("Select * from openquery (db, 'Select * from table where investor = #investor ') ", conn)
query.Parameters.AddWithValue("#investor", 69836)
dgBookings.DataSource = query.ExecuteReader
dgBookings.DataBind()
It works like this
Dim conn As SqlConnection = New SqlConnection("server='server1'; user id='w'; password='w'; database='w'; pooling='false'")
conn.Open()
Dim query As New SqlCommand("Select * from openquery (db, 'Select * from table where investor = 69836') ", conn)
dgBookings.DataSource = query.ExecuteReader
dgBookings.DataBind()
The error i'm getting is this
An error occurred while preparing a query for execution against OLE DB provider 'MSDASQL'.
And it's because it isn't replacing the #investor with the 69836
Any ideas?
SOLUTION
Here is how I solved my problem
Dim conn As SqlConnection = New SqlConnection("server='h'; user id='w'; password='w'; database='w'; pooling='false'")
conn.Open()
Dim query As New SqlCommand("DECLARE #investor varchar(10), #sql varchar(1000) Select #investor = 69836 select #sql = 'SELECT * FROM OPENQUERY(db,''SELECT * FROM table WHERE investor = ''''' + #investor + ''''''')' EXEC(#sql)", conn)
dgBookings.DataSource = query.ExecuteReader
dgBookings.DataBind()
Now I can write queries without the worry of SQL injection
Try using a parameterized query here is a link http://www.aspnet101.com/2007/03/parameterized-queries-in-asp-net/
Also, do not use OpenQuery... use the this to run the select
SELECT * FROM db...table WHERE ref = #ref AND bookno = #bookno
More articles describing some of your options:
http://support.microsoft.com/kb/314520
What is the T-SQL syntax to connect to another SQL Server?
Edited
Note: Your original question was asking about distributed queries and Linked servers. This new statement does not reference a distributed query. I can only assume you are directly connecting to the database now. Here is an example that should work.
Here is another reference site for using SqlCommand.Parameters
SqlCommand cmd = new SqlCommand("Select * from Table where ref=#ref", con);
cmd.Parameters.Add("#ref", SqlDbType.Int);
cmd.Parameters["#ref"] = 34;
Edited:
Ok Jamie taylor I will try to answer your question again.
You are using OpenQuery becuase you are probably using a linked DB
Basically the problem is the OpenQuery Method takes a string you cannot pass a variable as part of the string you sent to OpenQuery.
You can format your query like this instead. The notation follows servername.databasename.schemaname.tablename. If you are using a linked server via odbc then omit databasename and schemaname, as illustrated below
Dim conn As SqlConnection = New SqlConnection("your SQL Connection String")
Dim cmd As SqlCommand = conn.CreateCommand()
cmd.CommandText = "Select * db...table where investor = #investor"
Dim parameter As SqlParameter = cmd.CreateParameter()
parameter.DbType = SqlDbType.Int
parameter.ParameterName = "#investor"
parameter.Direction = ParameterDirection.Input
parameter.Value = 34
Use parameters instead of concatenating your SQL query.
Assuming your database engine being SQL Server, here's a piece of code which I hope will help.
Using connection As SqlConnection = new SqlConnection("connectionString")
connection.Open()
Using command As SqlCommand = connection.CreateCommand()
string sqlStatement = "select * from table where ref = #ref and bookno = #bookno";
command.CommandText = sqlStatement
command.CommandType = CommandType.Text
Dim refParam As SqlDataParameter = command.CreateParameter()
refParam.Direction = ParameterDirection.Input
refParam.Name = "#ref"
refParam.Value = Ref
Dim booknoParam As SqlDataParameter = command.CreateParameter()
booknoParam.Direction = ParameterDirection.Input
booknoParam.Name = "#bookno"
booknoParam.Value = Session("number")
Try
Dim reader As SqlDataReader = command.ExecuteQuery()
' Do your reading job here...'
Finally
command.Dispose()
connection.Dispose()
End Try
End Using
End Using
To sum it all up, avoid SQL statement concatenation at all cost, and use parameterized quesries!
Here is an interesting link that brings you through SQL injection problem resolution on MSDN:
How To: Protect From SQL Injection in ASP.NET
use sqlparameters like:
SqlCommand cmd = new SqlCommand("Select * from Table where id=#id", con);
cmd.Parameters.AddWithValue("#id", 34);
you can use parameterized queries.
http://www.functionx.com/aspnet/sqlserver/parameterized.htm
SqlCommand cmd = new SqlCommand("Select * from Table where ref=#ref", con);
cmd.Parameters.AddWithValue("#ref", 34);
it does not work because it is written in C#, not VB.
Try something like
Dim cmd As New SqlCommand("Select * from Table where ref=#ref", con)
cmd.Parameters.AddWithValue("ref", 34)
My preferred way is to let Visual Studio handle it all by creating a DAL:
http://www.asp.net/data-access/tutorials/creating-a-data-access-layer-cs
Use LINQ. It parametrizes queries automatically.
Check out ORM as an alternative (very good way to go if you are building something medium-sized or big). It takes a little time to configure it, but then development becomes VERY fast. You choose from the native, Linq to SQL or Entity Framework, OR, try any other ORM which works with .NET.