Background
I'm maintaining a VB.Net (formerly VB6) utility that exports records to a flat file. One of our customers reported that the new version was taking a long time to run, and digging into the trace it was easy to see why: (I've obfuscated this slightly)
exec [sys].sp_describe_first_result_set
N'Update [MAIN].[dbo].[Inventory] Set ExportId = #P1 Where Comp = #P2 and Osite = #P3 and Key = #P4',
N'#P1 numeric(10),#P2 varchar(6),#P3 varchar(6),#P4 int',1
This and statements like it were taking half a second each. The utility has to update the main inventory table with some information about the export, and the table is heavily triggered, so sp_describe_first_result_set has to simulate all the triggers in order to determine the result - I can verify that later down in the trace.
Problem
I can't figure out why exactly my code is calling sp_describe_first_result_set for an update statement, a thing that doesn't even have a result set. The command setup doesn't look like it's doing anything weird:
Dim connection = New ADODB.Connection
connection.ConnectionString = config.AdoConnectionString
connection.CursorLocation = CursorLocationEnum.adUseClient
connection.Open()
cmd = New ADODB.Command
With cmd
.ActiveConnection = connection
.CommandType = CommandTypeEnum.adCmdText
.CommandText = "Update [MAIN].[dbo].[Inventory] " &
"Set ExportId = ? " &
"Where Comp = ? and Osite = ? and Key = ?"
.Parameters.Append(NumericParameter(cmd.CreateParameter("ExportId", DataTypeEnum.adNumeric, ParameterDirectionEnum.adParamInput), 10, 0))
.Parameters.Append(cmd.CreateParameter("Comp", DataTypeEnum.adVarChar, ParameterDirectionEnum.adParamInput, 6))
.Parameters.Append(cmd.CreateParameter("Osite", DataTypeEnum.adVarChar, ParameterDirectionEnum.adParamInput, 6))
.Parameters.Append(cmd.CreateParameter("Key", DataTypeEnum.adInteger, ParameterDirectionEnum.adParamInput))
End With
cmd.Parameters("ExportID").Value = lExportId
cmd.Parameters("Comp").Value = sComp
cmd.Parameters("Osite").Value = sOsite
cmd.Parameters("Key").Value = iKey
cmd.Execute()
Is there some setting I'm just missing that's making it run sp_describe_first_result_set all the time? Is there a way to stop it from doing that?
You need to explicitly say you don't want a recordset.
In VB6, an ADODB Command object can tell whether or not it should return a recordset based on whether the value of the Execute() function is being assigned to anything.
In VB.NET, this is no longer done, but execution options flags can be provided as arguments to the Execute() function. Calling the command like this:
cmd.Parameters("ExportID").Value = lExportId
cmd.Parameters("Comp").Value = sComp
cmd.Parameters("Osite").Value = sOsite
cmd.Parameters("Key").Value = iKey
cmd.Execute(Options:=ExecuteOptionEnum.adExecuteNoRecords)
will not call sp_describe_first_result_set to establish the parameters of the record set, but will only execute the command directly, the same way more modern methods such as SqlCommand.ExecuteNonQuery() will.
Out of curiosity, does the modern equivalent to that code also trace as performing this op?
Dim cmd as New SqlCommand( _
"Update [MAIN].[dbo].[Inventory] Set ExportId = #e Where Comp = #c and Osite = #o and Key = #k",
"Data Source=YOUR_SERVER;Initial Catalog=YOUR_DB;User ID=YOUR_USER_EG_sa;Password=YOUR_PASSWORD" _
)
cmd.Connection.Open()
cmd.Parameters.AddWithValue("#e", lExportId)
cmd.Parameters.AddWithValue("#c", sComp)
cmd.Parameters.AddWithValue("#o", sOsite)
cmd.Parameters.AddWithValue("#k", iKey)
cmd.ExecuteNonQuery()
I use AddWithValue for testing/convenience purposes here, but you should probably avoid it in prod because it can cause performance issues with SQLS - see that link for advice on how to craft parameters properly
Related
I am writing some script to read the sql query result at Intouch environment, it's not exactly C# language but similar. I just want to get the "1" stored in my "SQLTest" variable (Define as a string data type).
Here's the result of my sql query
And here is my code:
Dim objDB As System.Data.SqlClient.SqlConnection;
Dim objCmd As System.Data.SqlClient.SqlCommand;
Dim objDR As System.Data.SqlClient.SqlDataReader;
Dim objTbl As System.Data.DataTable;
Dim sDBConnStr As String;
Dim sSQL As String;
Dim bOk As Boolean;
sDBConnStr = "Server=Desktop-3J641FK;Database=Runtime;Integrated Security=True;";
'' Connect and open the database
objDB = New System.Data.SqlClient.SqlConnection(sDBConnStr);
objDB.Open();
sSQL = "SELECT sum (case when EventLogKey = '5' and DetectDateTime between '2022-07-21 11:00:20' and '2022-07-25 11:00:20' then 1 else 0 end) FROM [Runtime].[dbo].[EventHistory]";
'' Invoke the SQL command
objCmd = New System.Data.SqlClient.SqlCommand(sSQL, objDB);
'' Retrieve the queried fields from the query into the reader
objDR = objCmd.ExecuteReader();
InTouch:SQLTesting = objDR.Read();
while objDR.Read() == true
InTouch:SQLTest = objDR.GetValue(0).ToString;
endwhile;
objDR.Close();
objDB.Dispose();
objCmd.Dispose();
InTouch:SQLTesting = objDR.Read();
while objDR.Read() == true
You are calling Read twice, so what do you expect to happen?
If there will be exactly one row, just call Read.
If there will be zero or one row, call Read with an if statement.
If there may be more than one row, call Read with a while loop.
Do one and only one of the above. If there might be more than one row and you want to do something different if there are no rows, use the HasRows property first, then use the while loop.
Having said all that, if there will only be one value in the result set then you should be calling ExecuteScalar, so the data reader is irrelevant:
InTouch:SQLTest = objCmd.ExecuteScalar().ToString();
I think your While loop is unnecessary here as you Read the sqldata reader already before While and you are using Sum in your sql which will return one value always. Try this :
InTouch:SQLTesting = objDR.Read();
InTouch:SQLTest = objDR.GetValue(0).ToString;
Dim StrSql = "update student set id=?"
Updated (StrSql,15)
Public Function Updated (ByVal strSql As String, ByVal ParamArray Parameters As String ())
For Each x In Parameters
cmd.Parameters.AddWithValue("?",x)
Next
cmd.ExecuteNonQuery()
End Function
You didn't leave us much to go on; as jmcilhinney points out, you need to add more detail to future questions. For example in this one you have code there that doesn't compile at all, doesnt mention the types of any variable, you don't give the name of the database...
...I'm fairly sure that "Incorrect syntax near" is a SQL Server thing, in which case you need to remember that it (re)uses named parameters, unlike e.g. Access which uses positional ones:
SQL Server:
strSql = "SELECT * FROM person WHERE firstname = #name OR lastname = #name"
...Parameters.AddWithValue("#name", "Lee")
Access:
strSql = "SELECT * FROM person WHERE firstname = ? OR lastname = ?"
...Parameters.AddWithValue("anythingdoesntmatterwillbeignored", "Lee")
...Parameters.AddWithValue("anythingdoesntmatterwillbeignoredalso", "Lee")
This does mean your function will need to get a bit more intelligent; perhaps pass a ParamArray of KeyValuePair(Of String, Object)
Or perhaps you should stop doing this way right now, and switch to using Dapper. Dapper takes your query, applies your parameters and returns you objects if you ask for them:
Using connection as New SqlConnection(...)
Dim p as List(Of Person) = Await connection.QueryAsync(Of Person)( _
"SELECT * FROM person WHERE name = #name", _
New With { .name = "John" } _
)
' use your list of Person objects
End Using
Yep, all that adding parameters BS, and executing the reader, and converting the results to a Person.. Dapper does it all. Nonquery are done like connection.ExecuteAsync("UPDATE person SET name=#n, age=#a WHERE id=#id", New With{ .n="john", .a=27, .id=123 })
http://dapper-tutorial.net
Please turn on Option Strict. This is a 2 part process. First for the current project - In Solution Explorer double click My Project. Choose Compile on the left. In the Option Strict drop-down select ON. Second for future projects - Go to the Tools Menu -> Options -> Projects and Solutions -> VB Defaults. In the Option Strict drop-down select ON. This will save you from bugs at runtime.
Updated(StrSql, 15)
Your Updated Function calls for a String array. 15 is not a string array.
Functions need a datatype for the return.
cmd.Parameters.AddWithValue("?", X)
cmd is not declared.
You can't possible get the error you mention with the above code. It will not even compile, let alone run and produce an error.
It is not very helpful to write a Function that is trying to be generic but is actually very limited.
Let us start with your Update statement.
Dim StrSql = "update student set id=?"
The statement you provided will update every id in the student table to 15. Is that what you intended to do? ID fields are rarely changed. They are meant to uniquely identify a record. Often, they are auto-number fields. An Update command would use an ID field to identify which record to update.
Don't use .AddWithValue. See http://www.dbdelta.com/addwithvalue-is-evil/
and
https://blogs.msmvps.com/jcoehoorn/blog/2014/05/12/can-we-stop-using-addwithvalue-already/
and another one:
https://dba.stackexchange.com/questions/195937/addwithvalue-performance-and-plan-cache-implications
Here is another
https://andrevdm.blogspot.com/2010/12/parameterised-queriesdont-use.html
Since you didn't tell us what database you are using I guessed it was Access because of the question mark. If it is another database change the connection, command and dbType types.
Using...End Using block ensures you connection and command are closed and disposed even if there is an error.
Private ConStr As String = "Your Connection String"
Public Function Updated(StudentNickname As String, StudentID As Integer) As Integer
Dim RetVal As Integer
Using cn As New OleDbConnection(ConStr),
cmd As New OleDbCommand("Update student set NickName = #NickName Where StudentID = #ID", cn)
cmd.Parameters.Add("#NickName", OleDbType.VarChar, 100).Value = StudentNickname
cmd.Parameters.Add("#ID", OleDbType.Integer).Value = StudentID
cn.Open()
RetVal = cmd.ExecuteNonQuery
End Using
Return RetVal
End Function
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Dim RowsUpdated = Updated("Jim", 15)
Dim message As String
If RowsUpdated = 1 Then
message = "Success"
Else
message = "Failure"
End If
MessageBox.Show(message)
End Sub
This code keeps your database code separated from user interface code.
I've this piece of code for inserting records into an Access table.
LastUpdated field is defined as Date/Time at database level. It fails when inserting giving the error
Data type mismatch in criteria expression
I'm using parameterized query which avoid problems with formatting values and it's very weird because I've the same code (with more parameters) to insert records on another table on which LastUpdated is defined in the same way and it's working fine.
Any idea?
SqlQuery = "INSERT INTO History (ActivityID, LastUpdated) VALUES (#p1,#p2)"
With sqlcommand
.CommandText = SqlQuery
.Connection = SQLConnection
.Parameters.AddWithValue("#p1", IDAct)
.Parameters.AddWithValue("#p2", DateTime.Today)
End With
result = sqlcommand.ExecuteNonQuery()
If (result = 1) Then
LabelWarning.Text = "Activity filled"
LabelWarning.BackColor = Color.ForestGreen
LabelWarning.Visible = True
ButtonSave.Visible = False
ButtonBack.Visible = False
ButtonOK.Visible = True
BlockControls()
End If
Maybe the problem is linked to parameter placeholder.
This MSDN doc states that OleDbCommand does not support named parameter (only positional) and the correct placeholder should be "?" and not "#p1".
https://msdn.microsoft.com/en-us/library/yy6y35y8%28v=vs.110%29.aspx
Edit
It turned out in comments that the placeholder have not to be so strictly adherent to the doc syntax. Only the order has to be absolutely preserved.
Explicitly declaring the parameter type however seemed to do the trick:
.Parameters.Add("#p2", OleDbType.DBTimeStamp)
.Parameters("#p2").Value = DateTime.Today
Using Crystal Report 9 and VB.Net
Report is running through stored procedure, I want to pass the data to stored procedure.
How to do it?
In VB6, i did like this.
Report.ReportFileName = REPPATH & "\Detail.rpt"
Report.StoredProcParam(0) = Did
Report.StoredProcParam(1) = Deed
Report.Action = 1
How to do it in vb.net?
I think something like the following should work:
SetDataSource have like 4 overrides that all take an IEnumerable like object as parameter. But this has been done with CR 13 for VS 2010... I hope you will find something like with CR 9.
Dim report As New CrystalReport1
Dim sqla = New SqlDataAdapter()
sqla.SelectCommand.Connection = New SqlConnection(sConnectionString)
sqla.SelectCommand = New SqlCommand("EXEC storedProcName #a, #b, #c")
sqla.SelectCommand.Parameters.Add("#a", paramA)
sqla.SelectCommand.Parameters.Add("#a", paramB)
sqla.SelectCommand.Parameters.Add("#a", paramC)
report.SetDataSource(sqla)
//'If you do not have parameters, you may use :
report.SetDataSource(New SqlDataAdapter("EXEC storedProcName ", sConnectionString))
report.Refresh()
EDIT : sqla.SelectCommand.Parameters.Add("#a", paramA) is depreciated in the CR13 version. sqla.SelectCommand.Parameters.AddWithValue("#a", paramA) is used instead.
I have a problem with something I have done many times but this time it just doesn't work.
This is what what I am trying to do (in Visual Studio 2003 and VB.NET)
Earlier in the code:
Private SaveCustomerInformationCommand As System.Data.SqlClient.SqlCommand
Then this in a setup process:
SaveCustomerInformationCommand = New System.Data.SqlClient.SqlCommand("spSaveCustomerInformation")
SaveCustomerInformationCommand.CommandType = Data.CommandType.StoredProcedure
Dim custIdParameter As System.Data.SqlClient.SqlParameter = SaveCustomerInformationCommand.CreateParameter()
custIdParameter.ParameterName = "#CustId"
custIdParameter.SqlDbType = Data.SqlDbType.Int
custIdParameter.Direction = Data.ParameterDirection.Input
SaveCustomerInformationCommand.Parameters.Add(custIdParameter)
And then later:
SaveCustomerInformationCommand.Parameters.Item("#CustId").Value = myValue
And I get this:
System.IndexOutOfRangeException: An SqlParameter with ParameterName '#CustId' is not contained by this SqlParameterCollection.
Any ideas/solutions?
AFAIK, the "#" is not technically part of the name of the parameter... rather it's what you put into your T-SQL to denote that a parameter name is coming afterwards. So I think you'll want to refer to it like this instead (with no "#") :
SaveCustomerInformationCommand.Parameters.Item("CustId").Value = myValue
You could also try the same thing when you initially insert the parameter name-- although since you're not getting an error there, I'd suspect the accessor call is to blame, not the inserter call.