How to create a SQL CLR stored procedure to get data from a web service and insert results into a SQL Server table
Asked Answered
S

1

1

I need to submit some data to a web service from our SQL Server then capture the results into a SQL Server table.

Up to this point I have been looking at creating a SQL CLR (C#) stored procedure to accept a/some parameter(s) and then call the web service and capture the results into a SQL Server table; But I am open to any avenues that might work.

My questions are these:

  1. Am I barking up the right tree so-to-speak, or is there a better way?

  2. Assuming a SQL CLR stored procedure is the best way; I am relatively new to C# in this capacity and was hoping someone could help point me in the right direction. Does anyone have some example code or pointers? I've tried doing some Google searches and could find a few things

Any help would be greatly appreciated.

Here's a few technical details.

  • The web service is hosted on an IIS 8.5 Web Server
  • I do have access to a WSDL file the vendor provided (if that helps)
  • I could run the SQL processes on any of the following: SQL Server 2008 R2, SQL Server 2012, SQL Server 2014
  • I am using Visual Studio version 2015
  • The web service both expects an XML for submitting, and returns its results in XML

Edit: for a small bit of added clarification the reason I was thinking of a SQL CLR stored procedure is that we already have a T-SQL stored procedure running that import, cleans up data in batch each night, we are adding a new system to the existing process and the vendor only supports using web service to gather the information we need . We are trying to keep our import processes together. Though as mentioned I am open to better alternatives, some direction would be exceptionally helpful.

Edit 2: Here's a quick overview of how the current process works.

  1. A user performs a search on a website

  2. System searches the tables of the database according to the parameters the user input (the search is a stored procedure). For current systems a nightly feed is imported using a scheduled SSIS package and stored procedures. The new additional system we are being required to add into the search only supports web-services for querying and extracting data, and we are being required to keep more real-time results from this system. (It is this step where we need to add the search capability to submit info to the web-service and get the results to merge in with the other results.)

  3. System logs the results of the search, and resulting values from various business logic calculations. (the logging & retention of data is required by gov't regulators, so it's not good enough to just get the results from the web-service, we need it logged in with our other search results. )

  4. System shows user the newly logged information.


I've decided I should have broken my question out into smaller steps and focus on each step.

1) Call a CLR Stored Proc from a Stored Proc, feeding the CLR some input paramaters.

2.1) in the CLR stored Proc build the WebRequest XML needed. 2.2) in the CLR stored proc capture the WebResponse XML

3) Post the (parsed) data elements we need from the response XML into SQL (or post the whole XML into SQL and parse it out from there. ?

For Step 1) I'm unsure how to make a CLR Stored Proc accept input parameters - but I haven't searched this yet so that's on me still.

Step 2.x) I believe I have this figured out (code below)

Step 3) I could use help with how to parse the response XML (example response below)

STEP 2 CODE (Posting to WebService and capturing Response):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Threading.Tasks;
using System.Web;
using System.Net;
using System.IO;

namespace WebServiceParseResultsTest
{
    class Program
    {
        public static void Main(string[] args)
        {
            string api_URL = "https://MySubDom.MyDomain.com/Services/SearchUtil/Search.aspx";
            string api_usr = "abcdef0123456789";
            string api_pas = "0123456789abcdef";
            string rec_typ = "C3";
            string rec_sta = "PEN";
            string searchParam = "a0123456789z";

            string XMLPostString = "<?xml version=\"1.0\"?> <REQUEST> <LOGIN api_password = \"" + api_pas + "\" api_user_id = \"" + api_usr + "\" />";
            XMLPostString += "<SEARCH_QUERY record_status = \" "+rec_sta+" \" record_type = \" "+rec_typ+" \" SearchSysNum = \""+searchParam+"\" />";
            XMLPostString += "</REQUEST>";

            Console.WriteLine("-----------------");
            Console.WriteLine(XMLPostString);
            Console.WriteLine("-----------------");

            WaitForEnterKey();
            Console.WriteLine(DateTime.Now + " <NN_Test_XML_POST>");

            string postResponse = PostXMLString(XMLPostString, api_URL);
            Console.WriteLine(DateTime.Now + " SERVER RESPONSE: " + postResponse);
            Console.WriteLine(DateTime.Now + " </NN_Test_XML_POST>");
            Console.WriteLine(DateTime.Now + " Press Enter to Exit");

            WaitForEnterKey();
        }

        public static void WaitForEnterKey()
        {
            Console.WriteLine(DateTime.Now + " Press Enter to Continue");
            while (Console.ReadKey().Key != ConsoleKey.Enter) { }

        }

