After trying to work this out for a few hours, I'm beginning to think I can't solve this without your help. Situation:
My stored procudure in SQL Server Management Studio (basically):
SET NOCOUNT ON;
DELETE myTable
... -- Complex join query that deletes duplicates
RETURN ##ROWCOUNT -- The number of rows affected
This procedure works well, if I run it in SSMS the following query is run:
USE myDb
GO
DECLARE #return_value int
EXEC #return_value = [dbo].[StoredProcedure]
#var1 = N'asd',
#var2 = N'fgh',
#var3 = N'jkl'
SELECT 'Return Value' = #return_value
GO
The result is shown as 1 row with name Return Value and value 8700 (this is correct, it's the number of rows that was deleted by the query).
My problems begin in my C#/NHibernate code. In my NHibernate mapping:
<sql-query name="MyStoredProcedureName">
exec dbo.StoredProcedure #var1=:var1, #var2=:var2, #var3=:var3
</sql-query>
In my method where I call the stored procedure:
var deletedEntities = session.GetNamedQuery("MyStoredProcedureName")
.SetString("var1", "asd")
.SetString("var2", "fgh")
.SetString("var3", "jkl")
.ExecuteUpdate();
This results in deletedEntities = -1. I would like it to be deletedEntities = 8700.
I'd like to point out that the following did return the number of affected rows:
var deletedEntities = session.CreateQuery("DELETE Class c WHERE c.var1 = :var1 AND c.var2 = :var2 AND c.var3 = :var3")
.SetString("var1", var1)
.SetString("var2", var2)
.SetString("var3", var3)
.ExecuteUpdate();
Where Class is the class that belongs to the NHibernate mapping.
So, to summarize:
ExecuteUpdate on a SIMPLE session.CreateQuery gives me back the number of affected rows (GOOD)
ExecuteUpdate on a COMPLEX session.GetNamedQuery gives me back -1 (BAD)
This same complex stored procedure gives me back the desired 8700 when I execute it in SSMS (GOOD)
Anyone has an idea of how to fix 2?
Using http://csharptechies.blogspot.co.uk/2013/04/how-to-get-return-value-from-stored.html I got my code working. In the procedure, replaced RETURN ##ROWCOUNT with SELECT ##ROWCOUNT
In the C# code, I replaced .ExecuteUpdate() with .UniqueResult(). UniqueResult returns an object, which will contain the ##ROWCOUNT value (need to convert to int as stated in the link above).
So the solution is a combination (I'd like to use the mapping):
Use SELECT instead of RETURN
Use .UniqueResult() instead of .ExecuteUpdate()
Edit: I got another problem, the query had a timeout whenever there was a large number of records to be removed. I fixed this with the help of other questions, such as SQL Server: Effects of using 'WITH RECOMPILE' in proc definition?.
Use WITH RECOMPILE in a complex query on large tables (my query involved a left outer join, min(id) and group by's, the number of records to be affected are often around 20.000 but can run up to 100.000+ and my table has over 350.000.000 records in it) to avoid timeouts.
Related
In a SQL stored proc i'm inserting a row and hoping to return the IDENTITY INT for the new row, then get it in my C# code.
ALTER PROCEDURE the_proc #val_2 VARCHAR(10), #val_3 VARCHAR(10)
AS
BEGIN
SET NOCOUNT ON
INSERT INTO the_table (field_2, field_3)
OUTPUT INSERTED.field_1
VALUES (#val_2, #val_3)
END
In C# i'm using LINQ but am fuzzy on how to retrieve that OUTPUT value. I tried including it as an OUTPUT parameter in the SQL proc declaration, but couldn't get that working either (exceptions complaining about it not being supplied in the call). The closest i've gotten is in walking into the Db.designer.cs code, where IExecuteResult result ReturnValue contains 0 (not correct) but inspecting the contained Results View (result.ReturnValue Results View) DOES have the outputed value.
key = (int)(db.TheProc(val2,val3).ReturnValue);
key is coming back as 0. I want the IDENTITY INT value from the INSERT.
OUTPUT INSERTED.*
is basically the same thing as doing a select. So this isn't going to show up as an output parameter but rather come back as a result set.
BTW, the ReturnValue should actually be zero which is what you are seeing in this case.
You'll need to change your linq statement so that you capture that result set.
Try this instead (assuming SQL Server) :
ALTER PROCEDURE the_proc
#val_2 VARCHAR(10),
#val_3 VARCHAR(10),
#newKey int OUTPUT
AS
BEGIN
SET NOCOUNT ON
INSERT INTO the_table (field_2, field_3) VALUES (#val_2, #val_3)
SET #newKey = SCOPE_IDENTITY()
END
Once you define your the_proc stored procedure to your LINQ to SQL dbml you can do this:
int? newKey = null;
dataContext.the_proc("val1", "val2", ref newKey);
// newKey.Value now contains your new IDENTITY value
Chris Lively nudged me in the right direction, which is to say re-examining the C# Linq code. The following pulls Field_1 out of the results. Maybe it's a weird way to get there and not the normal idiom, so if anyone has suggestions for something more "correct" please add a comment or answer.
var o = from res in db.MyProc( Val1, Val2 )
select res.Field_1;
int key = o.ToArray()[0];
Thanks.
I am making a windows service to be able to run operations on a sql server database (insert, edit, etc) and invoke Stored Procs.
However, is there a way for me to know the type of the SP? When invoking from C#, I need to knof if it is returning 1 value, or more, or none (so I can use executereader, scalar, etc)?
Thanks
A non-query is usually called with SqlCommand.ExecuteNonQuery(). But it's valid to run it as SqlCommand.ExecuteReader(). The only difference is that the first call to DataReader.Read() returns false for a stored procedure that does not return a resultset.
A rowset is run as SqlCommand.ExecuteReader(). The first call to DataReader.Read() will return true.
A scalar is just a shortcut for a rowset with one column and one row.
So you can use ExecuteReader in all three scenarios.
Though it seems unnecessary for your question, you can retrieve meta-data for the resultset using the fmtonly option. The setting causes the statement to return the column information only; no rows of data are returned. So you could run:
SET FMTONLY ON;
EXEC dbo.YourProc #par1 = 1;
SET FMTONLY OFF;
Executing this as a CommandText from C#, you can examine the column names the stored procedure would return.
To verify that a stored procedure run in this way does not produce any side effects, I ran the following test case:
create table table1 (id int)
go
create procedure YourProc(#par1 int)
as
insert into table1 (id) values (#par1)
go
SET FMTONLY ON;
EXEC dbo.YourProc #par1 = 1;
SET FMTONLY OFF;
go
select * from table1
This did not return any rows. So the format-only option makes sure no actual updates or inserts occur.
We have the ability to execute stored procs from the middle-tier. Basically, in a database table called "SQLJobStep" we have -- among other things -- a varchar(300) column called "StoredProcedure" which holds something like this:
usp_SendReminderEmails #Debug=0, #LoginID
Via the middle-tier, a user clicks on a link to run the proc of their choice. What happens in the backend is a proc ("usp_SQLJobsActionGet") looks up the correct value from the "SQLJobStep.StoredProcedure" column and executes the value above.
This is the part of the code from "usp_SQLJobsActionGet" that executes the above value:
DECLARE #StepId int
DECLARE #LoginID varchar(12)
DECLARE #StoredProcedure varchar(300)
SET #StepId = 74
SET #LoginID = 'Yoav'
SELECT #StoredProcedure = SJS.StoredProcedure
FROM SQLJobStep AS SJS
WHERE SJS.StepId = #StepId
SET #StoredProcedure = ISNULL(#StoredProcedure, '')
IF CHARINDEX('#LoginID', #StoredProcedure) > 0
BEGIN
SET #LoginID = ISNULL(#LoginID, 'UNKNOWN')
SET #StoredProcedure = REPLACE(#StoredProcedure, '#LoginID', '#LoginID = ''' + #LoginID + '''')
END
IF #StoredProcedure != ''
BEGIN
EXEC(#StoredProcedure)
END
Fairly simple stuff....
The above code converts the original value to (and then executes):
usp_SendReminderEmails #Debug=0, #LoginID = 'Yoav'
Here is the issue:
When executing the "usp_SendReminderEmails #Debug=0, #LoginID = 'Yoav'" value nothing happens. No error is returned to the middle-tier. But I know that a value is pulled from the SQLJobStep table because we have other stored procedure values that get pulled and they run fine. (Note that the other values only have the #LoginID parameter, while this has #Debug=0 as well.)
At the same time, if I run the code above (both the gutted code and calling "usp_SQLJobsActionGet" directly) in SQL Management Studio, it works perfectly.
Do you have any advice? I am sure I am missing something very basic.
Thanks.
My advice? Use sp_ExecuteSQL instead of concatenation / replacement:
IF #StoredProcedure != ''
BEGIN
EXEC sp_ExecuteSQL #StoredProcedure, N'#LoginID varchar(12)', #LoginID
END
Overall, though - the EXEC should work; are you sure that #StoredProcedure is not empty?
Thanks for helping. I found the answer to my issue, and as you can guess it had to do with issues beyond what I described originally:
In the usp_SendReminderEmails proc, we call another proc in order to audit each e-mail record that is sent. This auditing proc inserts a record into an audit table and then returns the identity (SELECT TOP 1 SCOPE_IDENTITY()). While it only returns 1 record at a time, it happens to be called in a cursor (in usp_SendReminderEmails) to send out each email at a time (note: this is a SQL Job proc).
What I noticed is that upon executing usp_SendReminderEmails #Debug=0, #LoginID = 'Yoav' in Management Studio, it works fine but there is a warning returned(!):
The query has exceeded the maximum number of result sets that can be displayed in the results grid. Only the first 100 result sets are displayed in the grid.
When calling the proc from the middle-tier, therefore, nothing happened - no error returned, but no processing of usp_SendReminderEmails either. I fixed it by calling the audit proc in an insert into temp table in usp_SendReminderEmails, thereby ensuring it doesn't get returned (since it's only an identity value):
INSERT INTO #AuditData (AuditDataId)
EXEC usp_AuditSave
Hi I have the following SP, however when I use LINQ to SQL it generates 2 multiple recordsets. For my sanity I am trying to fathom out what it is in the stored procedure that is doing this and would like to only return a single recordset... Can any help?
ALTER PROCEDURE [dbo].[CheckToken]
#LoginId int
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #failures INT
SET #failures = (SELECT COUNT(loginid) FROM auditerrorcode WHERE
errorid = 1012 AND loginid = #loginid
AND datecreated > DATEADD(hh, -1, getdate())
)
IF #failures > 10 UPDATE [login] SET [IsDisabled]=1 WHERE loginid = #loginid
SELECT * FROM [Login] WHERE LoginId = #LoginId
END
Execute your procedure stand alone and rule out your not getting two rows because there are two rows being returned for the ID you are passing in. Do this in SQL Managment Studio with a
EXEC dbo.CheckToken 999
Make sure to use the same #LoginID that you are calling from your .NET code.
Sorry Guys....
I looked again in the DBML file generated and deleted the CheckToken method which had multiple result sets defined. I then regenerated and now I get what I expected, one recordset
Looks like the mods I made to the SP has worked.
I'm executing several discrete queries in a single batch against SQL Server. For example:
update tableX set colA = 'freedom';
select lastName from customers;
insert into tableY (a,b,c) values (x,y,z);
Now, I want to capture the result in a DataSet (from select statement) which is easy enough to do...but how do I also capture the "meta" response from that command similar to the way Query Analyzer/SQL Mgt Studio does when it displays the "Messages" tab and diplays something similar to:
(1 Row affected)
(2 Rows Updated)
look into SQL Connection events. I think that's what you're after:
http://msdn.microsoft.com/en-us/library/a0hee08w.aspx
My suggestion would be to use the ##rowcount variable for this. Usually, when I'm doing these kind of commands, if I want to trap both the potential error and the rows affected, I do
declare #rowsAffected int, #error int
select * from sometable
select #rowsAffected = ##rowcount, #error = ##error
if ##error <> 0 goto errorCleanup
Nick is right to suggest ##ROWCOUNT - in fact, as a matter of routine I always use SET NOCOUNT ON, which has a (small) performance benefit - but more importantly, this detail is an implementation detail - so you code shouldn't care...
If you want to return a value (such as number of rows updated), use any of:
return value
output parameter
SELECT statement
The last has the most overhead