Could not resolve Piranha.IApi

An odd runtime, Piranha CMS related error due to mismatched Entity Framework libraries.
Piranha CMS
Piranha is an open source, headless content management system powered by ASP.NET Core. It makes a great engine for blogs by storing and providing structured content on demand via its API. Read more about the Piranha CMS at their website or via Github.
The Issue
Several months ago, I had created a website using Piranha's out of the box project templates for ASP.NET Core. Of course, life happened, and the project sat dormant for some time. When I came back to the project months later, I updated all of the project's NuGet dependencies except for the Pirhana packages. I left those for last. Each time I updated a set of related packages (i.e. ASP.NET Core related libs, Entity Framework libs, etc.) I tested out the site expecting to see one thing or another broken (just my luck 😊). At each step, I was happy to find nothing was broken and the site worked as expected. Until, that is, I upgraded the Piranha packages from generation 8.3 to 8.4.
To be honest I wasn't feeling too bad about things, until I launched the app and saw this nastiness at the top of the stack trace.
PM> dotnet run
[10:09:25 FTL] Application startup exception
System.Exception: Could not resolve a service of type 'Piranha.IApi' for the parameter 'api' of method 'Configure' on type 'FooApp.Web.Startup'.
---> System.MissingMethodException: Method not found: 'Microsoft.EntityFrameworkCore.Migrations.Operations.Builders.OperationBuilder`1<Microsoft.EntityFrameworkCore.Migrations.Operations.AddColumnOperation> Microsoft.EntityFrameworkCore.Migrations.Operations.Builders.ColumnsBuilder.Column(System.String, System.Nullable`1<Boolean>, System.Nullable`1<Int32>, Boolean, System.String, Boolean, System.Object, System.String, System.String, System.Nullable`1<Boolean>, System.String)'.
at Piranha.Data.EF.SQLServer.Migrations.InitialCreate.<>c.<Up>b__0_0(ColumnsBuilder table)
at Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder.CreateTable[TColumns](String name, Func`2 columns, String schema, Action`1 constraints, String comment)
at Piranha.Data.EF.SQLServer.Migrations.InitialCreate.Up(MigrationBuilder migrationBuilder)
at Microsoft.EntityFrameworkCore.Migrations.Migration.BuildOperations(Action`1 buildAction)
at Microsoft.EntityFrameworkCore.Migrations.Migration.get_UpOperations()
at Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.GenerateUpSql(Migration migration, MigrationsSqlGenerationOptions options)
at Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.<>c__DisplayClass16_2.<GetMigrationCommandLists>b__2()
at Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.Migrate(String targetMigration)
at Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.Migrate(DatabaseFacade databaseFacade)
at Piranha.Db`1..ctor(DbContextOptions`1 options)
at Piranha.Data.EF.SQLServer.SQLServerDb..ctor(DbContextOptions`1 options)
Red Herring? ... Or Maybe I'm Just Slow
The error above is complaining about this snippet of code from my app. It's claiming that it can't find the argument to IApi
to the Configure
method in Startup.cs
.
public void Configure(
IApplicationBuilder app,
IWebHostEnvironment env,
IApi api)
What on earth? It turns out, a missing IApi variable is often related to database issues. As shown below, this specific case was triggered by Piranha not being able to run a series of EF migrations.
Online Answers
Naturally, I looked for a solution to this issue and found a few ideas. Most of the information I found revolved around an inability for Piranha to connect to the underlying database. I double checked connection strings, found they were solid and kept troubleshooting.
What's Actually Happening
Looking at the stack trace more closely, I noticed the following entries leading to a System.MissingMethodException
.
System.MissingMethodException: Method not found: 'Microsoft.EntityFrameworkCore.Migrations.Operations.Builders.OperationBuilder`1<Microsoft.EntityFrameworkCore.Migrations.Operations.AddColumnOperation> Microsoft.EntityFrameworkCore.Migrations.Operations.Builders.ColumnsBuilder.Column(System.String, System.Nullable`1<Boolean>, System.Nullable`1<Int32>, Boolean, System.String, Boolean, System.Object, System.String, System.String, System.Nullable`1<Boolean>, System.String)'.
at Piranha.Data.EF.SQLServer.Migrations.InitialCreate.<>c.<Up>b__0_0(ColumnsBuilder table)
at Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder.CreateTable[TColumns](String name, Func`2 columns, String schema, Action`1 constraints, String comment)
at Piranha.Data.EF.SQLServer.Migrations.InitialCreate.Up(MigrationBuilder migrationBuilder)
Apparently, as part of the upgrade from gen 8.3 to 8.4 Piranha requires a few database changes that are trying to be invoked via Entity Framework Migrations. And it seems that, OperationBuilder<TOperation>
can't be found by the runtime.
But why?
So, after a little bit of digging I saw the OperationsBuilder<TOperation>
class in question was part of the Microsoft.EntityFrameworkCore.Relational
assembly. And what's odd is that this assembly is included in the application build as a transitive dependency.
It occurred to me that I might have a different version of this assembly than Piranha is expecting. Sure enough, after fetching the source code to Piranha's 8.3 product and loading it into Visual Studio, I came across this.
You can also more easily find the EF version number Piranha is using by querying the __EFMigrationsHistory
table and examining the ProductVersion
column. I guess the moral of the story is, stick with the version of EF Piranha CMS uses if you want a trouble free experience.
A Crude Workaround
I can be stubborn sometimes, and didn't really feel like giving up on the latest version of Entity Framework and friends. I wish I could tell you there was a really good reason behind this thinking, but to be completely honest, I'm not even sure what the feature set of EF Core gen 5 offers above gen 3.1. Still, I had a hunch that if I could keep Piranha's EF Context from trying to invoke database migrations the app would probably work.
Script-Migration
⬅️ 👏
In the end, I created a new project using Piranha's Template and instead of running the project, issued Script-Migration
in Visual Studio's Package Manager Console.
PM> Script-Migration
The output of Script-Migration
is a set of SQL (in this case TSQL) statements. A comparison of my existing database's __EFMigrationsHistory
table and the full output of Script-Manager
indicated that I only needed to issue the following statements to make my database compatible with Piranha 8.4.
ALTER TABLE [Piranha_Sites] ADD [LogoId] uniqueidentifier NULL;
GO
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20200625060542_AddSiteLogo', N'3.1.0');
GO
ALTER TABLE [Piranha_Posts] ADD [MetaTitle] nvarchar(128) NULL;
GO
ALTER TABLE [Piranha_Posts] ADD [OgDescription] nvarchar(256) NULL;
GO
ALTER TABLE [Piranha_Posts] ADD [OgImageId] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000';
GO
ALTER TABLE [Piranha_Posts] ADD [OgTitle] nvarchar(128) NULL;
GO
ALTER TABLE [Piranha_Pages] ADD [MetaTitle] nvarchar(128) NULL;
GO
ALTER TABLE [Piranha_Pages] ADD [OgDescription] nvarchar(256) NULL;
GO
ALTER TABLE [Piranha_Pages] ADD [OgImageId] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000';
GO
ALTER TABLE [Piranha_Pages] ADD [OgTitle] nvarchar(128) NULL;
GO
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20200812110105_AddOgFields', N'3.1.0');
GO
After issuing the statements above, I was able to run the app without incident.