簡體   English   中英

SQL 程序集 WebResponse 和字符串解析非常慢

[英]SQL Assembly WebResponse and String Parsing VERY slow

所以我正在快速學習C#的方法(完全繼承了這個問題的菜鳥); 我編寫了以下代碼,該代碼調用一個 Web 服務,該服務返回的 JSON 並不總是格式正確的。 這里的任務是將 JSON 字符串分解成數組段,然后插入到 SQL 表中以進行進一步的解析和測試。 即,如果返回字符串類似於

   {1234:{5678:{1:{"key":"val","key":"val"},{2:{"key":"val","key":"val"}}}}

那么行將是:

{1234}
{5678}
{1:{"key":"val","key":"val"}
{2:{"key":"val","key":"val"}

這是 .NET 3.0 和 SQL Server 2008 R2(舊版)。 這是我的工作代碼:

 public partial class UserDefinedFunctions
    {
         [Microsoft.SqlServer.Server.SqlFunction(DataAccess = 
    DataAccessKind.Read)]
    public static SqlString TestParse(SqlString uri, SqlString username, SqlString passwd, SqlString postdata)
    {
            //-----
           // The SqlPipe is how we send data back to the caller
       SqlPipe pipe = SqlContext.Pipe;
        SqlString document;
        try
        {
            // Set up the request, including authentication
            WebRequest req = WebRequest.Create(Convert.ToString(uri));
            if (Convert.ToString(username) != null & Convert.ToString(username) != "")
            {
                req.Credentials = new NetworkCredential(
                    Convert.ToString(username),
                    Convert.ToString(passwd));
            }
            ((HttpWebRequest)req).UserAgent = "CLR web client on SQL Server";

            // Fire off the request and retrieve the response.
            using (WebResponse resp = req.GetResponse())
            {

                using (Stream dataStream = resp.GetResponseStream())
                {
                    //SqlContext.Pipe.Send("...get the data");
                    using (StreamReader rdr = new StreamReader(dataStream))
                    {
                        document = (SqlString)rdr.ReadToEnd();
                        rdr.Close();

                        //-----
                        string connectionString = null;
                        string sql = null;
                        connectionString = "Data source= 192.168.0.5; Database=Administration;User Id=Foo;Password=Blah; Trusted_Connection=True;";
                        using (SqlConnection cnn = new SqlConnection(connectionString))
                        {
                            sql = "INSERT INTO JSON_DATA (JSONROW) VALUES(@data)";
                            cnn.Open();
                            using (SqlCommand cmd = new SqlCommand(sql, cnn))
                            {

                                String payload = "";
                                String nestpayload = "";
                                int nests = 0;
                                String json = document.ToString();
                                /*first lets do some housekeeping on our payload; double closing curly braces need to be escaped (with curly braces!) in order to keep them in the string.*/
                                json = json.Replace("\\", "");
                                int i = json.Length;
                                //return new SqlString(json);
                                while (i > 1)
                                {
                                    /*find the first closing "}" in the string and then check to see if there are more than one.
                                    We need to read the data up to each closing brace, pull off that substring and process it for each iteration until the string is gone.*/
                                    int closingbrace = json.IndexOf("}"); //First closing brace
                                    int nextbrace = Math.Max(0, json.IndexOf("{", closingbrace)); //Next opening brace
                                    String ChkVal = json.Substring(closingbrace + 1, Math.Max(1, nextbrace - closingbrace)); //+1 to ignore the 1st closing brace
                                    int checks = Math.Max(0, ChkVal.Length) - Math.Max(0, ChkVal.Replace("}", "").Length);
                                    payload = json.Substring(0, Math.Max(0, (json.IndexOf("}") + 1)));
                                    /*Remove the payload from the string*/
                                    json = json.Substring(payload.Length + 1);

                                    /*"nests" is how many nested levels excluding the opening brace for the closing brace we found.*/
                                    nests = (payload.Length - payload.Replace("{", "").Length);
                                    /*If we have more then one nest level check to see if any of them go with the payload*/

                                    if (nests > 1)
                                    {
                                        /*Break out the nested section and remove it from the payload.*/
                                        nestpayload = payload.Substring(0, payload.LastIndexOf("{"));
                                        payload = payload.Substring(payload.LastIndexOf("{"), payload.Length - payload.LastIndexOf("{"));

                                        while (nests > 1)
                                        {
                                            if (checks > 0) //# of right braces in payload equals number of left-side nests go with the payload
                                            {
                                                // payload = nestpayload.Substring(Math.Max(0, nestpayload.LastIndexOf("{")), Math.Max(0, nestpayload.Length) - Math.Max(0, (nestpayload.LastIndexOf("{")))) + payload;//The second Math.Max defaults to 1; if we got here there is at minimum one "{" character in the substring
                                                payload = nestpayload.Substring(nestpayload.LastIndexOf("{")) + payload;
                                                nestpayload = nestpayload.Substring(0, Math.Max(0, Math.Max(0, nestpayload.LastIndexOf("{"))));
                                                checks--;
                                                nests--;
                                            }
                                            else
                                            {
                                                /*If we got here there are no more pieces of the nested data to append to the payload.
                                                 We use an array and string.split to keep the nest ordering correct.*/
                                                string[] OrderedNest = nestpayload.Split('{');
                                                for (int s = 0; s < OrderedNest.Length; s++)
                                                {
                                                    if (OrderedNest[s] != "")
                                                    {
                                                        cmd.Parameters.AddWithValue("@data", "{" + OrderedNest[s].Replace(":", "}"));
                                                        cmd.ExecuteNonQuery();
                                                        cmd.Parameters.Clear();
                                                    }
                                                }

                                                //cmd.Parameters.AddWithValue("@data", nestpayload.Substring(Math.Max(0,nestpayload.LastIndexOf("{"))).Replace(":","}"));
                                                //cmd.Parameters.AddWithValue("@data", OrderedNest[1].Replace(":","}")+OrderedNest[2]);
                                                // cmd.ExecuteNonQuery();
                                                //cmd.Parameters.Clear();
                                                //nests = Math.Max(0, nests - 1);
                                                nests = 0;
                                                //nestpayload = nestpayload.Substring(0, Math.Max(0, Math.Max(0,nestpayload.LastIndexOf("{"))));

                                            }
                                        }
                                    }


                                    /*At the very end payload will be a single "}"; check for this and discard the last row*/
                                    if (payload != "}")
                                    {
                                        cmd.Parameters.AddWithValue("@data", new SqlChars(payload));
                                        cmd.ExecuteNonQuery();
                                        cmd.Parameters.Clear();
                                    }

                                    /*Get the new string length*/
                                    i = json.Length;
                                    payload = "";

                                }

                            }
                        }
                        //-----

                        /*  }
                          catch (Exception e)
                          {
                              return e.ToString();
                          }*/
                    }

           // Close up everything...
                    dataStream.Close();
                }
                resp.Close();
                // .. and return the output to the caller.

            }//end using
            return ("Finished");
        }
        catch (WebException e)
        {

            throw e;
        }                   
  }
}

雖然它有效,但速度非常慢; 4 分鍾以上將 1500 行寫入服務器。 每天一次,這將需要寫入約 60,000 條記錄; 剩下的時間可能是 100 條記錄發布並返回(我還沒有完成 POST 部分)。 我確信有很多我在這里做的不太合適的事情會導致問題,但我完全不知道從哪里開始。 我很興奮,我可以從中得到正確的回應! 任何想法/想法/幫助/同情將不勝感激。

這里有幾個問題,其中最重要的是您似乎已將您的“sa”密碼發布到這些公共互聯網上。 以下是我看到的代碼問題:

  1. 雖然可以在 SQLCLR 中進行 Web 服務調用,但這絕對是一個高級主題,充滿陷阱。 這不是 SQLCLR 的新手/初學者應該承擔的事情,它本身已經是常規 .NET 編程的一個微妙的子集。
  2. 去掉SqlPipe行和它上面的注釋行。 函數不會通過SqlPipe將數據傳回給調用者; 那是存儲過程。
  3. 您可能不應該使用WebRequest
  4. document應該是string ,而不是SqlString 您永遠不會返回document並且只會將其轉換回string ,所以應該就是這樣。
  5. 使用HttpWebRequest而不是WebRequest 這樣您就不必偶爾將其轉換為HttpWebRequest
  6. 不要將SqlString輸入參數轉換為string (例如Convert.ToString(uri) )。 所有Sql*類型都有一個Value屬性,該屬性返回本機 .NET 類型的值。 因此,只需使用uri.Value ,依此類推。
  7. 不要通過Convert.ToString(username) != null檢查NULL輸入。 所有Sql*類型都有一個IsNull屬性,您可以檢查。 所以改用!username.IsNull
  8. 不要在保持遠程HttpWebRequest連接打開的同時進行所有文本處理(尤其是與另一個系統聯系以進行逐行插入的處理)。 您應該在using (WebResponse resp = req.GetResponse())做的唯一一件事是填充document變量。 在離開最外層using()之前,不要對document的內容進行任何處理。
  9. 不要單獨插入(即while (i > 1)循環)。 他們甚至不在交易中。 如果在文檔中間出現錯誤,則您將加載部分數據(除非此過程沒問題)。
  10. 總是模式限定數據庫對象。 意思是, JSON_DATA應該是dbo.JSON_DATA (或者任何正在使用的架構,如果不是dbo )。
  11. 在您的connectionString您同時擁有 Id/Password 和Trusted_Connection 不要同時使用它們,因為它們是互斥的選項(如果兩者都有,ID/密碼將被忽略並且只使用Trusted_Connection )。
  12. 請不要以sa身份登錄或讓您的應用程序以sa身份登錄。 那只是在乞求一場災難。
  13. 您是否連接到與運行此 SQLCLR 對象不同的 SQL Server 實例? 如果是同一個實例,最好將其更改為SqlProcedure以便您可以使用Context_Connection=True; 作為連接字符串。 這是附加到調用它的會話的進程內連接。
  14. 不要使用Parameters.AddWithValue() 餿主意。 使用特定且適當的數據類型創建 SqlParameter。 然后通過Add()Parameters集合中。

可能還有其他問題,但這些是顯而易見的。 正如我在第 1 點中所說的那樣,您可能會在這里過頭。 不是試圖否定,只是試圖避免 SQLCLR 的另一個糟糕的實現,這通常會導致對這個非常有用的功能的負面看法。 如果您想繼續這樣做,那么請首先對 SQLCLR 的工作原理、最佳實踐等進行更多研究。一個很好的起點是我在 SQL Server Central: Stairway to SQLCLR上撰寫的關於此主題的系列文章。

或者,另一種選擇是使用SQL# SQLCLR 庫(我編寫的)完整版中提供的INET_GetWebPages SQLCLR TVF。 此選項不是免費的,但它允許您簡單地安裝 Web 請求塊,然后您只需要在 SQLCLR 標量 UDF 中單獨解析返回的文檔(這可能是最好的方法,即使您執行 Web 請求自己的函數/存儲過程)。 事實上,如果您要插入到 SQL Server 的同一個實例中的表中,您可以為文檔解析器創建一個 SQLCLR TVF,並使用yield return (將結果流回)傳回每個OrderedNest值,並使用如下:

DECLARE @JSON NVARCHAR(MAX);

SELECT @JSON = [content]
FROM   SQL#.INET_GetWebPages(@uri, .....);

INSERT INTO dbo.JSON_DATA (JSONROW)
  SELECT [column_name]
  FROM   dbo.MyBrokenJsonFixerUpper(@JSON);

祝你好運!

我將這個問題標記為已回答,因為很明顯需要的是重寫和重新思考我的原始腳本。 @Solomon Rutzky 贊成提供有用的信息,這使我得出了這個結論。 對於那些有興趣的人來說,重寫:

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Collections;
using System.Globalization;
// Other things we need for WebRequest
using System.Net;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

public partial class StoredProcedures
{
    [Microsoft.SqlServer.Server.SqlProcedure]
    public static void ApiParser(SqlString uri, SqlString user, SqlString pwd, SqlString postd)
    {
        // Create an SqlPipe to send data back to the caller
        SqlPipe pipe = SqlContext.Pipe;
        //Make sure we have a url to process
        if (uri.IsNull || uri.Value.Trim() == string.Empty)
        {
            pipe.Send("uri cannot be empty");
            return;
        }
    try
    {
        //Create our datatable and get the table structure from the database
        DataTable table = new DataTable();
        string connectionString = null;
        //connectionString = "Data source= 192.168.0.5; Database=Administration; Trusted_Connection=True;";
        connectionString = "Data Source=(localdb)\\ProjectsV12;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
        using (SqlConnection gts = new SqlConnection(connectionString))
        {
            gts.Open();
            using (SqlDataAdapter adapter = new SqlDataAdapter("SELECT TOP 0 * FROM sp_WebSvcs.dbo.JSON_DATA", gts))
            {
                adapter.Fill(table);
            }
        }

        // Send a message string back to the client.
        pipe.Send("Beginning Api Call...");
        String json = "";
        // Set up the request, including authentication
        WebRequest req = HttpWebRequest.Create(uri.Value);
        if (!user.IsNull & user.Value != "")
        {
            req.Credentials = new NetworkCredential(user.Value, pwd.Value);
        }
        ((HttpWebRequest)req).UserAgent = "CLR web client on SQL Server";

        // Fire off the request and retrieve the response.
        using (WebResponse resp = req.GetResponse())
        {

            using (Stream dataStream = resp.GetResponseStream())
            {

                using (StreamReader rdr = new StreamReader(dataStream))
                {
                    json = (String)rdr.ReadToEnd();

                    rdr.Close();
                }

                // Close up everything...
                dataStream.Close();
            }
            resp.Close();

        }//end using resp
        pipe.Send("Api Call complete; Parsing returned data...");
        int i = 0;
        String h = "";
        String l = "";
        int s = 0;
        int p = 0;
        int b = 0;
        int payload = 0;
        foreach (string line in json.Split(new[] { "}," }, StringSplitOptions.None))
        {
            if (line != "")
            {
                l = line;
                i = l.Replace("{", "").Length + 1;
                p = l.LastIndexOf("{");
                if (line.Length > i) //we find this at the beginning of a group of arrays
                {

                    h = line.Substring(0, p - 1);
                    s = Math.Max(0, h.LastIndexOf("{"));
                    if (h.Length > s && s != 0)
                    /*We have a nested array that has more than one level.
                     *This should only occur at the beginning of new array group.
                     *Advance the payload counter and get the correct string from line.*/
                    {
                        payload++;
                        l = line.Substring(s, line.Length - s);
                    }


                    h = (s >= 0) ? h.Substring(0, s) : h;
                    //=============
                    /*At this point 'h' is a nest collection. Split and add to table.*/
                    string[] OrderedNest = h.Split('{');
                    for (int z = 0; z < OrderedNest.Length; z++)
                    {
                        if (OrderedNest[z] != "")
                        {
                            table.Rows.Add(payload, "{" + OrderedNest[z].Replace(":", "").Replace("[","").Replace("]","") + "}");
                        }
                    }
                    //=============

                }
                else
                {
                    h = null;
                }
                //at this point the first character in the row should be a "{"; If not we need to add one.
                if (l[0].ToString() != "{")
                {
                    l = "{" + l;
                }

                if (l.Replace("{", "").Length != l.Replace("}", "").Length) //opening and closing braces don't match; match the closing to the opening
                {
                    l = l.Replace("}", "");

                    b = l.Length - l.Replace("{", "").Length;

                    l = l + new String('}', b);
                }
                table.Rows.Add(payload, l.Replace("\\\"", "").Replace("\\", "").Replace("]","").Replace("[",""));

            }
        }
        //====

        using (SqlConnection cnn = new SqlConnection(connectionString))
        {
            cnn.Open();
            using (SqlBulkCopy copy = new SqlBulkCopy(cnn))
            {
                copy.DestinationTableName = "sp_WebSvcs.dbo.JSON_DATA";
                copy.WriteToServer(table);
            }
        }
        //====

    } //end try
    catch (Exception e)
    {
        pipe.Send("We have a problem!");
        throw new Exception("\n\n" + e.Message + "\n\n");
    }
    pipe.Send("Parsing complete");

}

}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM