DbNull.Value Stored Procedure Parameter? - asp.net

I have a web service which has a generic function that returns a dataset from results of stored procedures... Some of the stored procedures have optional parameters where the value can be null but not all the time.
Anyhow I am trying to pass in a parameter which has a value of DBNull.Value
and I get this There was an error generating the XML document. back from the web service when doing so
If I leave this parameter out it works fine... but really would like to know why DBNull.Value causes this problem.

I beleive that's becuase a System.DBNull value is a null in database table but a null field in a procedure effectively equates to the null/nothing keyword. Not a database null value. I'm not sure of the technical differences under the hood.
But in your stored proc you can just default it to null and not send the value as you've already done or i believe if you sent null/nothing it would also work.

You can pass a NULL value in the SqlParemeter, but you must do some type conversion to make sure the right null value gets passed.
In this example, there is a parameter called "Count" which is an integer, which gets passed as null:
Using dtResult as New DataTable
Using cn as SqlConnection = New SqlConnection ("ConnectionString")
Using sqlcmd as SqlCommand - New SqlCommand("StoredProceName", cn) with {.CommandType=CommandType.StoredProcedure}
Dim sp As SqlParameter
sp = sqlcmd.Parameters.Add("#Count", SqlDbType.Int)
sp.value = CType(Nothing, SqlTypes.SqlInt32)
Using myDR as SqlDataReader = sqlcmd.ExecuteReader
dtResult.Load(myDR)
end using
return dtResult
End Using ' For sqlcmd
cn.Close() ' Close the connection
end using ' For cn
end using ' For dtResult

Related

SQLHelper.vb missing ExecuteDataTable

Why is there no Shared function for ExecuteDataTable in SQLHelper.vb. There is an: ExecuteReader, ExecuteDataset and ExecuteScaler.
This is not a problem as I will write my own. I am just wandering why this is the case. I would normally use a DataReader but I am writing a data logic layer and the DataTable needs to outlive the connection (DataReaders cannot outlive a connection).
ExecuteDataset() will already do what you need. A dataset is, in one sense, just a collection of DataTables.
I would normally use a DataReader but I am writing a data logic layer and the DataTable needs to outlive the connection (DataReaders cannot outlive a connection).
In this case, may I suggest that instead of building an ExecuteDatatable() method, you build an ExecuteEnumerable() method that uses a DataReader in an Iterator block. The code would look something like this:
Public Shared Iterator Function ExecuteEnumerable(Of T)( ... ) As IEnumerable(Of T)
Using cn As New SqlConnection( ... ), _
cmd As New SqlCommand( ... )
'As needed
'cmd.Parameters.Add( ... ).Value = ...
Using rdr As SqlDataReader = cmd.ExecuteReader()
While rdr.Read()
Yield transform(rdr)
End While
End Using
End Using
End Function
You'll notice that I skipped over a few things. I'm not familiar with the existing SqlHelper.vb file, and as you would want to match existing style I left room in the code for you to adapt for that. However, there are two important pieces I want to call out:
Note the cmd.Parameters.Add() call. One common failing of utility sql help classes is that they fail to adequately provide for query parameters. All to often the result is horribly insecure code. If you don't have a way right now to pass parameter data for your existing methods, you need to make one. That is priority 1.
The transform(rdr) call there will use a Func(IDataRecord, T) delegate that must be supplied as an argument to the function. For the ExecuteEnumerable() iterator concept to work, you must take a copy of the current values in the SqlDataReader object on each iteration. You could set up some kind of generic data transfer object here, as is done with the DataRow type using in a DataTable. However, rather than spending cpu and memory time creating a copy into a generic data transport object of some type, I prefer to use a delegate to have the code copy it directly into a strongly-typed business object. The downside is needing to send instructions on how to do that for your specific object with every call to method. Most often, though, this is easy enough to do with a shared factory method on your business object.
We can create same as DataSet like
' Execute a SqlCommand (that returns a resultset) against the specified SqlConnection
' using the provided parameters.
' e.g.:
' Dim dt As DataTable = ExecuteDataTable(conn, CommandType.StoredProcedure, "GetOrders", new SqlParameter("#prodid", 24))
' Parameters:
' -connection - a valid SqlConnection
' -commandType - the CommandType (stored procedure, text, etc.)
' -commandText - the stored procedure name or T-SQL command
' -commandParameters - an array of SqlParamters used to execute the command
' Returns: A dataset containing the resultset generated by the command
Public Overloads Shared Function ExecuteDataTable(ByVal connection As SqlConnection, _
ByVal commandType As CommandType, _
ByVal commandText As String, _
ByVal ParamArray commandParameters() As SqlParameter) As DataTable
If (connection Is Nothing) Then Throw New ArgumentNullException("connection")
' Create a command and prepare it for execution
Dim cmd As New SqlCommand
Dim dt As New DataTable
Dim dataAdatpter As SqlDataAdapter
Dim mustCloseConnection As Boolean = False
PrepareCommand(cmd, connection, CType(Nothing, SqlTransaction), commandType, commandText, commandParameters, mustCloseConnection)
Try
' Create the DataAdapter & DataSet
dataAdatpter = New SqlDataAdapter(cmd)
' Fill the DataSet using default values for DataTable names, etc
dataAdatpter.Fill(dt)
' Detach the SqlParameters from the command object, so they can be used again
cmd.Parameters.Clear()
Finally
If (Not dataAdatpter Is Nothing) Then dataAdatpter.Dispose()
End Try
If (mustCloseConnection) Then connection.Close()
' Return the dataset
Return dt
End Function ' ExecuteDataTable

