This document describes the changes that need to be made to migrate from one version of the blog to another.
Starting with version 12.0, we provide an Upgrade Assistant tool that automates most configuration migrations. This tool:
- Automatically detects your current configuration version
- Applies necessary transformations to
appsettings.jsonfiles - Creates backups before making changes
- Provides colorful console output with clear warnings and instructions
Usage:
# From your blog directory
dotnet run --project tools/LinkDotNet.Blog.UpgradeAssistant
# Preview changes without applying
dotnet run --project tools/LinkDotNet.Blog.UpgradeAssistant -- --dry-run
# See all options
dotnet run --project tools/LinkDotNet.Blog.UpgradeAssistant -- --helpFor detailed documentation, see docs/Migrations/UpgradeAssistant.md.
Note: While the Upgrade Assistant handles most configuration changes automatically, some migrations still require manual steps (especially database schema changes). These are noted below.
The index on BlogPostRecords was replaced. Both queries that read this table (the dashboard visit counter and TransformBlogPostRecordsJob) filter on DateClicked only, but the old index led with BlogPostId and could therefore never be seeked. The replacement leads with DateClicked and carries BlogPostId and Clicks, which makes the dashboard's GROUP BY BlogPostId, SUM(Clicks) index-only.
For SQL providers, run the ChangeBlogPostRecordIndex Entity Framework migration, or execute scripts/2026-09-13-BlogPostRecordIndex.sql (SQL Server, includes an optional __EFMigrationsHistory baseline). The portable equivalent is:
DROP INDEX IX_BlogPostRecords_BlogPostId_DateClicked ON BlogPostRecords;
CREATE INDEX IX_BlogPostRecords_DateClicked_BlogPostId_Clicks
ON BlogPostRecords (DateClicked, BlogPostId, Clicks);The
DROPmay fail because the old index does not exist. That is expected on most installations, and safe to ignore.BlogDbContextbootstraps its schema withDatabase.EnsureCreated(), which only acts on an empty database and never writes__EFMigrationsHistory. On a database that already had tables when it was first started, no Entity Framework migration has ever been applied — includingAddBlogPostRecordIndex, which introduced the old index. Check what you actually have withEXEC sp_helpindex 'BlogPostRecords'before running anything.
ShowBlogPostPage recorded a visit on every render rather than only the first, so each page view was counted at least twice. This is now fixed. Expect the numbers on the dashboard to drop by roughly half from the upgrade date onward — that is the correction, not a drop in traffic. Historical data is left untouched and is not comparable with data recorded after the upgrade.
Code blocks now have a header that contains the copy button and, optionally, the language of the code block. The ShowCodeBlockLanguage setting was added on the root level of the appsettings.json file (handled by the Upgrade Assistant). The default is true, set it to false to hide the language.
{
...
"ShowCodeBlockLanguage": true
}A new BrokenLinks table is introduced to store the results of the broken link checker. For SQL providers, run the AddBrokenLinks Entity Framework migration or execute the following script:
CREATE TABLE BrokenLinks
(
Id [VARCHAR](900) NOT NULL,
BlogPostId [NVARCHAR](256) NOT NULL,
BlogPostTitle [NVARCHAR](256) NOT NULL,
Url NVARCHAR(MAX) NOT NULL,
Reason [NVARCHAR](1024) NOT NULL,
CheckedDate DATETIME2 NOT NULL,
)
ALTER TABLE BrokenLinks
ADD CONSTRAINT PK_BrokenLinks PRIMARY KEY (Id)The EnableBrokenLinkChecker setting was added on the root level of the appsettings.json file (handled by the Upgrade Assistant). The default is true, set it to false to turn the checker off.
{
...
"EnableBrokenLinkChecker": true
}EnableTagDiscoveryPanel setting was added on the root level of the appsettings.json file. This setting controls whether the Tag Discovery panel is shown in the navigation bar.
{
...
"EnableTagDiscoveryPanel": true
}ShowBuildInformation setting was added on the root level of the appsettings.json file. This setting controls whether build information (like build date) is shown in the Footer component.
{
...
"ShowBuildInformation": true
}A new config has been added UseMultiAuthorMode in appsettings.json. The default value of this config is false. If set to true then author name will be associated with blog posts at the time of creation.
Starting with v9.0 the blog uses Entity Framework Migrations for all SQL providers. If you are already having a database you need to run the following script that creates the history table and the initial entry:
IF OBJECT_ID(N'[__EFMigrationsHistory]') IS NULL
BEGIN
CREATE TABLE [__EFMigrationsHistory] (
[MigrationId] nvarchar(150) NOT NULL,
[ProductVersion] nvarchar(32) NOT NULL,
CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId])
);
END;
GO
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20241128180004_Initial', N'8.0.11');
GORead more in the documentation.
If you used the sponsor/donation mechanism in the appsettings.json like this:
{
...
"KofiToken": "TokenHere",
"GithubSponsorName": "namehere",
"PatreonName": "namehere",
}These moved to their own respective subsection:
{
"SupportMe": {
"KofiToken": "TokenHere",
"GithubSponsorName": "namehere",
"PatreonName": "namehere",
"ShowUnderBlogPost": true,
}
}The ShowUnderBlogPost is needed to indicate that the part will be shown under each blog post. We also added more possibilities to customize this:
"SupportMe": {
"KofiToken": "ABC123",
"GithubSponsorName": "your-tag-here",
"PatreonName": "your-tag-here",
"ShowUnderBlogPost": true,
"ShowUnderIntroduction": true,
"ShowInFooter": true,
"ShowSupportMePage": true,
"SupportMePageDescription": "Buy my book here: [My Blazor Book](https://google.com) or please contribute to my open-source project here: [My Awesome Repo](https://github.com) . This can be **markdown**."
}Use true or false to choose where you want the donation buttons to appear and also a support me page can optionally be added to the nav menu. Checkout the Donation section in the documentation.
Shortcodes, a form a templating that can be adjusted dynamically, are introduced in this version. The following table has to be added to the database:
CREATE TABLE Shortcodes
(
Id [NVARCHAR](450) NOT NULL,
Name [NVARCHAR](512) NOT NULL,
MarkdownContent NVARCHAR(MAX) NOT NULL,
)
ALTER TABLE Shortcodes
ADD CONSTRAINT PK_Shortcodes PRIMARY KEY (Id)A new SimilarBlogPost table is introduced to store similar blog posts.
CREATE TABLE SimilarBlogPosts
(
Id [NVARCHAR](450) NOT NULL,
SimilarBlogPostIds NVARCHAR(1350) NOT NULL,
)
ALTER TABLE SimilarBlogPosts
ADD CONSTRAINT PK_SimilarBlogPosts PRIMARY KEY (Id)Add the following to the appsettings.json:
{
"SimilarBlogPosts": true
}Or false if you don't want to use this feature.