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…
Let’s consider the following example:
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.
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 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:
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
.
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:
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:
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:
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
:
Finally, we are ready to use our extension inside BooksManagerActor
:
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.
As a result, your Receive
functions may look like this:
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!
Subscribe to Havret on Software
Get the latest posts delivered right to your inbox