1

Code Migration due to Performance Issues :-

  1. SQL Server LIKE Condition ( BEFORE )
  2. SQL Server Full Text Search --> CONTAINS ( BEFORE )
  3. Elastic Search ( CURRENTLY )

Achieved So Far :-

We have a web page created in ASP.Net Core which has a Auto Complete Drop Down of 2.5+ Million Companies Indexed in Elastic Search https://www.99corporates.com/

Due to performance issues we have successfully shifted our code from SQL Server Full Text Search to Elastic Search and using NEST v7.2.1 and Elasticsearch.Net v7.2.1 in our .Net Code.

Still looking for a solution :-

If the user does not select a company from the Auto Complete List and simply enters a few characters and clicks on go then a list should be displayed which we had done earlier by using the SQL Server Full Text Search --> CONTAINS

  1. Can we call the ASP.Net Web Service which we have created using SQL CLR and code like SELECT * FROM dbo.Table WHERE Name IN( dbo.SQLWebRequest('') )

    [System.Web.Script.Services.ScriptMethod()]
    [System.Web.Services.WebMethod]
    public static List<string> SearchCompany(string prefixText, int count)
    {
    }
    
  2. Any better or alternate option

2 Answers2

1

While that solution (i.e. the SQL-APIConsumer SQLCLR project) "works", it is not scalable. It also requires setting the database to TRUSTWORTHY ON (a security risk), and loads a few assemblies as UNSAFE, such as Json.NET, which is risky if any of them use static variables for caching, expecting each caller to be isolated / have their own App Domain, because SQLCLR is a single, shared App Domain, hence static variables are shared across all callers, and multiple concurrent threads can cause race-conditions (this is not to say that this is something that is definitely happening since I haven't seen the code, but if you haven't either reviewed the code or conducted testing with multiple concurrent threads to ensure that it doesn't pose a problem, then it's definitely a gamble with regards to stability and ensuring predictable, expected behavior).

To a slight degree I am biased given that I do sell a SQLCLR library, SQL#, in which the Full version contains a stored procedure that also does this but a) handles security properly via signatures (it does not enable TRUSTWORTHY), b) allows for handling scalability, c) does not require any UNSAFE assemblies, and d) handles more scenarios (better header handling, etc). It doesn't handle any JSON, it just returns the web service response and you can unpack that using OPENJSON or something else if you prefer. (yes, there is a Free version of SQL#, but it does not contain INET_GetWebPages).

HOWEVER, I don't think SQLCLR is a good fit for this scenario in the first place. In your first two versions of this project (using LIKE and then CONTAINS) it made sense to send the user input directly into the query. But now that you are using a web service to get a list of matching values from that user input, you are no longer confined to that approach. You can, and should, handle the web service / Elastic Search portion of this separately, in the app layer.

Rather than passing the user input into the query, only to have the query pause to get that list of 0 or more matching values, you should do the following:

  1. Before executing any query, get the list of matching values directly in the app layer.
  2. If no matching values are returned, you can skip the database call entirely as you already have your answer, and respond immediately to the user (much faster response time when no matches return)
  3. If there are matches, then execute the search stored procedure, sending that list of matches as-is via Table-Valued Parameter (TVP) which becomes a table variable in the stored procedure. Use that table variable to INNER JOIN against the table rather than doing an IN list since IN lists do not scale well. Also, be sure to send the TVP values to SQL Server using the IEnumerable<SqlDataRecord> method, not the DataTable approach as that merely wastes CPU / time and memory.

    For example code on how to accomplish this correctly, please see my answer to Pass Dictionary to Stored Procedure T-SQL

In C#-style pseudo-code, this would be something along the lines of the following:

List<string> = companies;

companies = SearchCompany(PrefixText, Count);

if (companies.Length == 0)
{
   Response.Write("Nope");
}
else
{
   using(SqlConnection db = new SqlConnection(connectionString))
   {
     using(SqlCommand batch = db.CreateCommand())
     {
        batch.CommandType = CommandType.StoredProcedure;
        batch.CommandText = "ProcName";

        SqlParameter tvp = new SqlParameter("ParamName", SqlDbType.Structured);
        tvp.Value = MethodThatYieldReturnsList(companies);
        batch.Paramaters.Add(tvp);

        db.Open();
        using(SqlDataReader results = db.ExecuteReader())
        {
           if (results.HasRows)
           {
              // deal with results
              Response.Write(results....);
           }
        }
     }
   }
}
Solomon Rutzky
  • 46,688
  • 9
  • 128
  • 171
  • Thanks Solomon, firstly for spending your valuable time answering my question and giving a detailed explanation which actually make sense. I will try out the steps 1, 2 and 3. I will post my observation after implementing the same. – Shirish Patel Sep 09 '19 at 18:33
  • @ShirishPatel You're welcome. I just updated to add some pseudo code to help make sure that you are understanding what I was getting at. I also added a link to more specific example code which should include the T-SQL side as well. – Solomon Rutzky Sep 09 '19 at 19:09
0

Done. Got the solution.

Used SQL CLR https://github.com/geral2/SQL-APIConsumer

exec  [dbo].[APICaller_POST]
@URL = 'https://www.-----/SearchCompany'
,@JsonBody = '{"searchText":"GOOG","count":10}'

Let me know if there is any other / better options to achieve this.