0

I have a problem saving changes to my database using SaveChangesAsync inside a AKKA.NET receive function. Can someone explain what is going on?

Some more details: I have this receive block:

Receive<UpdateUnitVM>((msg) => {
                using (var scope = AutofacDependencyContainer.Current.CreateTenantScope(new TenantId(msg.UnitConfiguration.TenantId)))
                {
                    var db = scope.GetService<ApplicationDbContext>();

                    ...work on some objects inside the db context

                    //now save and continue with next actor using PipeTo
                    var continueFlags = TaskContinuationOptions.AttachedToParent & TaskContinuationOptions.ExecuteSynchronously;
                    db.SaveChangesAsync().ContinueWith(myint => new UnitConditionRuleGuard.UnitChanged(msg.UnitConfiguration.GetTenantUnitPair()), continueFlags).PipeTo(unitConditionRuleGuard);
                }
            });

If I change the code to use SaveChanges like this it works:

Receive<UpdateUnitVM>((msg) => {
                using (var scope = AutofacDependencyContainer.Current.CreateTenantScope(new TenantId(msg.UnitConfiguration.TenantId)))
                {
                    var db = scope.GetService<ApplicationDbContext>();

                    ...work on some objects inside the db context

                    //now save (blocking) and continue with next actor sequentially
                    db.SaveChanges();
                    unitConditionRuleGuard.Tell(new UnitConditionRuleGuard.UnitChanged(msg.UnitConfiguration.GetTenantUnitPair()));
                }
            });

Be aware that the using-block creates a new Autofac dependency-injection container scope, so after the using block is exited, the db-object is disposed. I have a feeling that this is the problem. However, I'm not sure what to do about it and how to extend the lifetime of the dbcontext-object appropriately.

Nikola Schou
  • 2,386
  • 3
  • 23
  • 47
  • Not sure if it is good for your scenario, but have you tried `ReceiveAsync()` and await `db.SaveChanges()`? – Gigi Sep 08 '16 at 13:00

3 Answers3

3

Using SaveChangesAsync + ContinueWith is not the same as awaiting for saving changes to complete. What you're doing there, is making asynchronous call with subsequent operations and then disposing the database context before it finish to save the changes.

You can either fix that by using ReceiveAsync handler and using await db.SaveChangesAsync(); to complete, or by throwing using { .. } block away and disposing the scope explicitly instead, inside the ContinueWith block, you've written at the beginning - second option is more risky, as actor won't be waiting for operation to complete before processing a next message and may create more than one scope in the result.

Bartosz Sypytkowski
  • 7,463
  • 19
  • 36
1

Well I wouldn't use a DI container. :)

One alternative would be to manually manage the db context, rather than 'using (var thing)' - this would work by opening a connection, doing the work, then in the continuation once you have saved asynchronously, close the connection, then PipeTo somewhere else.

tomliversidge
  • 2,339
  • 1
  • 15
  • 15
  • I'm not ready to throw away my DI-container, as it gives me other advantages:-) However, I followed your advice in a slightly different way, still using the DI-container. by removing the using-block and adding this task continuation: ...ContinueWith(x => scope.Dispose()). The DI-scope will cleanup and dispose the DbContext. Now it works. However, I'm concerned what will happen if an exception is thrown so that we never reach the last ContinueWith-block. – Nikola Schou Sep 07 '16 at 12:56
  • Wouldn't the exception be caught in the continuation? You can check the task to see if it has errored – tomliversidge Sep 07 '16 at 13:27
0

I ended up with this working solution which seems pretty good and still fairly simple. My only concern left is how it will behave if exceptions are thrown in one of the continuations so further comments will be appreciated.

Receive<UpdateUnitVM>((msg) => {
                var scope = AutofacDependencyContainer.Current.CreateTenantScope(new TenantId(msg.UnitConfiguration.TenantId));

                var db = scope.GetService<ApplicationDbContext>();

                ...work on some objects inside the db context...

                //now save and continue with next actor using PipeTo
                var continueFlags = TaskContinuationOptions.AttachedToParent & TaskContinuationOptions.ExecuteSynchronously;
                db.SaveChangesAsync()
                   .ContinueWith(...transform message..., continueFlags)
                   .PipeTo(...some other actor...)
                   .ContinueWith(x => scope.Dispose(), continueFlags);
            }
        });
Nikola Schou
  • 2,386
  • 3
  • 23
  • 47