checking for duplicate values before attempting insert (ASP.NET)

I have a form where two fields on the first page of the form make up the primary key. I want to check for duplicate values before attempting to insert the record, since I don't want the user to go all the way through the form only to find out they can't submit it. So I'm trying to check for duplicate values when the user tries to go to the next page of the form. I wasn't quite sure how to do it, and sure enough I'm getting an error. ("Object reference not set to an instance of an object.") The problem is apparently in my if statement, "If myValue.Length > 0 Then", but I'm not sure what needs to be in place of that.
Protected Sub CustomValidator1_ServerValidate(ByVal source As Object, ByVal args As System.Web.UI.WebControls.ServerValidateEventArgs) Handles CustomValidator1.ServerValidate
'get values
Dim checkPrefix = txtCoursePrefix.Text
Dim checkNum = txtCourseNum.Text
'db connectivity
Dim myConn As New OleDbConnection
myConn.ConnectionString = AccessDataSource1.ConnectionString
myConn.Open()
'select records
Dim mySelect As New OleDbCommand("SELECT prefix, course_number FROM tableCourse WHERE prefix='checkPrefix' AND course_number='checkNum'", myConn)
'execute(Command)
Dim myValue As String = mySelect.ExecuteScalar()
'check if record exists
If myValue.Length > 0 Then
CustomValidator1.ErrorMessage = "some exp text"
CustomValidator1.SetFocusOnError = "true"
CustomValidator1.IsValid = "false"
End If
End Sub
Thought I'd post the final solution:
'select records
Dim mySelect As New OleDbCommand("SELECT 1 FROM tableCourse WHERE prefix=? AND course_number=?", myConn)
mySelect.Parameters.AddWithValue("#checkPrefix", checkPrefix)
mySelect.Parameters.AddWithValue("#checkNum", checkNum)
'execute(Command)
Dim myValue = mySelect.ExecuteScalar()
'check if record exists
If myValue IsNot Nothing Then
CustomValidator1.SetFocusOnError = True
args.IsValid = False
End If
This error indicates that the content of myValue variable is null. If it's null you can't use Length property (or any other property for that matter) on it. You have to check for null explicitly:
If myValue IsNot Nothing Then
EDIT 1
Your sql query is wrong. I don't know what would be the right query, as I don't know your database, but I think you intender to write this:
Dim mySelect As New OleDbCommand("SELECT prefix, course_number FROM tableCourse WHERE prefix=" + checfkPreix + " AND course_number=" + checkNum, myConn)
or something to that effect. You might want to consider using string.Format function for forming the string. And you also need to make sure that there is some kind of protection against SQL Injection, since you form your query from user input. In your case using of OleDbParameter might be appropriate.
Edit 2
You also right to mention that there might be a problem with ExecuteScalar. ExecuteScalar is supposed to return a single value and your select query are returning two (prefix and course_number). Change it so that it returns a single parameter SELECT prefix FROM or simply SELECT 1 FROM and then the rest of the query:
Dim mySelect As New OleDbCommand("SELECT 1 FROM tableCourse WHERE prefix=? AND course_number=?", myConn)
mySelect.Parameters.AddWithValue("#checkPrefix", checkPrefix)
mySelect.Parameters.AddWithValue("#checkNum", checkNum)
Edit 3
You are not setting failed validation properly in your validator.
Add
args.IsValid = False
inside your if statement.
First ExecuteScalar will only return a single value, so in this case you are only going to get the column prefix from the result. Second if there is no match with your query it will return null, so your next length check should account for that scenario:
if String.IsNullOrEmpty(myValue) Then
...
Reference: http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.executescalar.aspx
myValue is null if there is no duplicate, so you have to apply .Length only if myValue is not null (which means checking for null only is enough; without .Length)
If Not string.IsNullOrEmpty(myValue) Then
try something like this instead (you will have to adapt it to VB.Net) DBNull is different from Null or Nothing so you have to compare it to both
If myValue <> DBNull and not myvalue is nothing Then

