I am currently having problems calling a stored procedure async from within a insert-update-trigger. For this I m using the service broker.
--message type
CREATE MESSAGE TYPE [TheMessage] VALIDATION = NONE
--contract
CREATE CONTRACT [TheContract] ([TheMessage] SENT BY ANY);
--queue
CREATE QUEUE [TheQueue] WITH ACTIVATION
(STATUS = ON, MAX_QUEUE_READERS = 1,
PROCEDURE_NAME = TheStoreProcedure,
EXECUTE AS OWNER);
--service
CREATE SERVICE [TheService] ON QUEUE [TheQueue] ([TheContract]);
Within the trigger:
DECLARE #Handle UNIQUEIDENTIFIER;
BEGIN DIALOG CONVERSATION #Handle
FROM SERVICE [TheService]
TO SERVICE 'TheService'
ON CONTRACT [TheContract]
WITH ENCRYPTION = OFF;
SEND ON CONVERSATION #Handle
MESSAGE TYPE [TheMessage](N'some data');
Within the stored procedure:
DECLARE #Handle UNIQUEIDENTIFIER;
DECLARE #MessageType SYSNAME;
RECEIVE TOP (1)
#Handle = conversation_handle,
#MessageType = message_type_name
FROM [TheQueue];
IF(#Handle IS NOT NULL)
BEGIN
-- some statements
END
This setup doesn't seem to work. The trigger does not throw any errors so I assume the message is queued. But the receive within the stored doesn't seem to work. None of my statements are being executed.
Check if the message isn't retained in sys.transmission_queue. Transmission_status column should explain why the message isn't delivered.
Check if the message is in the queue: SELECT ... FROM [TheQueue]. If the message is there and the procedure didn't activate check the queue's is_receive_enabled status in sys.service_queues. If the queue is disabled, you probably rolled back 5 receives in a row during testing and triggered the poison message mechanism.
If the queue is enabled, check the queue monitors status, see Understanding Queue Monitors.
If the message is neither in the queue nor in transmission queue, it must been consumed by the activated procedure. Verify your ERRORLOG for any error output. Disable activation, send a message again, then run the procedure manually from an SSMS query window see if you get any error message.
Mae sure your activated procedure does not fall into the traps of the EXECUTE AS context. See Why does feature … not work under activation? and Call a procedure in another database from an activated procedure
Ok, thanks for your answers, I fixed it.
The problem was that the service broker was disabled..
USE AdventureWorks
GO
ALTER DATABASE AdventureWorks SET SINGLE_USER WITH ROLLBACK IMMEDIATE
ALTER DATABASE AdventureWorks SET ENABLE_BROKER
ALTER DATABASE AdventureWorks SET MULTI_USER
GO
Related
We are trying to implement a system that look like this diagram:
In step 1, the User Application will call a Stored Procedure to call the service (begin the dialog conversation). And we need an ID to identify that request.
After that, the User Application will start making periodical request to the Reply "Queue" using Receive statement with Where clause (I put Queue in the double quote, because in fact it doesn't really work like a queue).
Here, the problem we have is that, how can we maintain a unique ID throughout the process (step 2 and 3), and that ID is somewhere outside of Message Body, because we want to use Where clause of Receive. The conversation_handle could not help us, because they are different from each endpoint.
Is there any ID that persist from the begin of the dialog until END CONVERSATION?
"Periodically ask for a response for a specific request"
You cannot do that. Your application must dequeue responses and handle them, as they become available, all of them. Any architecture that attempts to poll for specific messages will fail. This is a fundamental tenet of Service Broker.
Also you do not "ask periodically" in Service Broker. You have WAITFOR (RECEIVE ...) exactly to replace pooling 'periodically'.
We use this syntax:
BEGIN DIALOG CONVERSATION #Handle
FROM SERVICE #InitiatorService
TO SERVICE #TargetService
ON CONTRACT #OnContract
WITH ENCRYPTION = OFF;
The variable #Handle will after this statement contain a uniqueidentifier which will remain the same during the entire conversation. On the receiving end, we have this:
RECEIVE TOP(1)
#Handle = [conversation_handle],
#Type = [message_type_name],
#Message = [message_body]
FROM
[TargetQueue]
Which get the same value for #Handle as was created by the sender.
We have pretty much just implemented this: http://rusanu.com/2007/12/03/resending-messages/ and it works great.
i wrote a procedure in which i tried to send mail using below command.
EXEC Sp_send_cdontsmail 'from','to','Test','TEST DATA'
After executing its showing "Command(s) completed successfully."
but i am not getting any mail.please help me on this.
You need to configure Database Mail and then use sp_send_dbmail to send mail. This is a supported procedure, part of the SQL Server.
PS. I am aware that out there some code sample circulates that advocates something along the lines of:
CREATE PROCEDURE [dbo].[sp_send_cdontsmail]
...
EXEC #hr = master..sp_OACreate 'CDONTS.NewMail', #MailID OUT
EXEC #hr = master..sp_OASetProperty #MailID, 'From',#From
EXEC #hr = master..sp_OASetProperty #MailID, 'Body', #Body
...
This is horrible code. As one can easily see there is absolutely 0 (zero, nada, zip) error checking in this code. Any failure will be reported as 'success'. COM interop will not raise T-SQL notification messages, is all in the HRESULT, which goes unchecked. Steer away from such code.
I'm trying to learn the basics of Service Broker, and have created an application originally based on the SSMS template. However I can't send a message to my queue. It just says the message type is not part of the service contract.
The absolute bare minimum I need to recreate this is the following batch:
USE [test_db]
GO
CREATE MESSAGE TYPE [test_message]
AUTHORIZATION [dbo]
VALIDATION = WELL_FORMED_XML
GO
CREATE CONTRACT [test_contract]
AUTHORIZATION [dbo] (
[test_message] SENT BY ANY
)
GO
CREATE QUEUE [dbo].[test_queue]
WITH STATUS = ON
,RETENTION = OFF
--,ACTIVATION (
-- STATUS = ON
-- ,PROCEDURE_NAME = [dbo].[test_activator]
-- ,MAX_QUEUE_READERS = 1
-- ,EXECUTE AS N'dbo'
--)
ON [PRIMARY]
GO
CREATE SERVICE [test_service]
AUTHORIZATION [dbo]
ON QUEUE [dbo].[test_queue] (
[test_contract]
)
GO
BEGIN TRANSACTION
DECLARE #dialog_handle UNIQUEIDENTIFIER
BEGIN DIALOG #dialog_handle
FROM SERVICE test_service
TO SERVICE N'test_service';
SEND ON CONVERSATION #dialog_handle
MESSAGE TYPE test_message (N'<test />');
END CONVERSATION #dialog_handle;
COMMIT TRANSACTION
GO
...which yields:
Msg 8431, Level 16, State 1, Line 10
The message type 'test_message' is not part of the service contract.
I only need to be able to send asynchronous messages within the same database. There are no remote connections to consider, and I don't strictly even need to handle a reply.
I've checked and double-checked that the message type, contract, queue and service all exist and the properties of the contract says that the message type is included
What am I missing?
As Denis already answered, you're missing the ON CONTRACT test_contract.
If you omit it then the DEFAULT contract is used. Every database has a contract named DEFAULT which has one message type, also named DEFAULT. The DEFAULT contract is used when you omit any contract in BEGIN DIALOG and the DEFAULT message type is used when you omit the message type in SEND:
BEGIN DIALOG #dialog_handle
FROM SERVICE test_service
TO SERVICE N'test_service'; <-- will use the DEFAULT contract
SEND ON CONVERSATION #dialog_handle
(N'<test />'); <-- will use the DEFAULT message type
DEFAULT message type has no validation. The DEFAULT contract binds the DEFAULT message type to both initiator and target (both can send the message in this contract, ie. SENT BY ANY). Contract and message type names are always case sensitive, irrelevant of database collation, so the name DEFAULT is case sensitive.
Try this:
BEGIN DIALOG #dialog_handle
FROM SERVICE test_service
TO SERVICE N'test_service'
ON CONTRACT test_contract;
Let say I have two instances of the same app interacting with a backend service in Service Broker. How can each instance know to handle only conversations it initiated and ignore the rest? If I recall correctly, every RECEIVE will remove the message from the queue.
Here's an example:
-- Assume the SquareService return the square of the number sent to it
-- Instance 1
BEGIN DIALOG #Conversation1
FROM SERVICE InitService
TO SERVICE 'SquareService'
ON CONTRACT (MyContract)
WITH ENCRYPTION = OFF;
SEND ON CONVERSATION #Conversation1 MESSAGE TYPE MyMessageType('1');
-- Instance 2
BEGIN DIALOG #Conversation2
...;
SEND ON CONVERSATION #Conversation2 MESSAGE TYPE MyMessageType('2');
Now who should I write the RECEIVE statement so that Instance 1 will correctly get 1 and Instance 2 get 4 back?
You are already using a Conversation Group.
Is this not sufficient for your needs when Receiving the messages?
-> using GET CONVERSATION GROUP and RECEIVE together
you can read more about it here: http://technet.microsoft.com/en-us/library/ms166131%28v=sql.105%29.aspx
and also here Sql Server Service Broker Conversation Groups
I'm assuming you have an InitQueue associated with your InitService. You can use a WHERE clause with RECEIVE to listen for messages on the same conversation:
WAITFOR (RECEIVE #response = CONVERT(xml, message_body)
FROM InitQueue -- InitService setup to use InitQueue?
WHERE conversation_handle = #Conversation1
I need to use RAISERROR to throw a message(pop up message) and commit that transaction.Is there any option?
For severity 18 the transaction got rollback.I have changed the severity to 10 and tried like
RAISERROR('Your Reference Number is %s',10,0,#param);
this but it commits the transaction but doesnt show message.
What i need here is Message has to be thrown and transaction has to be commited
Any suggestion?
Don't use exceptions to pass back "OK" messages. You wouldn't in a c# or java program. Exception means "I've aborted because SHTF"
You'd use thsi to return meaningful data
SELECT 'Your Reference Number is ' + #param
In a typical template (from my answer Nested stored procedures containing TRY CATCH ROLLBACK pattern?)
SET XACT_ABORT, NOCOUNT ON
BEGIN TRY
BEGIN TRANSACTION
[...Perform work, call nested procedures...]
COMMIT TRANSACTION
SELECT 'Your Reference Number is ' + #param
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0
ROLLBACK TRANSACTION
RAISERROR [rethrow caught error using #ErrorNumber, #ErrorMessage, etc]
END CATCH
RAISERROR with severity above 10 will be treated like an exception by the ADO.Net client. Depending on how your call context looks like, this may or may not rollback the transaction. If you use a SqlTransaction or a TransactionScope in the client, or a BEGIN TRY/BEGIN CATCH block on the server side, this will likely roll back the transaction. Point is that RAISERROR neither commits nor rolls back the transaction, is your own code that rolls back or commits and we cannot know what you're doing from your post.
RAISERROR with severity bellow 10 will be considered a informational message and not cause an exception. See Database Engine Error Severities. This is probably why you say that the 'it doesn't show the message' (whatever that means). Client side frameworks treat the informational messages differently, for instance ADO.Net will raise an SqlConnection.InfoMessage event on the connection but will not raise an exception. You probably don't have anything set up in your application for this event and your code is simply ignoring the info messages. For example how to use the InfoMessage event see Connection Events (ADO.NET)
It sounds like you need to use the WITH NOWAIT parameter for RAISERROR - this will output it to the message window immediately:
RAISERROR('Your Reference Number is %s',10,0,#param) WITH NOWAIT