/ AKKA.NET, ASP.NET CORE, ENTITY FRAMEWORK CORE

How to use Entity Framework Core with Akka.NET

Akka.NET is quite opinionated in terms of persistence. Paraphrasing Henry Ford’s famous quote: You can persist your data any way you want in Akka.NET, so long as you use event sourcing. But what if you don’t want to? Perhaps the problem you are trying to solve doesn’t overlap with this particular mental model, and yet you think you can still gain a lot by leveraging Akka. Then you have to bake your own solution. In this blog post, I would like to show you one way of integrating Akka.NET with an ORM. To keep things on the bleeding edge, we will use Entity Framework Core.

DbContext in Akka.NET - naive solution

If you are new to Akka.NET, you may wonder, why using Entity Framework - or another ORM of your choice - with Actors is worth writing an article about? It should be as simple as that. You want to persist the state of your actor to database? Just give me a reference to DbContext, and I am ready to go. Right? Well, technically yes, but…

Nope

Let’s consider the following example:

public class BooksManagerActor : ReceiveActor
{
    public BooksManagerActor(BookstoreContext bookstoreContext)
    {
        ReceiveAsync<CreateBook>(async command =>
        {
            var newBook = new Book
            {
                Id = Guid.NewGuid(),
                Title = command.Title,
                Author = command.Author,
                Cost = command.Cost,
                InventoryAmount = command.InventoryAmount,
            };

            bookstoreContext.Books.Add(newBook);
            await bookstoreContext.SaveChangesAsync();
        });

        ReceiveAsync<UpdateBook>(async command =>
        {
            var book = await bookstoreContext.Books.FindAsync(command.Id);
            if (book != null)
            {
                command.Patch.ApplyTo(book);
                await bookstoreContext.SaveChangesAsync();
            }
        });

        ReceiveAsync<DeleteBook>(async command =>
        {
            var book = await bookstoreContext.Books.FindAsync(command.Id);
            if (book != null)
            {
                bookstoreContext.Books.Remove(book);
                await bookstoreContext.SaveChangesAsync();
            }
        });

        ReceiveAsync<GetBookById>(async query =>
        {
            var book = await bookstoreContext.Books.FindAsync(query.Id);
            Sender.Tell(Mappings.Map(book));
        });

        ReceiveAsync<GetBooks>(async query =>
        {
            var books = await bookstoreContext.Books.Select(book => Mappings.Map(book)).ToListAsync();
            Sender.Tell(books);
        });
    }
}

At first glance, this code might look fine. We just took BooksManagerActor from How to integrate Akka.NET and ASP.NET Core example app and make it operate on DbContext instead of an in-memory collection. This code works - you can try it if you like to - but has some fundamental flaws.

The first flaw - the one that I would like to dissect in this article - is in the way BookstoreContext is obtained by our actor. If you’ve ever worked with ASP.NET, you’ve probably written dozens of services or controllers which take this kind of dependency to perform their work. In ASP.NET it’s perfectly fine to use DbContext this way because DbContext’s lifecycle scope is defined on the boundaries of a single web request. In the Akka.NET world, however, things are quite different. Due to the nature of actors which can - in theory - live forever, each dependency you inject will live as long as your actor lives. BooksManagerActor is configured to sit in memory until our app is running, and the same applies to DbContext it uses - it effectively becomes a singleton. This is dangerous because DbContext was not designed to be a long living object. It caches data and gets stale pretty soon. Moreover, it is not thread-safe, so if you decide to pass it to child actors, you are just asking for troubles.

There’s no doubt that we have to find a better way of managing DbContext dependency, if we want to use Entity Framework with our actors. One possible solution would be to create an instance of DbContext whenever we want to perform some database operation.

using (var bookstoreContext = new BookstoreContext(/*connection string*/))
{
    /*...*/
}

It should work, and it might actually solve our problem, but it is not a particularly elegant solution, to be fair. To make this possible we have to override OnConfiguring method of DbContext, and therefore make it dependent on concrete database provider implementation. This is a code smell, which directly violates the Dependency Inversion Principle.