ODP.NET VB.Net calling a stored procedure and returning a refCursor

This problem has driven me mad for over a day now. I can create a connection to the database, I can execute sql and return results from that but I can't seem to call a stored Procedure. Here is the code
Dim myCMD As New OracleCommand
Dim TheDataReader as New OracleDataReader
myConnection1.Open()
myCMD.Connection = myConnection1
myCMD.CommandType = CommandType.StoredProcedure
myCMD.CommandText = "WS_DATA_LAYER.select_user_groups"
myCMD.Parameters.Add(New OracleParameter("id_user", OracleDbType.VarChar2)).Value = "TXA"
myCMD.Parameters.Add(New OracleParameter("ws_rs", OracleDbType.RefCursor)).Direction = ParameterDirection.Output
' Tried every single execute function here and none have worked
' Either error is thrown or empty refcursor
myCMD.ExecuteScalar()
TheDataReader = myCMD.Parameters(1).Value().GetDataReader()
The Problem lies in ExecuteScalar at the moment. It's throwing an exception called "Input string was not in a correct format". I've tried passing the string with Oracle single quotes and get the same thing. If I use
TheDataReader = myCMD.ExecuteQuery()
it works ok but no results are returned. I've verified that the procedure returns results for the user I'm logged in as. When the query was executing I could see a refcursor in there but it was empty. I must be going mad.
Any help is appreciated
Anyone else that may have this problem, I was passing the OracleDBType.Varchar2 as a parameter to the above VB method. But I had it declared as an integer, it needs to be explicitly passed as an OracleDBType

Executenonquery return value

