Question

In my app I begin an import process by uploading an XML file. Once it is uploaded, a series of stored procedures are run and the xml file is parsed, and data is inserted into many different tables. As it progresses, a Stats table is updated to show the progress of the insert. The Stats table has 4 columns: BatchId, BatchCount, ProcessCount, and ErrorCount. BatchCount is the count of all records. If the records are inserted correctly without issue ProcessCount is incremented by 1, if it fails ErrorCount is incremented by 1. (These two columns are always changing during the import process)

My main goal is to display the import progress using SignalR. I have a hub method that is polling just fine, as I'm seeing updated timestamps in my UI. The problem however, is that the count is either always 0 (initial value), or very very occasionally it will read once and show something random like 985. If it does read a value, it only does it once and will not change again. Here is my hub method:

public void BeginPolling()
{
    while (true)
    {
        var stats = _repository.GetImportStats();
        var message = "Preparing file...";

        if (stats != null)
        {
            message = DateTime.Now + " - Count: " + stats.ProcessCount.ToString();
        }
        else
        { 
            message = DateTime.Now + " - Stats result returned null.";
        }

        //the message displays in a div on my UI
        Clients.Caller.showProgress(message);

        //I have tried various sleep times (1000, 5000, 10000)
        Thread.Sleep(5000);
    }
}

Below is my _repository.GetImportStats method, for testing purposes it simply grabs the first (and only) record:

public Stats GetImportStats()
{
    return DataContext.Stats.FirstOrDefault();
}

Also for testing, I tried just putting a button on my UI that makes an ajax call to the same GetImportStats() method, and it always returns the ProcessCount just fine, so I think the problem lies within my SignalR implementation. Any help is appreciated!

Was it helpful?

Solution 2

You are doing one of the big nonos of Entity Framework, you have a long lived DbContext. You should never do this.

In this case you are having issues with Entity Framework's Change Tracker. By default EF uses MergeOption.AppendOnly.

This means by default, on each EF query, EF will only deserialize rows to objects, IFF the key does not match an object in the context's Change Tracker.

So given that you are using the same instance of the EF context each loop, EF just uses the cached values each time you do a query (the SQL runs, but EF does not create or alter the object as you expect). This is entirely expected, if you adhere to the rule that EF contexts are short lived.

To fix this you have several options.

  • Instantiate a new EF context (and change tracker) each loop.
  • Tell EF to use the MergeOption.OverwriteChanges on each query.
  • Tell EF not to use the change tracker AT ALL (this disables Write Access, but is much faster).

OTHER TIPS

It might be best to use GetHubContext and a Timer for this.

One potential problem I see with BeginPolling is that it never returns. For all SignalR transports other than WebSockets, this means that there will a never ending XHR started each time BeginPolling is called.

Not only does this unnecessarily waste server resources, it also might interfere with your clients ability to receive more messages since browsers limit the number of simultaneous connections that can be made to a single server.


P.S. Making BeginPolling async and using await Task.Delay(...); would prevent you from unnecessarily holding on to a server thread with Thread.Sleep, but that wouldn't solve your never ending XHR problem. So once again, I suggest using GetHubContext and a Timer outside your Hub.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top