Uncle Bob

Uncle Bob wouldn’t be happy, and besides, we would still have to find a way how to feed our BookstoreContext with a connection string. Not to mention that this design would make testing of our actors into a nightmare.

Scoped Lifetime to the rescue

To make DbContext available for our actors and leverage its full capabilities, e.g., context pooling or built-in configuration, we will use interface IServiceScopeFactory. ASP.NET Core hosting middleware uses this interface under the hood to create a scope for DI injection, and wraps each request in using statement:

using (var scope = scopeFactory.CreateScope())
{
  /*...*/
}

To use Entity Framework safely in your actors, you should apply the same pattern. By passing IServiceScopeFactory to your actors, and using it to create scope inside actors’ receive functions, you would have control over the lifecycle of your dependencies, including DbContext.

public class BooksManagerActor : ReceiveActor
{
    public BooksManagerActor(IServiceScopeFactory serviceScopeFactory)
    {
        ReceiveAsync<CreateBook>(async command =>
        {
            using (var serviceScope = serviceScopeFactory.CreateScope())
            {
                var bookstoreContext = serviceScope.ServiceProvider.GetService<BookstoreContext>();

                var newBook = new Book
                {
                    Id = Guid.NewGuid(),
                    Title = command.Title,
                    Author = command.Author,
                    Cost = command.Cost,
                    InventoryAmount = command.InventoryAmount,
                };

                bookstoreContext.Books.Add(newBook);
                await bookstoreContext.SaveChangesAsync();
            }
        });

        /*...*/
    }    
}

In this example, we are creating IServiceScope using IServiceScopeFactory. IServiceScope exposes scoped IServiceProvider which allows us to resolve BookstoreContext to perform our database work. By default, DbContext is registered with scoped lifecycle in DI container. This way we don’t have to explicitly dispose it when we don’t need it anymore. IServiceScope takes care of all the necessary clean up work on our behalf.

With DbContext pooling enabled this cleanup work results in returning DbContext to the pool for further usage.

The downside of this solution is that it is quite cumbersome, especially if you decide to delegate database operations to actors which reside at the bottom of your actor hierarchy. You will have to pass IServiceScopeFactory all the way down. Wouldn’t it be great if we have a mechanism to enhance our actors with the option to create a service scope on demand? Actually, we have this mechanism, and it is called Akka Extensions.

Building ServiceScopeExtension

Akka Extensions is a very simple, yet powerful concept. It is a general extensibility point you can use if you want to provide new features to Akka. It is heavily utilized by packages like Akka.Persistance or Akka.Cluster. In our example we will use is to fit our actor with IServiceScopeFactory.

To build Akka Extension, you have to implement 2 interfaces: IExtension and IExtensionId.

The first one is used to signify an object as an ActorSystem extension. As a marker interface, it doesn’t define any methods or properties for us to implement, but this is the place where we put our logic in.

So let’s define what ServiceScopeExtension should do:

public class ServiceScopeExtension : IExtension
{
    private IServiceScopeFactory _serviceScopeFactory;
    
    public void Initialize(IServiceScopeFactory serviceScopeFactory)
    {
        _serviceScopeFactory = serviceScopeFactory;
    }
    
    public IServiceScope CreateScope()
    {
        return _serviceScopeFactory.CreateScope();
    }
}

Then we need to implement IExtensionId contact. This is the interface containing methods for creating, registering and retrieving extensions withing ActorSystem. It may seem a bit complicated, luckily, to make our lives easier, Akka team prepared a basic implementation which takes care of all the intricacies for us. All we have to do is to inherit from ExtensionIdProvider<T> and override one method:

public class ServiceScopeExtensionIdProvider : ExtensionIdProvider<ServiceScopeExtension>
{
    public override ServiceScopeExtension CreateExtension(ExtendedActorSystem system)
    {
        return new ServiceScopeExtension();
    }