I want to perform a search on a table to see if record exists. I do not want to perform insert or update after. I have done this already but somehow I cannot get this to work. On my asp.net page I cannot seem to get any value returned. The error is "input string not in correct format" I ma sure it is obvious but I cannot seem to see it now!
here is my code:
Dim con As New SqlConnection("connstring")
Dim cmd As New SqlCommand("checkname", con)
cmd.CommandType = CommandType.StoredProcedure
cmd.Parameters.Add(New SqlParameter("#d", SqlDbType.Int))
cmd.Parameters("#id").Value = TextBox1.Text
Dim para As New SqlParameter
para.Direction = ParameterDirection.ReturnValue
para.ParameterName = "returnvalue"
cmd.Parameters.Add(para)
con.Open()
cmd.ExecuteNonQuery()
Dim exists As Integer
exists = Convert.ToInt32(cmd.Parameters("returnvalue").Value)
If exists = 1 Then
Label1.Text = "You......"
ElseIf exists = 0 Then
Label1.Text = "You....."
End If
con.Close()
stored procedure:
CREATE PROCEDURE checkname
-- Add the parameters for the stored procedure here
#id int
AS
--This means it exists, return it to ASP and tell us
-- SELECT 'already exists'
IF EXISTS(SELECT * FROM attendees WHERE id = #id)
BEGIN
RETURN 1
END
ELSE
BEGIN
RETURN 0
END
You need to ensure that you are passing an integer.
int intValue;
if(!int.TryParse(TextBox1.Text, out intValue))
{
// Update your page to indicate an error
return;
}
cmd.Parameters.Add(New SqlParameter("id", SqlDbType.Int));
cmd.Parameters("id").Value = intValue;
(Technically you don't need the "#" character when
defining the parameters in the .NET
code.)
You have declared your procedure parameter as #d instead of #id. Also a return parameter cannot be an input parameter. The return value should be an exit code. You most likely want to create an output parameter and set that to 1 or zero inside of your stored procedure.
Edit: to clarify, the return value is generally regarded as an indicator of correct execution. Zero usually means success, where any other numeric value is generally regarded as an error code. That is why I recommended adding an output parameter instead of adding a return value parameter.
ExecuteNonQuery returns the number of rows affected. Therefore the return values that you set in your stored procedure are thrown away and will not be returned by the ExecuteNonQuery method.
ExecuteNonQuery is used to Insert / Delete / Update operations. Not for SELECT, you need either ExecuteScalar or ExecuteReader methods. This link will help you to know how to use output parameters : http://aspdotnet-suresh.blogspot.com/2010/10/introduction-here-i-will-explain-how-to.html

SQL Stored Procedures failing to return values

I am working on a Tag system for a news page designed in ASP.NET. For the system I require a TagExists method to check for tags within the database. The stored procedure I have written is below.
ALTER PROCEDURE [dbo].[Tags_TagExists](
#Tag varchar(50))
AS
BEGIN
If (EXISTS(SELECT * FROM dbo.Tags WHERE LOWER(#Tag) = LOWER(Tag)))
RETURN 1
ELSE
RETURN 0
END
When I call this method however 0 is always returned. I am using the following code to call the method
Public Shared Function TagExists(ByVal name As String) As Boolean
Dim result As Boolean
Using conn As SqlConnection = New SqlConnection(ConnectionString)
Dim cmd As New SqlCommand("Tags_TagExists", conn)
cmd.CommandType = CommandType.StoredProcedure
cmd.Parameters.AddWithValue("#Tag", name)
conn.Open()
result = Convert.ToBoolean(cmd.ExecuteScalar())
conn.Close()
End Using
Return result
End Function
I have tried switching the procedure to return 0 if the tag exists and 1 if it does not and it still returns 0 despite the exact same testing conditions. I have also returned the actual select query and it has complained of the Tag "news" (my test item) not being an int on execution showing the select itself is definitely properly formed.
If anyone can shed some light on this, Thanks
Michael
It should probably be a function, but here is the stored proc code:
ALTER PROCEDURE [dbo].[Tags_TagExists](
#Tag varchar(50))
AS
BEGIN
If EXISTS(SELECT 1 FROM dbo.Tags WHERE LOWER(#Tag) = LOWER(Tag))
BEGIN
SELECT 1
END
ELSE
BEGIN
SELECT 0
END
END
You're returning from a Stored Procedure, not getting a single scalar value from a SQL statement.
I'm assuming this is a simple example and you have other processing you want to handle inside the Stored Procedure. In that case, using the Stored Procedure and return value is the right way to go. You need to handle the return value from the Stored Procedure in your C# code (Please excuse any syntax errors, my VB.NET is a bit rusty):
Public Shared Function TagExists(ByVal name As String) As Boolean
Dim result As Boolean
Using conn As SqlConnection = New SqlConnection(ConnectionString)
Dim cmd As New SqlCommand("Tags_TagExists", conn)
cmd.CommandType = CommandType.StoredProcedure
cmd.Parameters.AddWithValue("#Tag", name).
Dim retVal As SqlParameter = _
cmd.Parameters.Add("return_value", SqlDbType.Int)
retval.Direction = ParameterDirection.ReturnValue
conn.Open()
cmd.ExecuteNonQuery()
result = System.Convert.ToBoolean(retval.Value)
conn.Close()
End Using
Return result
End Function
If you're strictly interested in the return value and your Stored Procedure isn't performing any other use, then convert it to a simple select statement (or function). Your use of ExecuteScalar would work in that case.
Please try using SELECT 1 and SELECT 0 instead of RETURN statement
Hope that helps,

Resources