        public static string PostXMLFile(string fileName, string uri)
        {
            // Create a request and pass the URL to receive the post. 
            WebRequest request = WebRequest.Create(uri);
            // We set the Method property of the request to POST.
            request.Method = "POST";
            // We create what is being sent by the POST method and convert it to byte array.
            string postData = GetTextFromXMLFile(fileName); 
            //this.GetTextFromXMLFile(fileName);

            byte[] byteArray = Encoding.UTF8.GetBytes(postData);
            // We set the ContentType of the WebRequest to xml.
            request.ContentType = "text/xml";
            // We set the ContentLength of the WebRequest.
            request.ContentLength = byteArray.Length;
            // We get the request stream.
            Stream dataStream = request.GetRequestStream();
            // write the data to the request stream.
            dataStream.Write(byteArray, 0, byteArray.Length);
            // create the Stream object.
            dataStream.Close();

            //-----
            HttpWebResponse response;
            response = (HttpWebResponse)request.GetResponse();
            if (response.StatusCode == HttpStatusCode.OK)
            {
                Stream responseStream = response.GetResponseStream();
                string responseStr = new StreamReader(responseStream).ReadToEnd();
                return responseStr;
            }
            return null;
            //------

        } // END: PostXMLFile


        public static string PostXMLString(string XMLtext, string uri)
        {
            // Create a request and pass the URL to receive the post. 
            WebRequest request = WebRequest.Create(uri);
            // We set the Method property of the request to POST.
            request.Method = "POST";
            // We create what is being sent by the POST method and convert it to byte array.
            string postData = XMLtext;

            byte[] byteArray = Encoding.UTF8.GetBytes(postData);
            // We set the ContentType of the WebRequest to xml.
            request.ContentType = "text/xml";
            // We set the ContentLength of the WebRequest.
            request.ContentLength = byteArray.Length;
            // We get the request stream.
            Stream dataStream = request.GetRequestStream();
            // write the data to the request stream.
            dataStream.Write(byteArray, 0, byteArray.Length);
            // create the Stream object.
            dataStream.Close();

            //-----
            HttpWebResponse response;
            response = (HttpWebResponse)request.GetResponse();
            if (response.StatusCode == HttpStatusCode.OK)
            {
                Stream responseStream = response.GetResponseStream();
                string responseStr = new StreamReader(responseStream).ReadToEnd();
                return responseStr;
            }
            return null;
            //------

        } //END: PostXMLString

        private static string GetTextFromXMLFile(string file)
        {
            StreamReader reader = new StreamReader(file);
            string ret = reader.ReadToEnd();
            reader.Close();
            return ret;
        }

    }
}

EXAMPLE RESPONSE XML

<RESPONSE version="1.3">
    <SEARCH_RESULTS>
        <RECORD record_type="CC" 
                record_id="0123456789abcdef0123456789abcdef" 
                record_num="987" 
                record_status="PEN" 
                record_date="8/11/2017 9:22:57 PM" 
                u_name="TEST20"  
                create_date="2/1/2017 6:15:49 AM" 
                 />
    </SEARCH_RESULTS>
</RESPONSE>

Step 3 (Parsing XML RESPONSE FROM WEBSERVICE)

