4

I have written a service which runs a SchedulerCallback once a day and does various work and updates a MS SQL database using EntityFramework 6.1.3, ApplicationUserManager and LINQ

After each task it saves the changes using SaveChanges on a global context that lives within the class, but every few weeks one or more of the SaveChanges fail with: "The transaction operation cannot be performed because there are pending requests working on this transaction".

The scheduler looks like this:

Timer Scheduler = new Timer(new TimerCallback(SchedulerCallback))

All the SaveChanges are done after any loops and I have been unable to discover what I am doing wrong in the Entity Framework documentation or on Stack Overflow.

A much simplified example of my service class ignoring the scheduling and performing only one task and SaveChanges:

public partial class MyService : ServiceBase
{
    private MyContext myContext { get; set; }
    private ApplicationUserManager UserManager { get; set; }

    public MyService()
    {
        MyContext = new MyContext();
        UserManager = new ApplicationUserManager(new UserStore<ApplicationUser>(MyContext));
    }

    private void SchedulerCallback()
    {
        var users = (from u in MyContext.Users where u.Status = StatusFlag.PlaceOrder select u.User).ToList();

        foreach (var user in users)
        {
            myContext.Order.Add(new MyOrder());
            myContext.Order.User = user;

            OrderTransaction transaction new OrderTransaction();
            order.Transactions = new List<OrderTransaction>();
            order.Transactions.Add(transaction);

            user.Status = StatusFlag.OrderPlaced;
        }

        try
        {
            myContext.SaveChanges();
        }
        catch (Exception e)
        {
            Logger.LogError("Exception", e);
        }
    }   
}

In my service SchedulerCallback does a lot more and calls SaveChanges() on the context multiple times, but not in a loop but rather for various tasks. It is not the first time I call SaveChanges that the error occurs, at least not so far, but then again it doesn't happen very often so it is hard to establish any pattern.

What am I doing wrong?

Tarostar
  • 1,196
  • 1
  • 15
  • 27
  • Is there any other system, besides your Scheduler that may edit the same data while the scheduler is running? I'm thinking that other system may open a transaction and sometimes update the same rows of the table that you are trying to modify. – Cristian Rusanu Jun 19 '17 at 14:46
  • Interesting line of thought, but I'm fairly sure this is not the problem as it is very rare that anyone would edit their data and that they would do this early in the morning when the scheduler runs seems incredibly unlikely. – Tarostar Jun 20 '17 at 04:13
  • As a temporary measure I now try to sleep the service thread for 1 second and then retry the SaveChanges if an exception occurred. This should also solve the problem if someone was just editing their data, but feels very hacky and of course it might just fail again. – Tarostar Jun 20 '17 at 04:16

1 Answers1

3

Timer's callback is executed in different threads (similar problem), but DbContext is not thread safe, so create context just before doing any work:

public partial class MyService : ServiceBase
{
    private void SchedulerCallback()
    {
        using (var myContext = new MyContext())
        {
            var UserManager = new ApplicationUserManager(new UserStore<ApplicationUser>(MyContext));

            var users = (from u in myContext.Users where u.Status = StatusFlag.PlaceOrder select u.User).ToList();

            foreach (var user in users)
            {
                myContext.Order.Add(new MyOrder());
                myContext.Order.User = user;

                OrderTransaction transaction = new OrderTransaction();
                order.Transactions = new List<OrderTransaction>();
                order.Transactions.Add(transaction);

                user.Status = StatusFlag.OrderPlaced;
            }

            try
            {
                myContext.SaveChanges();
            }
            catch (Exception e)
            {
                Logger.LogError("Exception", e);
            }
        }
    }   
} 
Ivan R.
  • 1,875
  • 1
  • 13
  • 11
  • I have considered this, but I use the ApplicationUserManager and do LINQ queries on the context at many different points and so it would complicate the code quite a bit. I'll do it if there is no other option, but I would like to understand what is causing this exception. – Tarostar Jun 20 '17 at 04:08
  • Further to this, it is my understanding that SaveChanges is a complete transaction. So given that all my other uses of the context are only reading from the database previous SaveChanges and reads on the context in the service should not causing this error. – Tarostar Jun 20 '17 at 04:20
  • 1
    Anyway, you can implement Dispose methodof MyService. It does not take a long time. (I have made some fixes in my answer). I am not sure but I think the cause of this problem that context object is not properly disposed. Also you can set up logging EF to catch not committed transaction. [Link](https://msdn.microsoft.com/en-us/library/dn469464(v=vs.113).aspx) – Ivan R. Jun 20 '17 at 09:16
  • From my [reading](https://stackoverflow.com/questions/15666824/entity-framework-and-context-dispose) I don't think I need to call dispose. However, to make sure the context is being properly disposed as you say I have moved the context into a using statement in the SchedulerCallback function. It means I have to pass the context and ApplicationUserManager to all the various functions using them, but at least I am sure that a new context is created each time the service runs. If this doesn't help I might create the context for each SaveChanges call. Interesting link on EF logging. Thanks! – Tarostar Jun 20 '17 at 12:53
  • I have found the root of problem so I have updated my answer explaining. – Ivan R. Jun 21 '17 at 10:41
  • The daily callaback has now been running for 3 months wrapping the context inside the "using" statement for for each callback as explained by Ivan above and there has not been a single incident despite increasing load. Thanks Ivan. – Tarostar Sep 19 '17 at 04:38