
TL;DR: Wolverine has a pretty good development and production time story for developers using EF Core and that is constantly being improved.
Wolverine was explicitly restarted 3-4 years back specifically to combine with Marten as a complete end to end solution for Event Sourcing and CQRS with asynchronous messaging support. While that “Critter Stack” strategy has definitely paid off, vastly more .NET developers and systems are using EF Core as their primary persistence mechanism. And since I’d personally like to see Wolverine get much more usage and see JasperFx Software continue to grow, we’ve made a serious effort to improve the development time experience with EF Core and Wolverine.
To get started using EF Core with Wolverine, install this Nuget:
dotnet add package WolverineFx.EntityFrameworkCore
I should say, that’s not expressly necessary, but all of the development time accelerators, middleware, and transactional inbox/outbox integration we’re about to utilize require that library.
Let’s just get started with a simple Wolverine bootstrapping configuration that is going to use a single EF Core DbContext (for now, Wolverine happily supports using multiple DbContext types in a single application) and SQL Server for the Wolverine message persistence we’ll need for transactional outbox support later:
var builder = Host.CreateApplicationBuilder();var connectionString = builder.Configuration.GetConnectionString("sqlserver")!;// Register a DbContext or multiple DbContext types as normalbuilder.Services.AddDbContext<ItemsDbContext>( x => x.UseSqlServer(connectionString), // This is actually a significant performance gain // for Wolverine's sake optionsLifetime:ServiceLifetime.Singleton);// Register Wolverinebuilder.UseWolverine(opts =>{ // You'll need to independently tell Wolverine where and how to // store messages as part of the transactional inbox/outbox opts.PersistMessagesWithSqlServer(connectionString); // Adding EF Core transactional middleware, saga support, // and EF Core support for Wolverine storage operations opts.UseEntityFrameworkCoreTransactions();});// Rest of your bootstrapping...
With that in place, let’s look at a simple message handler that uses our ItemsDbContext:
public static class CreateItemCommandHandler{ public static ItemCreated Handle( // This would be the message CreateItemCommand command, // Any other arguments are assumed // to be service dependencies ItemsDbContext db) { // Create a new Item entity var item = new Item { Name = command.Name }; // Add the item to the current // DbContext unit of work db.Items.Add(item); // This event being returned // by the handler will be automatically sent // out as a "cascading" message return new ItemCreated { Id = item.Id }; }}
In the handler above, you’ll notice there’s no synchronous calls at all, and that’s because we’ve turned on Wolverine’s transactional middleware for EF Core that will handle the actual transaction management. You’ll also notice that we’re using Wolverine’s cascading messages syntax to kick out an ItemCreated domain event upon the successful completion of this handler. With the EF Core transactional middleware, that is also handling any integration with Wolverine’s transactional outbox for reliable messaging. Absolutely nothing else for you to do in that handler to enable any of that behavior, and we can shove off some of the typically ugly async/await mechanics into Wolverine itself while keeping our actual application behavior cleaner.
Now let’s go a little farther and utilize some Wolverine optimizations for our EF Core usage and change the service registration up above to this:
// If you're okay with this, this will register the DbContext as normally,// but make some Wolverine specific optimizations at the same timebuilder.Services.AddDbContextWithWolverineIntegration<ItemsDbContext>( x => x.UseSqlServer(connectionString), "wolverine");
That version of the integration optimizes application performance by fine tuning the service lifetimes in a way that improves Wolverine’s internal usage of the DbContext type, and adds direct mappings for Wolverine’s internal inbox and outbox storage. By using a “Wolverine optimized DbContext” like this, Wolverine is able to improve your system’s performance by allowing EF Core to batch the SQL commands for your application code and Wolverine’s transactional outbox storage in a single database round trip — and that’s important because the single most common killer of performance in enterprise applications is database chattiness!
So that’s the bare bones basics, now let’s look at some recent improvements in Wolverine for…
Development Time Usage with EF Core
We’ve invested a lot of time recently in trying to make EF Core easier to work with at development time with Wolverine. Coming from Marten where our database migrations have an “it should just work” model that quietly configures the database to match your application configuration at runtime for quick iteration at development time.
With the Wolverine.EntityFrameworkCore library, you can get that same behavior with EF Core through this option:
builder.UseWolverine(opts =>{ opts.Services.AddDbContextWithWolverineIntegration<ItemsDbContext>( x => x.UseSqlServer(connectionString)); // Diff the DbContext against the live DB at startup and apply missing DDL. opts.UseEntityFrameworkCoreWolverineManagedMigrations(); // This will make Wolverine do any necessary database migration // work happen at application startup opts.Services.AddResourceSetupOnStartup();});
To be clear, with this setup, you can change your EF Core mappings, then restart the application or an IHost in testing and your application will automatically detect any database differences from the configuration and quietly apply a patch for you on application startup. This enables a much faster iteration cycle than EF Core Migrations do in my opinion.
The Weasel docs go deeper on the diff engine, opt-outs, and how it handles schemas.
Another feature in Marten that our community utilizes very heavily is the ability to quickly reset the state of a database in tests. I’ve also occasionally used the Respawn library for the same kind of ability when developing closer to the metal of a relational database to do the same. In a recent version of Wolverine, we’ve added similar abilities to our EF Core support including a version of Marten’s IInitialData concept to help you reset data in tests:
public class SeedItems : IInitialData<ItemsDbContext>{ public async Task Populate(ItemsDbContext context, CancellationToken cancellation) { context.Items.Add(new Item { Name = "Seed" }); await context.SaveChangesAsync(cancellation); }}builder.Services.AddInitialData<ItemsDbContext, SeedItems>();
And to see that in usage:
[Fact]public async Task ordering_flow(){ await _host.ResetAllDataAsync<ItemsDbContext>(); // arrange ... act ... assert}
The ResetAllDataAsync<T>() method will look through a DbContext object to see all the tables it maps to, and delete all the data in those tables. It does take into account foreign key relationships to order its operations. After the data is wiped out, each IInitialData<T> registered in your system will be applied to lay down baseline data.
While this feature will surely have to be enhanced if many people start using it, this is already helping us make the Wolverine internal EF Core testing a lot more reliable and easier to use.
Declarative Persistence with EF Core
The next usage is special to Wolverine. A lot of times in simpler HTTP endpoints or command handlers you simply need to load an entity by its identity or primary key. And frequently, you’ll need to apply some repetitive validation that the entity exists in the first place. For that common need, Wolverine has its declarative persistence helpers like the [Entity] attribute shown below that can automatically load an entity through EF Core by its identity on the incoming command type implied by some naming conventions like this sample below:
The mapping of the identity can be explicitly mapped as well of course, and the pre-generated code always reveals Wolverine’s behavior around handlers or HTTP endpoint methods.
public class ItemsDbContext : DbContext{ public DbSet<BacklogItem> BacklogItems { get; set; } public DbSet<Sprint> Sprints { get; set; } }public record CommitToSprint(Guid BacklogItemId, Guid SprintId);public static class CommitToSprintHandler{ public static object[] Handle( CommitToSprint command, // There's a naming convention here about how // Wolverine "knows" the id for the BacklogItem // from the incoming command [Entity(Required = true)] BacklogItem item, [Entity(Required = true)] Sprint sprint ) { return item.CommitTo(sprint); }}
In the code above, Wolverine “knows” that the ItemsDbContext persists both the BacklogItems and Sprint entities, so it’s generating code around your handler to load these entities through ItemsDbContext. We can also tell Wolverine to automatically stop handling or in HTTP usage return a 400 ProblemDetails response if either of the requested entities are missing in the database. This helps keep Wolverine handler or HTTP endpoint code simpler by eliminating asynchronous code and letting you write more and more business or workflow logic in pure functions that are easy to test.
In the code above, the EF Core transactional middleware is calling ItemsDbContext.SaveChangesAsync() for you, and the automatic EF Core change tracking will catch the change to the BacklogItem.
And now, I think this is cool, Wolverine has its own new mechanism to batch up the two queries above through a custom EF Core futures query mechanism so that the handler above can fetch both the BacklogItem and the Sprint entity in one database round trip.
But wait, there’s more!
At the risk of making this blog post way too long, here’s more ways that Wolverine can make EF Core usage more successful:
- Operation side effects for the Functional Programmer in your life
- Saga storage with EF Core, which I do recommend if your saga also needs to persist other data in its operations using the same
DbContexttype - Multi-tenancy support
- Domain events integration for you folks that want your
Entitytypes to publish their own domain events, then publish them to Wolverine - A new Specification pattern support for Wolverine & EF Core we call “Query Plans” based on a similar feature in Marten
- Wolverine’s new Batch Querying support for EF Core similar to a Marten feature