Handling SQL Errors in Try-Catch blocks using the WCF SQL Adaptor

At times  I am required to raise business rule type errors or T-SQL errors back to the BizTalk application from a stored procedure. This blog is about issues I encountered  when using a typed stored procedure schema.

The basic structure of the stored procedure is similar to below. The SQL statements are normally wrapped inside in a Try-Catch block with any business errors raised by the RAISERROR statement.

Create PROCEDURE [dbo].[MySproc]

@RequestID int
AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

  begin try

        select @RequestID    
        
        --RAISERROR  ( 'This is my raised error',  16,1)        
        --select 1/0    
                
    
        
        return 0
    end try
    begin catch
        --error handler
        select     ERROR_NUMBER() AS ExceptionErrorNumber
                ,ERROR_MESSAGE() AS ExceptionErrorMessage;
            
        return 1    
    end catch;
END

Below is the generated typed schema using the Consume Adaptor Service wizard. The first element StoredProcedureResultSet0 is the returned dataset and second element StoredProcedureResultSet1 is the dataset representing the error structure.

image

 

When dropping a test message in, the response message looks like this below:

image

As expected the StoredProcedureResultSet0 contains the recordset returned. Now if I modify the stored procedure to raise an error as highlighted below.

alter PROCEDURE [dbo].[MySproc]
@RequestID int
AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

  begin try

        select @RequestID            
        RAISERROR  ( 'This is my raised error',  16,1)        
        --select 1/0    
        return 0
    end try
    begin catch
        --error handler
        select     ERROR_NUMBER() AS ExceptionErrorNumber
                ,ERROR_MESSAGE() AS ExceptionErrorMessage;
            
        return 1    
    end catch;
END

The following message will be received:

image

Notice the StoredProcedureRecordSet0 has the recordset value returned from the select statement and  StoredProcedureRecordSet1 has the error message that was thrown from the RASIERROR statement. This is what you would expect.

Now look what happens if you cause a SQL error.  I generated an error by dividing 1 by zero as highlighted below which will be caught by the Begin-Catch statement block.

alter PROCEDURE [dbo].[MySproc]
@RequestID int
AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

  begin try

        select @RequestID            
        --RAISERROR  ( 'This is my raised error',  16,1)        
        select 1/0    
        return 0
    end try
    begin catch
        --error handler
        select     ERROR_NUMBER() AS ExceptionErrorNumber
                ,ERROR_MESSAGE() AS ExceptionErrorMessage;
            
        return 1    
    end catch;
END

The response message gets suspended with the following error:

The adapter failed to transmit message going to send port “WcfSendPort_SqlAdapterBinding_TypedProcedures_dbo_Custom” with URL “mssql://.//Prototype?”. It will be retransmitted after the retry interval specified for this Send Port. Details:”Microsoft.ServiceModel.Channels.Common.AdapterException: The ResultSet returned as part of the Typed Stored Procedure or Typed Polling invocation did not match the metadata available. If this Stored Procedure or Polling Statement can return a variable number of result sets, consider using the un-typed Stored Procedure or un-typed Polling operation instead

The reason for the suspended message is when a SQL error is raised, the error recordset is placed in another StoredProcedureRecordSet array starting at element 2 as shown below:

image

From this point on any further requests that generate any SQL errors will now be placed in the StoredProcedureRecordSet2 as shown until the send port is unlisted and restarted.

image

image

 

To solve this issue there are two options available.  The first option is described below.

1. Modify the generated SQL WCF schema to include a second StoredProcedureResultSet2 as shown to capture the SQL errors.

image

 

2. Create a map with an in-line XSLT functiod to map any one of the error elements to the destination schema.

image

The XSLT script is shown here which uses wildcards in the path leading up to the exception errors.

<Error>
<Number><xsl:value-of select="/*/*/*/*[local-name()='ExceptionErrorNumber' and namespace-uri()='http://schemas.microsoft.com/Sql/2008/05/ProceduresResultSets/dbo/CatchExceptions']"/></Number>
<Message><xsl:value-of select="/*/*/*/*[local-name()='ExceptionErrorMessage' and namespace-uri()='http://schemas.microsoft.com/Sql/2008/05/ProceduresResultSets/dbo/CatchExceptions']"/></Message>
</Error>

 

The second option is to develop a custom pipeline component using the XML Translator Stream to rename the element names that are greater than 1 . I will continue this in part 2 of this blog.

Enjoy.

Creating a composite response schema for the BizTalk SQL WCF Adapter

I had a scenario where I was requesting several different complex xml documents from a SQL database to a BizTalk application. Returning xml from the stored procedures were used because of the relational structure of the data and to avoid having to use multiple maps to get to the final xml structure.

Instead of having to create new send ports for every query because of the response schemas where different for each request, I decided to use a composite schema that allowed me to use a single send port.

Below are the steps I used to go about developing the solution for BTS2009 and SQL2008. I will use the sample database AdventureWorks and will use a simple xml structure for each entity involved.

1. Create the required stored procedures

Create procedure [BTS].[Sel_Contacts_XML]

as

WITH XMLNAMESPACES ('http://BT.Composite.Schemas.WcfSQL/ContactResponse' as nsql1)

select Title,FirstName,LastName,EmailAddress

from Person.Contact

for XML PATH('nsql1:Contact')

 

Create procedure [BTS].[Sel_Products_XML]

as

WITH XMLNAMESPACES ('http://BT.Composite.Schemas.WcfSQL/ProductResponse' as nsql2)

select ProductNumber,Name,ListPrice

from Production.Product

for XML PATH('nsql2:Product')

 

2. Create the schemas for each stored procedure in the BizTalk project. Ensure that the target namespace matches the namespace defined in the stored procedure. For this example I called them ContactResponse and ProductResponse.

3. Create a new schema that will import the above schemas. This schema will be used as the common composite response schema for each of the WCF SQL requests.

image

Below is the final composite schema with the imported ContactResponse and ProductResponse schemas. Note that the target namespace and the root element of this composite schema will be specified in the WCF adaptor properties latter on.

image

4. Create the orchestration to call the stored procedures. Note there is only one send port but there is a separate operation for each stored procedure.  Each operation was created manually and the name of the operation is important as it is used in the soap action header configuration latter.

For this demonstration the orchestration requested all the contacts first from the database and wrote the response to a file, then a second request was made to the database for the products and the response written to a file also.

image

5. Once the application is deployed to BizTalk, the WCF Custom adaptor must be configured.

When using stored procedures that output XML, the action property must be defined as following. XmlProcedure/<schema_name>/<procedure_name>

The operation name must match the name of the operation inside the orchestration

image

 

Below is the Action operation. Note the two operations that match port operation in the orchestration.

<BtsActionMapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

  <Operation Name="SelContacts_OP" Action="XmlProcedure/BTS/Sel_Contacts_XML" />

  <Operation Name="SelProducts_OP" Action="XmlProcedure/BTS/Sel_Products_XML" />

</BtsActionMapping>

Next the binding properties for using stored procedures outputting xml must be set to the target namespace and the root name of the composite schema that was created.

image

 

This is sample output extract of the first request for the Contacts.

image

And this is the output from the second request for the products.

image