I decided to try and do this in SQL (I'm much stronger in SQL than C#) Here's what I came up with. But if anyone has good information on how to do this in C# I'd love to learn!

    DECLARE @vMyXML XML

    SET @vMyXML = '<RESPONSE version="1.3">
        <SEARCH_RESULTS>
            <RECORD record_type="CC" 
                    record_id="0123456789abcdef0123456789abcdef" 
                    record_num="987" 
                    record_status="PEN" 
                    record_date="8/11/2017 9:22:57 PM" 
                    u_name="TEST20"  
                    create_date="2/1/2017 6:15:49 AM" 
                  />
             <RECORD record_type="BC" 
                    record_id="1234567890bcdefa1234567890bcdefa" 
                    record_num="879" 
                    record_status="APR" 
                    record_date="8/12/2017 10:23:58 PM" 
                    u_name="TEST21"  
                    create_date="3/2/2017 7:16:50 AM" 
                  />
        </SEARCH_RESULTS>
    </RESPONSE>'

    SELECT 
        Attribs.value('@record_type'    , 'nvarchar(10)')       as [record_type]
      , Attribs.value('@record_id'      , 'nvarchar(50)')       as [record_id]
      , Attribs.value('@record_num'     , 'int')                as [record_num]

      , Attribs.value('@record_status'  , 'nvarchar(25)')       as [record_status]
      , Attribs.value('@record_date'    , 'DateTime')           as [record_date]
      , Attribs.value('@u_name'         , 'nvarchar(75)')       as [u_name]
      , Attribs.value('@create_date'    , 'DateTime')           as [create_date]

    INTO AA_TMP_MyTestXMLResults
    FROM @vMyXML.nodes('/RESPONSE/SEARCH_RESULTS/RECORD') as myXML(Attribs) 

Select * from AA_TMP_MyTestXMLResults
DROP TABLE AA_TMP_MyTestXMLResults  
Shipowner answered 6/8, 2017 at 21:30 Comment(9)
Can't you simply have a small C# console app and do this stuff. You will anyways have to write C# code.Rissa
Yes there is a better way. Calling a web service will kill your database performance (if its even allowed, used to be that some assemblies were denied). Do the web service calls outside the database. From your question I don't see a need for CLR procedures but you will need a process like a windows service or Web API or even console app.Cautious
@niksofteng Thanks, i suppose we certainly could. Would the C# code be pretty much the same? If so, any chance i could get some help in direction or examples?Shipowner
Hi Arkling, you can definitely use SQLCLR to do this, and your db performance should not be affected by it. Where I work we are using SQLCLR code A LOT, and in many instances we are getting better perf out of the system by using SQLCLR (we run highly transactional 24/7 systems) . Anyway, is it a good idea to use SQLCLR - well it depends. I do agree with @Cautious that if you can use a windows service or some other app, you'd probably be better off. If you do decide to use SQLCLR post here and maybe we can help.Ringent
@NielsBerglund are you saying you make web service calls from SQL CLR? I don't have a problem with it in general but IMHO you are hammering a screw by putting that functionality into the database..Cautious
Hey @Crowcoder, we sure are - and we are also doing a lot of other "weird" sh*t, socket connections, RabbitMQ calls, lots and lots of stuff, and we are doing thousands and thousands of transactions per second in addition.Ringent
@NielsBerglund my world has crumbled.Cautious
@Cautious Why did you think that it wouldn't work? Personal experience and/or what you've read? There is a lot of misinformation floating around on blog posts, etc about SQLCLR, and most of it due to misunderstanding it and/or misusing it. But often it is not hammering a screw, and in fact it can make for a much less convoluted solution.Elkeelkhound
@SolomonRutzky I didn't think it wouldn't work, I guess our DBAs are brainwashing me. I've only used SQL CLR once and it was because of a way we had to incorporate a 3rd party calculation into a stored procedure but it would never occur to me to put much business logic in the database. It seems like it would be at best more difficult to unit test than regular libraries.Cautious
E
0

SQLCLR should be ok in this scenario, as long as you are careful and aware of the various nuances of working in the highly restricted environment that is SQL Server's CLR host (i.e. SQLCLR), namely:

  1. You cannot use native web service code. Instead, you need to use HttpWebRequest and HttpWebResponse. And, that means that you need to generate the request XML by hand, and then parse the XML response yourself (as opposed to getting back a .NET object).
  2. In order to avoid the performance bottle neck that most people refer to when dealing with networking calls, you need to increase the URI Connection Limit via ServicePointManager for the URI you are connecting to. The default limit is 2 and any calls above that will wait until one of those 2 connections completes. I am working on a blog post that will explain this in detail, with examples, and I will post the link here once it is published.
  3. The Assembly will need to be set to EXTERNAL_ACCESS. In order to do that, do not set the Database to TRUSTWORTHY ON. Instead, sign the Assembly, create an Asymmetric Key in master from that Assembly, create a Login from that Asymmetric Key, and finally grant that Login the EXTERNAL ACCESS ASSEMBLY permission.

For more information on working with SQLCLR in general, please see the series I am writing on this topic on SQL Server Central (free registration is required to read their content): Stairway to SQLCLR.

Also, while not a free option, if you want the ability to make these web calls without having to deal with the coding, figuring out best practices, scripting, etc, then take a look at the Full version of SQL# (which I wrote). The Full version has INET_GetWebPages which allows for making web requests. And, it has INET_GetConnectionLimitForURI, INET_GetCurrentConnectionCountForURI, and INET_SetConnectionLimitForURI which allow you to manage that URI connection limit and reduce/avoid that performance bottleneck.

Elkeelkhound answered 8/8, 2017 at 3:37 Comment(1)
Thank you everyone for your feedback and suggestions. I've decided to step back and try to break this up into smaller pieces. I think we will be OK with using a CLR stored proc, there are only ~20 times a day a relatively lightly used DB so even if it takes "a little more resources" than is optimal I think we will do alright. I've added to the orig post, the code I have so far. This gets me to the web service and pulls back the response XML , my next step is to parse the XML for the data we need. I suppose this could happen in the CLR C# code or we could put the full XML in SQL to parseShipowner

© 2022 - 2025 — McMap. All rights reserved.