U2U Article: "Consuming WebServices in SQL Server 2005"Copyright © 2004 by U2U nv/sa, Belgium. All rights reserved.
Please address any questions or suggestions to U2U.

Consuming WebServices in SQL Server 2005

Nico Jacobs
U2U nv/sa

Introduction

Sql Server 2005 offers the functionality of writing functions, stored procedures, aggregates, triggers and types in .NET. In this way, we can ‘wrap’ .NET functionality in a database function, procedure or type. In an earlier article, we demonstrated this principle by wrapping regular expression functionality in a CLR function. In this article, we illustrate the same principle, but this time by consuming a web service. We will build a function in the AdventureWorks demo database, which converts an amount in Euros to an amount in US Dollars, fetching the conversion ratio from a web service. For this, we will use the webservice hosted at http://www.webservicex.net/CurrencyConvertor.asmx. We want to point out that we at U2U did not create nor maintain this webservice, and as such cannot guarantee the availability nor the correctness of this service; it is just used for illustration purpose.

This code is based on Visual Studio 2005 Beta 2 and SQL Server 2005 CTP (build June 2005, IDW 15). In the final release, some of the special operations which are needed to add the webservices in the beta releases, should be solved.

This article does not describe all steps in great detail, it assumes from the reader already a basic knowledge of creating CLR functions for SQL Server 2005.

 

Creating the conversion function

The first step in this process consists of creating a C# or VB.NET Sql Server project, which is one of the installed templates in Visual Studio 2005 (notice that the beta releases of both products are still a bit picky on the installation order: you need to install Sql Server 2005 before you install Visual Studio 2005 in order for the templates to show up in Visual Studio).

Unfortunately, if we would continue working in the same way as we are used to in Visual Studio for making regular web service client, we will fail in using them in SQL Server 2005. The reason is that SQL Server 2005 does not allow CLR code to load dynamically generated assemblies, which is exactly what the traditional webservices are doing. The solution to that is make sure these assemblies are created at compile time, and load the additional assemblies manually. Yet another problem is that the proxy class generated by Visual Studio performs operations which are not allowed by SQL Server. This latter problem will probably be solved by the final release, but in the mean time we have to work around this problem as well, by generating the proxy class with an setting generating old asynchronous code, as explained in a post from Vineet Rao on blogs.msdn.com.

So first, we generate the proxy file for our webservice with the wsdl.exe tool in C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin. We have to convince this tool however, to generate code using the ‘old’ asynchronous way (without the <MethodName>Async methods). Only proxy classes generated that way can be loaded with external access privileges. If we would use the ‘new’ proxy classes, we have to use unsafe as security level, which can better be avoided. We can do this by using a configuration file. First create a configuration file containing the following xml:

<wsdlParameters xmlns='http://microsoft.com/webReference/'>

  <language>c#</language>

  <protocol>Soap</protocol>

  <nologo>true</nologo>

  <sharetypes>false</sharetypes>

  <webReferenceOptions>

    <codeGenerationOptions>properties oldAsync</codeGenerationOptions>

  </webReferenceOptions>

</wsdlParameters>

Save this file as c:\oldwsdlconfig.xml (change the language element to VB if you are creating a VB.NET project: <language>vb</language>). Now, call wsdl.exe to generate the proxy class:

C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\wsdl.exe /par:c:\ oldwsdlconfig.xml http://www.webservicex.net/CurrencyConvertor.asmx

This will generate a CurrencyConvertor.cs (or .vb) file. We will now add this file to our SQL Server project in Visual Studio by right clicking our project, selecting Add -> Existing items, and then browse to the location where the CurrencyConvertor.cs (or .vb) file was created. We will see an error if we build our code this way: the generated proxy class uses the webservice dll, which is not yet referenced, so right click the project in the Solution Explorer, choose add reference, and select System.Web.Services from the list.

Next, we have to avoid that our code will do runtime assembly creation for the serialization and deserialization of the objects which travel across this webservice. In order to do that, we right click in our Solution Explorer on the project and select Properties. The exact setting is slightly different in VB.NET and C#.

C#

Select the Build tab, and at the bottom select for Generate serialization assembly the value On

VB.NET

Select the Compile tab, click Advanced Compile Options… and select at the bottom of the dialog in the Generate serialization assemblies combobox the value on.

While we are in the project property window, we can set the permission level property as well: select the database tab, and set the value for permission level to external. In this way, we mark this assembly as an assembly which is ‘safe’ in the sence that it will not do dangerous thread manipulations, platform invokes, …, and hence it is not marked as unsafe. But it is less safe than an assembly marked ‘safe’, because it makes calls over the network, or reads/writes to the hard disk.