    public static ServiceScopeExtensionIdProvider Instance = new ServiceScopeExtensionIdProvider();
}

These two components are theoretically all we need to use our extension. However, if you look at the code you need to write to actually use it, then… Let’s say that Akka Extension API is not particularly user-friendly. It has some rough edges. That’s why it is considered a good practice to use C# extension methods to give it a better look and feel. In our case, we need two extension methods: the first one to initialize extension, and the second to create IServiceScope.

Here they are:

public static class Extensions
{
    public static void AddServiceScopeFactory(this ActorSystem system, IServiceScopeFactory serviceScopeFactory)
    {
        system.RegisterExtension(ServiceScopeExtensionIdProvider.Instance);
        ServiceScopeExtensionIdProvider.Instance.Get(system).Initialize(serviceScopeFactory);
    }

    public static IServiceScope CreateScope(this IActorContext context)
    {
        return ServiceScopeExtensionIdProvider.Instance.Get(context.System).CreateScope();
    }
}

In order to make our extension available at runtime, we have to go to the Startup.cs file and change the code we use to register our ActorSystem:

services.AddSingleton(provider =>
{
    var serviceScopeFactory = provider.GetService<IServiceScopeFactory>();
    var actorSystem = ActorSystem.Create("bookstore", ConfigurationLoader.Load());
    actorSystem.AddServiceScopeFactory(serviceScopeFactory);
    return actorSystem;
});

Finally, we are ready to use our extension inside BooksManagerActor:

public class BooksManagerActor : ReceiveActor
{
    public BooksManagerActor()
    {
        ReceiveAsync<CreateBook>(async command =>
        {
            using (IServiceScope serviceScope = Context.CreateScope())
            {
                var bookstoreContext = serviceScope.ServiceProvider.GetService<BookstoreContext>();
                var newBook = new Book
                {
                    Id = Guid.NewGuid(),
                    Title = command.Title,
                    Author = command.Author,
                    Cost = command.Cost,
                    InventoryAmount = command.InventoryAmount,
                };

                bookstoreContext.Books.Add(newBook);
                await bookstoreContext.SaveChangesAsync();
            }
        });
        /*...*/
    }
}

If you are using DbContext in every Receive block (like it is in our example), you may be tempted to override AroundReceive method from ActorBase class to reduce the amount of boilerplate code.

protected override bool AroundReceive(Receive receive, object message)
{
    using (IServiceScope serviceScope = Context.CreateScope())
    {
        _bookstoreContext = serviceScope.ServiceProvider.GetService<BookstoreContext>();
        return base.AroundReceive(receive, message);
    }
}

As a result, your Receive functions may look like this:

ReceiveAsync<CreateBook>(async command =>
{        
    var newBook = new Book
    {
        Id = Guid.NewGuid(),
        Title = command.Title,
        Author = command.Author,
        Cost = command.Cost,
        InventoryAmount = command.InventoryAmount,
    };

    _bookstoreContext.Books.Add(newBook);
    await _bookstoreContext.SaveChangesAsync();
});

Don’t do it! If you try to run the app with this changes applied, you will see that it doesn’t work anymore. It’s because of the way how ReceiveAsync is implemented. Every time your ReceiveAsync handler is invoked, Akka wraps it into a separate Task. To enforce one message at a time processing strategy, actor’s mailbox is suspended until the task completes. It’s a bit like cheating, but it works for most of the time. At least until you start playing with AroundReceive, which isn’t an async method and simply returns before your handler does its async work.

Summary

So, we have completed the refactoring effort to find a safe way of using DbContext inside our actors. In this blog post, you learned what is the most common problem you may face when trying to use Entity Framework Core in Akka.NET application. We solved it by utilizing IServiceScopeFactory and building Akka Extension around it.

In the next article, we will explore this subject further, and create our own Entity Framework Core based version of Persistent Actor. Stay tuned.

You can find the source code of the example application on GitHub. If you have any questions or some points are not clear to you, please, don’t hesitate to leave me a comment below!