0

I have a simple webmethod that looks a bit like this:

[WebMethod]
public static object getData(string id, string type, string[] filters)
{    
    //do some validation of inputs
    //execute a stored procedure and process with LINQ

    return new { Result = "OK", Records = results, TotalRecordCount = unpagedTotal };
}

When I run this in debug mode connected to my database it consistently returns the same data for the same inputs, as you would expect. When this is run in production the Records and TotalRecordCount in my JSON is sometimes different for exactly the same inputs. There are no determinable patterns that dictate when the numbers change - but frequently they do to all different values. The only observation I can make about the values is that TotalRecordCount never exceeds the actual record count - when the value changes it's always to a lower number.

There are no INSERT statements in the stored procedures. This is for reading data only. I have run SQL Server Profiler on the server and confirm that there are no queries running that could alter the count of the records. This is not a transactional system, it's more of a read-only reporting database that gets rebuilt once a day.

I think I have isolated the issue to IIS, because all of the following tests pass:

Rule out SQL Server

  1. Run the stored procedure 100 times in a loop and check outputs
  2. Repeat above for 2 SSMS login sessions using the same SQL login

Rule out JavaScript / Front-end

  1. In Chrome DevTools inspect: Network > Request > Headers. Check the Request Payload is correct.
  2. In Chrome DevTools inspect: Network > Response - number of records displayed on page match (the erroneous) number of records in response

Rule out Application Layer / C#

  1. Open solution in Visual Studio
  2. Connect application to production DB
  3. Run in debug mode - unable to replicate the error after many runs. TotalRecordCount is consistent.

Rule out Network

  1. Check that the response coming back from server is a well-formed JSON object (stringified)
  2. Compare Request Payload against @params in the stored procedure (using SQL Profiler)

What's left? IIS. I don't know much about IIS.

What I want to know is... is there some setting(s) in IIS that could manifest this bizarre behaviour? Have I missed anything? Does anyone have a clue what's going on?

I've just deployed this application and am having a brain meltdown after looking at this for several hours. All help appreciated!

SQL Code

As requested, the stored procedure looks a little like this:

ALTER PROCEDURE [SO].[getData]

 @ID bigint
 @Type string

WITH ENCRYPTION
AS
BEGIN                

DECLARE @resultsTable
as table (   
col1 nvarchar(100)
col2 nvarchar(100)
)

--Add test data
INSERT INTO @resultsTable
EXEC [SO].[someSP1] @ID @Type
INSERT INTO @resultsTable
EXEC [SO].[someSP2] @ID @Type
INSERT INTO @resultsTable
EXEC [SO].[someSP3] @ID @Type
INSERT INTO @resultsTable
EXEC [SO].[someSP4] @ID @Type

-- Returned combined dataset
SELECT * FROM @resultsTable

END

each of the someSP procedures have the same signature as this parent one. As mentioned above this works fine if I run it hundreds of times from 2 instances of SSMS connected using the same SQL Login to SQL server (e.g. emulating an application)

I cannot post someSP as they are huge and sensitive. The options I have are:

WITH ENCRYPTION SET NOCOUNT ON;

Adam
  • 1,932
  • 2
  • 32
  • 57
  • Can we see some example output? – Pseudonym Nov 13 '15 at 16:55
  • Can you share some procedure code? Are you using NOLOCK? – Sean Lange Nov 13 '15 at 16:58
  • @Pseudonym Not easily. The data is of a sensitive nature. I don't think it's relevant. There's a line which says `unpagedTotal = results.Count()` and this inconsistently changes the `TotalRecordCount` in production only. – Adam Nov 13 '15 at 16:58
  • 1
    @Adam you need to show some sample data and the method call perhaps the WebService on the production box is pointing to an old version of the web service.. have you tried refreshing the web reference..? – MethodMan Nov 13 '15 at 17:02
  • @MethodMan not sure if that's the case as this is being executed on the front-end by a javascript client (see: jtable.org) - the fault is intermittent and inconsistent. I will see what I can do about sample data but to be clear if I sit on a chrome web browser and load the page and use the same controls in the same order I get different results each time for the same query with the same inputs in a db which has no writes. – Adam Nov 13 '15 at 17:14
  • 2
    One thing you can test to see if you can reproduce locally is pull up the site in 2 browsers and hit it at the same time. The reason it could be inconsistent in production is because it's a static method and multiple people are hitting it at the same time. – Stephen Brickner Nov 13 '15 at 17:16
  • use the chrome debugger hitting `F12` perhaps you may be able to see what's going on that way as well – MethodMan Nov 13 '15 at 17:16
  • @MethodMan - I do appreciate all people's input and time on my question but I wonder if you actually bothered to read it before you posted comments? I described in detail the use of Chrome DevTools. – Adam Nov 13 '15 at 17:19
  • @StephenBrickner I had no idea that a static `WebMethod` had issues serving multiple users. Thank you very much. I will look at this. What is the impact of changing the method to non-static? I didn't write this code but I don't see why it needs to be static to be honest. – Adam Nov 13 '15 at 17:22
  • It depends on how it's called. If called through ajax I believe it has to be static so you could just use a lock to force one to wait for the other. – Stephen Brickner Nov 13 '15 at 17:24

1 Answers1

1

For a complete answer I'll add my comment from above: One thing you can test to see if you can reproduce locally is pull up the site in 2 browsers and hit it at the same time. The reason it could be inconsistent in production is because it's a static method and multiple people are hitting it at the same time. I don't know for sure that this is your issue but it sounds like it. Try using a lock to force one request to wait for another.

[WebMethod]
public static object getData(string id, string type, string[] filters)
{    
    //do some validation of inputs
    //execute a stored procedure and process with LINQ

    var _lock = new object();

    lock(_lock)
    {
        return new { Result = "OK", Records = results, TotalRecordCount = unpagedTotal };
    }
}
Stephen Brickner
  • 2,584
  • 1
  • 11
  • 19
  • Thanks. I am looking down this avenue but it seems bizarre to me because: a) I thought all `WebMethod`s are static by nature as they are invoked by the client instead of in this application instance and b) It is totally normal for multiple clients of a web application to connect to the same webservice and get data from it. If this is all data-read and not write then what should it matter if two people make request at same time? Surely they'll both hit getData and whichever gets to execute the SQL sp first will get the data back first. Why would the data be different? – Adam Nov 13 '15 at 17:31
  • Here is a post by Marc Gravell that explains it pretty well: http://stackoverflow.com/questions/3037637/c-sharp-what-if-a-static-method-is-called-from-multiple-threads Again, I don't know if this is your issue but in the past I have come across what you are describing when static methods were sharing state. – Stephen Brickner Nov 13 '15 at 17:37
  • Also, the lock would need to be wrapped around any code that could possibly share state not just what's returned. – Stephen Brickner Nov 13 '15 at 17:42
  • This is invaluable information. Thank you very much. I'll confirm a fix before marking as right answer – Adam Nov 13 '15 at 17:43
  • Thanks, you saved my ass! For completeness I didn't use any locking in the end. The link you posted gave me the answer - static fields have shared-state. I refactored those out of the code and got the desired results - pure consistency! – Adam Nov 13 '15 at 23:30