Сборка SQL WebResponse и разбор строк ОЧЕНЬ медленный - PullRequest
0 голосов
/ 19 мая 2018

Итак, я быстро изучаю способы C # (полный noob, унаследовавший эту проблему);Я написал следующий код, который вызывает веб-сервис, который возвращает 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 записей POSTED и возвращено (я еще не обработал часть POST).Я уверен, что есть много вещей, которые я делаю здесь не так, которые вызывают проблемы, но я абсолютно не знаю, с чего начать.Я был настолько взволнован, что смог получить от этого правильный ответ!Любые идеи / мысли / помощь / сочувствие будет принята с благодарностью.

Ответы [ 2 ]

0 голосов
/ 24 мая 2018

Я отмечаю ответ на этот вопрос, так как стало ясно, что нужно переписать и переосмыслить мой оригинальный сценарий.@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");

}

}

0 голосов
/ 19 мая 2018

Здесь есть несколько проблем, не в последнюю очередь из-за того, что вы опубликовали свой пароль "sa" в этих общедоступных сетях.Вот проблемы с кодом, которые я вижу:

  1. Хотя в SQLCLR можно выполнять вызовы веб-служб, это определенно сложная тема, полная ловушек.Это не то, что должен предпринять новичок / новичок в 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. Не проверяйте NULL входы через Convert.ToString(username) != null.Все типы Sql* имеют свойство IsNull, которое вы можете проверить.Поэтому вместо этого используйте !username.IsNull.
  8. . НЕ выполняйте всю обработку текста (особенно обработку, которая связывается с другой системой для выполнения построчных вставок), сохраняя при этом удаленное соединение HttpWebRequest открытым. only вещь, которую вы должны делать в using (WebResponse resp = req.GetResponse()) - это заполнение переменной document.Не выполняйте никакой обработки содержимого document, пока не окажетесь за пределами этого самого внешнего using().
  9. Не выполняйте отдельные вставки (то есть цикл while (i > 1)).Они даже не в транзакции.Если вы получите ошибку в середине документа, вы загрузите частичные данные (если это не подходит для этого процесса).
  10. ВСЕГДА объекты базы данных, соответствующие схеме.Это означает, что JSON_DATA должно быть dbo.JSON_DATA (или любой другой схемой, если не dbo).
  11. В вашем connectionString у вас есть и идентификатор / пароль, и Trusted_Connection.Не используйте оба варианта, поскольку они являются взаимоисключающими (если у вас есть оба, идентификатор / пароль игнорируются и используется только Trusted_Connection).
  12. Пожалуйста, не входите в систему как sa иливаше приложение войдите как sa.Это просто попрошайничество.
  13. Вы подключаетесь к другому экземпляру SQL Server, на котором запущен этот объект SQLCLR?Если это тот же экземпляр, вам лучше изменить его на SqlProcedure, чтобы вы могли использовать Context_Connection=True; в качестве строки подключения.Это внутрипроцессное соединение, которое подключается к сеансу, из которого он вызывается.
  14. Не использовать Parameters.AddWithValue().Плохая идея.Создайте параметр SqlParameter с определенным и соответствующим типом данных.Затем добавьте is в коллекцию Parameters через Add().

Могут быть и другие проблемы, но они были очевидными.Как я сказал в пункте № 1, вы можете быть здесь над головой.Не пытаясь быть отрицательным, просто пытаясь избежать другой плохой реализации SQLCLR, которая часто приводит к отрицательному мнению об этой очень полезной в других отношениях функции.Если вы хотите продолжить это, то, пожалуйста, сначала изучите, как работает SQLCLR, передовой опыт и т. Д. Хорошее место для начала - серия статей, которые я пишу на эту тему в SQL Server Central: Лестница в SQLCLR .

Или другой вариант - использовать INET_GetWebPages SQLCLR TVF, который доступен в полной версии библиотеки SQL # SQLCLR (которую я написал).Эта опция не является бесплатной, но она позволит вам просто установить часть веб-запроса, а затем вам просто нужно будет проанализировать возвращенный документ отдельно в скалярном UDF SQLCLR (что, вероятно, является лучшим подходом в любом случае, даже если вы делаете веб-запрос).функция / хранимая процедура самостоятельно).Фактически, если вы вставляете в таблицу в том же экземпляре SQL Server, вы можете создать SQLCLR TVF для анализатора документов и передать каждое значение OrderedNest обратно, используя yield return (для потоковой передачи результатов назад), и использовать какследует:

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);

Удачи!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...