If you are going to need to pass messages back and forth, Service Broker is going to work well for you. There is no builtin functionality to block until a row is inserted, however you can use a query subscription
to notify you when the resultset of a query would change. This is built on top of the Service Broker architecture, and I think you need to setup the subscription from .NET, not from SQL.
Going back to Service Broker, if it's local you don't have to worry about a lot, like routing, etc., so your case is the easiest one. The transmission queue is just a holding area for messages that need to be sent, nothing to worry about.
I think the thing that is tripping you up is that you don't need to worry about getting the conversation handles, as you get them anyway.
(1) Have the receiver of messages block on the queue:
declare @status tinyint, @far_handle uniqueidentifier, @myMessage nvarchar(max);
waitfor (
receive @status = status,
@far_handle = conversation_handle,
@myMessage = CONVERT(NVARCHAR(MAX), message_body)
from q1
)
(2) Start a conversation from your service back to itself:
declare @near_handle uniqueidentifier
begin dialog conversation @near_handle
from service s1 to service 's1'
with encryption = off
send on conversation @near_handle ('hello')
Now when you start the conversation in (2), you will get the conversation handle for it's side of the connection, which then you can do what you want with, i.e. insert into a table, etc..
On the blocking side, when a message arrives, you collect the status & message body, as well as the conversation handle, which is the handle for that side of the conversation. You can use this to reply, store it, update a table row with it, etc..
Now the thing is, because at first it was receiving without a conversation handle, because it didn't have one, when the conversation is established, it should put it's conversation_handle in the where clause for the receive.
receive @status = status,
@myMessage = CONVERT(NVARCHAR(MAX), message_body)
from q1
where conversation_handle = @far_handle
Otherwise it will start receiving its own sent messages. This is because you have one service talking to itself on the same queue. You could get around this by using 2 services talking to each other. This is usually a cleaner approach.
This basically eliminates the need for going to sys.conversation_endpoints
, which is really for admin of conversations.
Also, for conversations to end cleanly, you should end them from both sides. Never get yourself into the situation where you need to use end conversation with cleanup
.
To handle multiple conversations handled concurrently, you could use a Service Broker feature called queue activation
. If you didn't need them to be handled concurrently, you wouldn't need this. To use activation, its going to be best to use two services & queues.
Complete Example
(1) Do some setup
create queue srcq
create service src on queue srcq([DEFAULT])
GO
create queue destq
create service dest on queue destq([DEFAULT])
GO
(2) Create a procedure to handle messages received
create procedure messageHandler as
declare @far_handle uniqueidentifier,
@message xml,
@message_type nvarchar(256),
@name varchar(32),
@payload nvarchar(max),
@handler varchar(128)
waitfor (
receive @far_handle = conversation_handle, @message_type = message_type_name, @message = cast(message_body as xml)
from destq
)
if (@message_type = 'http://schemas.microsoft.com/SQL/ServiceBroker/Error')
-- Deal with error
exec dealWithError
else if (@message_type = 'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog')
begin
-- End the Conversation
end conversation @far_handle;
end
else
begin
set @name = @message.value('(/xml/name)[1]', 'varchar(32)');
set @payload = @message.value('(/xml/payload)[1]', 'nvarchar(max)');
if (select ReceiverHandle from ProviderInfo where Name = @name) is null
update ProviderInfo
set ReceiverHandle = @far_handle
where Name = @name;
-- Now Process @name however you want to
-- This basically creates a string, say 'bobHandler', and then executes it as an sp, passing it the payload
set @handler = @name + 'Handler';
exec @handler @payload;
end
GO
(3) Create a Handler for messages associated with the name 'bob'
create procedure bobHandler (@payload nvarchar(max))
as
print 'hello'
GO
(4) Set the destination queue to use activation
alter queue destq
with activation (
status = on,
procedure_name = messageHandler,
max_queue_readers = 10,
execute as 'dbo'
)
GO
(5) On the Sender, start a conversation, store the sending handle, then send a message
declare @near_handle uniqueidentifier
begin dialog conversation @near_handle
from service src to service 'dest'
with encryption = off
-- Store this handle somewhere for future use
merge into ProviderInfo p
using (
select 'bob' as Name, @near_handle as SenderHandle
) t on p.Name = t.Name
when matched then
update set SenderHandle = t.SenderHandle, ReceiverHandle = null
when not matched then
insert (Name, SenderHandle) values (t.Name, t.SenderHandle);
send on conversation @near_handle ('<xml><name>bob</name><payload>89237981273982173</payload></xml>')
GO
Sending the message will cause the message handler to wake up, and call the 'bobHandler' stored procedure. Setting max_queue_readers
to 10 means that 10 messages can be handled concurrently.
If you didn't want to use activation, so one thread at the receiver processing all messages coming in, you could do this by simply turning it off on the destination queue, and changing the 'messageHandler' stored procedure to use wait for (receive)
, and running it's code in a loop.
If all of this is out, because you actually want a human person to call the receiving procedure, forget activation, and try this:
create procedure handleMessage (@name varchar(32))
as
declare @far_handle uniqueidentifier,
@message xml,
@message_type nvarchar(256),
@payload nvarchar(max),
@handler varchar(128),
@loop bit = 1
while (@loop = 1)
begin
-- Wait for a handle with our name
select @far_handle = conversation_handle
from destq
where cast(message_body as xml).value('(/xml/name)[1]', 'varchar(32)') = @name
if (@far_handle is not null)
set @loop = 0
else
waitfor delay '00:00:02'
end
set @loop = 1
while (@loop = 1)
begin
waitfor (
receive @message_type = message_type_name, @message = cast(message_body as xml)
from destq
where conversation_handle = @far_handle
)
if (@message_type = 'http://schemas.microsoft.com/SQL/ServiceBroker/Error')
-- Deal with error
exec dealWithError
else if (@message_type = 'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog')
begin
-- End the Conversation
end conversation @far_handle;
--Exit
set @loop = 0
end
else
begin
set @payload = @message.value('(/xml/payload)[1]', 'nvarchar(max)');
if (select ReceiverHandle from ProviderInfo where Name = @name) is null
update ProviderInfo
set ReceiverHandle = @far_handle
where Name = @name;
-- Now Process @name however you want to
-- This basically creates a string, say 'bobHandler', and then executes it as an sp, passing it the payload
set @handler = @name + 'Handler';
exec @handler @payload;
end
end
GO