I am running into a very interesting bug (or feature).
I have a ADO Command object that makes a call to the database.
The call looks similar to:
cmd.CommandText = "uspMySearch";
cmd.CommmandType = Command.StoredProcedure.
cmd.Parameters.AddWithValue("#SearchBy", searchBy)
// The value of searchBy is: '( FORMSOF (INFLECTIONAL, steve''s) AND FORMSOF (INFLECTIONAL, game) )'
int result = (int)cmd.ExecuteScalar();
// The result returned is 0. I was expecting 1.
When I execute the same query in SQL/Query Analyzer, I get a different result.
The sql looks like this:
EXEC uspMySearch #SearchBy = '( FORMSOF (INFLECTIONAL, steve''s) AND FORMSOF (INFLECTIONAL, game) )'
// The result returned is 1. This is the expected result.
In order to confirm I was calling the correct stored procedure, I modifed uspMySearch to return a random number. I was calling the right Sp!
Anyone have any insights as to whats going on here?
Thanks.
Steve
Environment
SQL/Server 2008 R2
.NET 4.0
I believe you've got an extraneous single quote in your searchBy variable in the C#, try:
// note steve's rather than steve''s, shouldn't need to escape the single quote
string searchBy =
"( FORMSOF (INFLECTIONAL, steve's) AND FORMSOF (INFLECTIONAL, game) )";
...
cmd.Parameters.AddWithValue("#SearchBy", searchBy);
int result = (int)cmd.ExecuteScalar();
Another possibility, raised by #FlyingStreudel in a comment, is that your stored procedure is not using SELECT to return the value. If you're using RETURN, you should instead try:
var retval = new SqlParameter("#RETVAL", SqlDbType.Int);
retval.Direction = ParameterDirection.ReturnValue;
cmd.Parameters.Add(retval);
...
cmd.ExecuteNonQuery();
int result = Convert.ToInt32(retval.Value);
ExecuteScalar is not for executing Stored Procedures - for that you you use ExecuteNonQuery combined with Parameters with direction Output / InputOutput / Return.
http://msdn.microsoft.com/en-us/library/system.data.parameterdirection.aspx
http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.aspx
EDIT - perhaps the following works too (although wouldn't recommend it):
cmd.CommandText = "uspMySearch";
cmd.CommmandType = Command.StoredProcedure;
cmd.Parameters.AddWithValue("#SearchBy", searchBy).Direction = ParameterDirection.InputOutput;
int result = (int)cmd.ExecuteScalar();
var Result = cmd.Parameters ["#SearchBy"].Value;
I'm going to throw out a wild guess, but I'm guessing it has something to do with your cast. Can you change the code to:
object result = cmd.ExecuteScalar();
And use the debugger to inspect the result variable to see what it is (and what it's type is). This may give you (or me) a clue as to what's going on.
When you say the result returned is 1 - how is it returned; resultset or printed out?
Normally when using ExecuteScalar() I use a query which selects a single column value (i.e. of the format SELECT ... FROM ...).
Related
I create a SQL stored procedure
create proc p1
(
#name1 nvarchar(50),
#rErr int OUTPUT
)
as
begin Transaction
insert into test (name1)
values (#name1)
if #name1 = 'n100'
Begin
Rollback Transaction
Set #rErr=50001
Return #rErr
End
commit Transaction
How can I get the #rErr value in C#?
Accessing output parameters is a little awkward; you need to add them to the parameters collection in the usual way, with with a .Direction of Output. Then you can read the .Value of the parameter object after executing the method. However: it is much easier to use select and process it as a result. Note that return values can be done in a similar way, but with the appropriate .Direction. The fact that you both output and return it, in this case, makes it even more fun... I'd just use the output part, personally. Or ... throw an exception (raiserrror).
Something like:
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = "p1";
cmd.CommandType = CommandType.StoredProcedure;
var name = cmd.CreateParameter();
name.ParameterName = "#name1";
name.Value = "abc"; // etc
cmd.Parameters.Add(name);
var err = cmd.CreateParameter();
err.ParameterName = "#rErr";
err.Direction = ParameterDirection.Output;
cmd.Parameters.Add(err);
cmd.ExecuteNonQuery();
if (err.Value is int i)
{
// error i happened
}
}
However: if you'd just used:
raiserror (50001, 16, 1) -- severity needs to be at least 16 here; typically = 16
or (in more recent SQL Server versions):
throw 50001, 'oops', 1
you can get a similar result much more easily; this will result in an Exception direction from the ADO.NET layer.
(note that you should add the custom error message formally to sysmessages when using raiserror - throw doesn't require that step)
If you used the throw (or raiserror) approach, and removed the output parameter, this entire piece of code could become, with some help from Dapper:
conn.Execute("p1", new { name1 = "abc" }, commandType: CommandType.StoredProcedure);
which is a lot easier to get right!
You don't need transaction for a single insert, you can refactor your SP like following. To return the code as output parameter, you can simply use RETURN(50001)
CREATE PROC P1 (#name1 NVARCHAR(50),
#rErr INT output)
AS
IF #name1 <> 'n100'
BEGIN
INSERT INTO test(name1)
VALUES (#name1)
RETURN(0)
END
RETURN(50001)
I am trying to use interceptor to add WITH (NO LOCK) for some queries (not for all queries, so ReadUncommitted is not a choice).
Code looks like this:
var rawSql = sql.ToString();
if (!rawSql.Contains(IQueryOverExtensions.QueryHintNoLockString))
return sql;
var noWhere = rawSql.Substring(0, rawSql.IndexOf(WhereKeyword, StringComparison.InvariantCulture));
var from = noWhere.Substring(noWhere.IndexOf(FromKeyword, StringComparison.InvariantCulture));
var fromWithNoLock = from.Replace("_ ", $"_ {WithNoLock} ");
var sqlWithNoLock = rawSql.Replace(from, fromWithNoLock);
return base.OnPrepareStatement(new SqlString(sqlWithNoLock));
Here, I take part form FROM clause to WHERE clause and to each alias add WITH (NO LOCK)
The issue is, that the final SQL's parameters are all "?" and exception is thrown that SQL is not valid. Why parameters are not filled in and how to fix it?
Thanks in advance
Finally I got it. I had to get number of parameters by calling sql.GetParameterCount() and then replace all question marks with #p0, #p1, #p2, etc.
I have an C# method to execute a SQL job. It executes the SQL job successfully.
And the code works perfect.
And I'm using standard SQL stored procedure msdb.dbo.sp_start_job for this.
Here is my code..
public int ExcecuteNonquery()
{
var result = 0;
using (var execJob =new SqlCommand())
{
execJob.CommandType = CommandType.StoredProcedure;
execJob.CommandText = "msdb.dbo.sp_start_job";
execJob.Parameters.AddWithValue("#job_name", "myjobname");
using (_sqlConnection)
{
if (_sqlConnection.State == ConnectionState.Closed)
_sqlConnection.Open();
sqlCommand.Connection = _sqlConnection;
result = sqlCommand.ExecuteNonQuery();
if (_sqlConnection.State == ConnectionState.Open)
_sqlConnection.Close();
}
}
return result;
}
Here is the sp which executing inside the job
ALTER PROCEDURE [Area1].[Transformation]
AS
BEGIN
SET NOCOUNT ON;
SELECT NEXT VALUE FOR SQ_COMMON
-- Transform Master Data
exec [dbo].[sp_Transform_Address];
exec [dbo].[sp_Transform_Location];
exec [dbo].[sp_Transform_Product];
exec [dbo].[sp_Transform_Supplier];
exec [dbo].[sp_Transform_SupplierLocation];
-- Generate Hierarchies and Product References
exec [dbo].[sp_Generate_HierarchyObject] 'Area1',FGDemand,1;
exec [dbo].[sp_Generate_HierarchyObject] 'Area1',RMDemand,2;
exec [dbo].[sp_Generate_Hierarchy] 'Area1',FGDemand,1;
exec [dbo].[sp_Generate_Hierarchy] 'Area1',RMDemand,2;
exec [dbo].[sp_Generate_ProductReference] 'Area1',FGDemand,1;
exec [dbo].[sp_Generate_ProductReference] 'Area1',RMDemand,2;
-- Transform Demand Allocation BOM
exec [Area1].[sp_Transform_FGDemand];
exec [Area1].[sp_Transform_FGAllocation];
exec [Area1].[sp_Transform_RMDemand];
exec [Area1].[sp_Transform_RMAllocation];
exec [Area1].[sp_Transform_BOM];
exec [Area1].[sp_Transform_RMDemand_FK];
-- Transform Purchasing Document Data
exec [dbo].[sp_Transform_PurchasingDoc];
exec [dbo].[sp_Transform_PurchasingItem];
exec [dbo].[sp_Transform_ScheduleLine];
exec [dbo].[sp_CalculateRequirement] 'Area1'
exec [dbo].[sp_Create_TransformationSummary] 'Area1'
-- Trauncate Integration Tables
exec [dbo].[sp_TruncateIntegrationTables] 'Area1'
END
The problem is, even the job is executed successfully or not it always returns -1. How can I identify whether job is successfully executed or not.
After running msdb.dbo.sp_start_job the return code is mapped to an output parameter. You have the opportunity to control the parameter's name prior to execution:
public int StartMyJob( string connectionString )
{
using (var sqlConnection = new SqlConnection( connectionString ) )
{
sqlConnection.Open( );
using (var execJob = sqlConnection.CreateCommand( ) )
{
execJob.CommandType = CommandType.StoredProcedure;
execJob.CommandText = "msdb.dbo.sp_start_job";
execJob.Parameters.AddWithValue("#job_name", "myjobname");
execJob.Parameters.Add( "#results", SqlDbType.Int ).Direction = ParameterDirection.ReturnValue;
execJob.ExecuteNonQuery();
return ( int ) sqlCommand.Parameters["results"].Value;
}
}
}
You need to know the datatype of the return code to do this - and for sp_start_job, it's SqlDbType.Int.
However, this is only the results of starting the job, which is worth knowing, but isn't the results of running your job. To get the results running of your job, you can periodically execute:
msdb.dbo.sp_help_job #jobName
One of the columns returned by the procedure is last_run_outcome and probably contains what you're really interested in. It will be 5 (unknown) while it's still running.
A job is usually the a number of steps - where each step may or may not be executed according to the outcome of previous steps. Another procedure called sp_help_jobhistory supports a lot of filters to specify which specific invocation(s) and/or steps of the job you're interested in.
SQL likes to think about jobs as scheduled work - but there's nothing to keep you from just starting a job ad-hoc - although it doesn't really provide you with much support to correlate your ad-hoc job with an instance is the job history. Dates are about as good as it gets (unless somebody knows a trick I don't know.)
I've seen where the job is created ad-hoc job just prior to running it, so the current ad-hoc execution is the only execution returned. But you end up with a lot of duplicate or near-duplicate jobs laying around that are never going to be executed again. Something you'll have to plan on cleaning up afterwards, if you go that route.
A note on your use of the _sqlConnection variable. You don't want to do that. Your code disposes of it, but it was apparently created elsewhere before this method gets called. That's bad juju. You're better off just creating the connection and disposing of it the same method. Rely on SQL connection pooling to make the connection fast - which is probably already turned on.
Also - in the code you posted - it looks like you started with execJob but switched to sqlCommand - and kinda messed up the edit. I assumed you meant execJob all the way through - and that's reflected in the example.
From MSDN about SqlCommand.ExecuteNonQuery Method:
For UPDATE, INSERT, and DELETE statements, the return value is the number of rows affected by the command. When a trigger exists on a table being inserted or updated, the return value includes the number of rows affected by both the insert or update operation and the number of rows affected by the trigger or triggers. For all other types of statements, the return value is -1. If a rollback occurs, the return value is also -1.
In this line:
result = sqlCommand.ExecuteNonQuery();
You want to return the number of rows affected by the command and save it to an int variable but since the type of statement is select so it returns -1. If you test it with INSERT or DELETE or UPDATE statements you will get the correct result.
By the way if you want to get the number of rows affected by the SELECT command and save it to an int variable you can try something like this:
select count(*) from jobs where myjobname = #myjobname
And then use ExecuteScalar to get the correct result:
result = (int)execJob.ExecuteScalar();
You need to run stored proceedure msdb.dbo.sp_help_job
private int CheckAgentJob(string connectionString, string jobName) {
SqlConnection dbConnection = new SqlConnection(connectionString);
SqlCommand command = new SqlCommand();
command.CommandType = System.Data.CommandType.StoredProcedure;
command.CommandText = "msdb.dbo.sp_help_job";
command.Parameters.AddWithValue("#job_name", jobName);
command.Connection = dbConnection;
using (dbConnection)
{
dbConnection.Open();
using (command){
SqlDataReader reader = command.ExecuteReader();
reader.Read();
int status = reader.GetInt32(21); // Row 19 = Date Row 20 = Time 21 = Last_run_outcome
reader.Close();
return status;
}
}
}
enum JobState { Failed = 0, Succeeded = 1, Retry = 2, Cancelled = 3, Unknown = 5};
Keep polling on Unknown, until you get an answer. Lets hope it is succeeded :-)
When I execute the following query in SSMS I get the expected result i.e. '1'
SELECT TSID
FROM tblTimesheets
WHERE TSUser = 'PJW' AND TSDate = '2012-01-18';
However, when the SqlCommand is produced by the code in my application the ExecuteScalar fails (it simply causes the method to exit with no error message).
public int GetID(string paramUser, DateTime paramDate)
{
string strSql = "SELECT TSID " +
"FROM tblTimesheets " +
"WHERE TSUser = #TSUser AND TSDate = #TSDate;";
string strConnection = BuildConnectionString();
SqlConnection linkToDB = new SqlConnection(strConnection);
linkToDB.Open();
SqlCommand sqlCom = new SqlCommand(strSql, linkToDB);
sqlCom.Parameters.Add("#TSUser", SqlDbType.Text);
sqlCom.Parameters.Add("#TSDate", SqlDbType.Date);
sqlCom.Parameters["#TSUser"].Value = paramUser;
sqlCom.Parameters["#TSDate"].Value = paramDate;
int intResult = (Int32)sqlCom.ExecuteScalar();
linkToDB.Close();
return intResult;
}
I've stepped through the code and can confirm the parameters are PJW and 2012-01-18 as required, but the ExecuteScalar returns any data, which I know should be there based on my comparable query in SSMS.
Please assist.
Instead of SqlDbType.Text try any of the following, depending on the type of the column:
SqlDbType.VarChar
SqlDbType.NVarChar
SqlDbType.NText
SqlDbType.NChar
SqlDbType.Char
When the parameter is of DB type date, it is a good practice to defensively strip the time part on setting the parameter, like this:
sqlCom.Parameters.Add("#TSDate", SqlDbType.Date);
sqlCom.Parameters["#TSDate"].Value = paramDate.Date;
Please let me know if this does not help, and I'll remove my answer.
You say the ExecuteScalar fails with no error message. Wrap your code in a try-catch block to make sure any exceptions that ExecuteScalar() might be throwing are caught.
Other than that try and do as others have suggested and view the SQL produced using SQL Profiler, then run that SQL in SSMS to compare results.
SELECT TSID
FROM tblTimesheets
WHERE TSUser = 'PJW' AND TSDate = '2012-01-18';
Here U r passing the exact date as parameter
Where as while passing the parameter in to the stored procedure you are passing
"DateTime paramDate"
A date time variable
May be you need to parse to exact date format as supported by the stored procedure
i.e you need to format the paramDate variable to 'YYYY-mm-DD'
I am not sure.. Try it.. and reply if it helps or not !
Does anyone know how can I read the output variable from .net c#?
Example:
If I have the following stored proc which will return the output variables (#customer_id, #customer_name, #customer_address, #customer_age) instead of the select variable, how can I read the output variable with the following?
mySqlCommand.CommandText = "EXEC app_customers #name=" + sName.Text;
mySqlConnection.Open();
SqlDataReader mySqlDataReader = mySqlCommand.ExecuteReader();
while (mySqlDataReader.Read())
{
}
When the result is a single value (or if you're just interested in the first value in the first column), use the method ExecuteScalar.
It returns an object, simply cast it to the expected type.
int id = (int)mySqlCommand.ExecuteScalar();
Note: the way you're invoking a procedure is not the normal way to do it. Set the command to reference the stored procedure, then add appropriate parameters to the command.Parameters collection. Invoking the procedure using "exec ..." is not a best practice and may even leave you vulnerable. If you need more info on executing such a call, start here.
Edit:
If it is truly an output parameter you need to capture (I believe I misread your question), then the above paragraph is even more applicable. Consider this approach:
mySqlCommand.CommandText = "app_customers";
mySqlCommand.CommandType = System.Data.CommandType.StoredProcedure;
mySqlCommand.Parameters.AddWithValue("#name", theValue);
var customerIdParam = mySqlCommand.Parameters.Add("#customer_id", System.Data.SqlDbType.Int);
customerIdParam.Direction = System.Data.ParameterDirection.Output;
// add more parameters, setting direction as appropriate
mySqlCommand.ExecuteNonQuery();
int customerId = (int)customerIdParam.Value;
// read additional outputs