Finally, we can start the main programming step: writing our conversion function. This function takes as input a sql money type, and returns something of Sql money type as well: the result of multiplying the input parameter with the conversion rate of Euro versus US Dollars. To add this code, right click the project in the solution explorer, select Add -> User-Defined Function, and choose a database in which we want to deploy this CLR function (e.g. AdventureWorks). Alter the code so that it becomes as follows:

C#

public partial class UserDefinedFunctions

{

  [Microsoft.SqlServer.Server.SqlFunction]

  public static SqlMoney EUR2USD(SqlMoney amount)

  {

    CurrencyConvertor cc = new CurrencyConvertor();

    return new SqlMoney(cc.ConversionRate(Currency.EUR,Currency.USD)

      * amount.ToDouble());

  }

};

VB.NET

Partial Public Class UserDefinedFunctions

  <Microsoft.SqlServer.Server.SqlFunction()> _

  Public Shared Function EUR2USD(ByVal amount As SqlMoney) As SqlMoney

    Dim cc As New CurrencyConvertor()

    Return New SqlMoney(amount.ToDouble() * _

    cc.ConversionRate(Currency.EUR, Currency.USD))

  End Function

End Class

Next, we can build and deploy this project.

Fixing the code in the SQL Management Studio

We can now try out our code by starting the SQL Server 2005 Management Studio. If we browse with our Object Explorer to the database in which we deployed our code (e.g. Adventureworks), we see in the programmability folder, under assemblies, that one assembly is loaded, and that one scalar function is created:

However, if we try to use our function, we run into a problem. Open a Query window by right clicking the in the object explorer the database in which the function is deployed, and select New Query. In that query window, type the following T-SQL statement:

SELECT dbo.EUR2USD(1)

When we execute this statement, instead of obtaining the conversion rate of euros to US dollars, we get an error message:

Msg 6522, Level 16, State 2, Line 2

A .NET Framework error occurred during execution of user defined routine or aggregate 'EUR2USD':

System.InvalidOperationException: Cannot load dynamically generated serialization assembly. In some hosting environments assembly load functionality is restricted, consider using pre-generated serializer. Please see inner exception for more information. ---> System.IO.FileLoadException: LoadFrom(), LoadFile(), Load(byte[]) and LoadModule() have been disabled by the host.

The reason for this is a missing dll. When we look at the bin directory of our Visual Studio project, we see that two dlls have been created: WebServiceConsume.dll and WebServiceConsume.XmlSerializers.dll. This latter dll is needed to do the Xml serialization and deserialization on the webservice calls, but this dll is not loaded automatically in the SQL Server database. Hence, we have to load this dll manually. To do so, right click the assemblies folder in the object explorer of the SQL management studio, and select new assembly…

Next, specify the path to the WebServiceConsume.XmlSerializers.dll file after clicking the Browse button. You can keep the permission set on Safe, this assembly will not make any external calls. After you click OK, we can refresh our assemblies folder, and we see the new assembly appearing.

If we now try our T-SQL query with our EUR2USD function again, we see that it succeeds: the conversion web service can now be called directly from within the database by wrapping it in a CLR function.

Web services and efficiency

One last word of caution: be careful when using such webservice calls. Since these calls make network calls, they are slow. So be very careful if you apply these functions on large tables. To illustrate this, run this query to get all the prices in euros and US dollars (504 rows, AdventureWorks database):

select StandardCost, dbo.EUR2USD(StandardCost) from Production.Product

This query took 4 minutes and 44 seconds to complete, because 504 calls where made to the webservice. However, we can rewrite the query as follows:

declare @rate float

select @rate = dbo.EUR2USD(1)

select StandardCost, StandardCost * @rate from AdventureWorks.Production.Product

Now, we only make one single call to the web service, and store the conversion rate in a local variable. Unless the result of the webservice call changes during the execution of our function, both batches return the same result, only the latter batch returns in less than one second, more than 300 times as fast as our first query.

So be careful when using web services: if the result of a single web service call is used frequently, try to cache this result in a SQL variable or temporary table, in order to avoid unnecessary web service calls. Also notice that this caching cannot happen in the CLR function, since assemblies which are loaded into SQL Server, are not allowed to store static or shared state. The only exception to this is when you mark your assembly as unsafe.

Dr. Nico Jacobs

is trainer and consultant at U2U (www.u2u.net). His focus is on Microsoft SQL Server and ADO.NET. You can reach Nico at nico@u2u.be.

Contact me Contact


Contact me Receive U2U Newsletter.
Looking for a challenging job Download Brochure On Site Training Looking for a challenging job
Favorites Favorites

Copyright © 1999-2010 by U2U