{"ArticleId":null,"Name":"Upgrade migrations","Content":"\u003Cp\u003ENew installations get their data from the installer. Existing installations are brought up to date by \u003Cem\u003Eupgrade migrations\u003C/em\u003E: small classes that run once, at startup, when a store starts a newer GrandNode version against an older database. Whenever a change adds localization resources, permissions, admin menu entries, scheduled tasks, settings or indexes, it needs both \u2014 seed data for new installs and a migration for existing ones.\u003C/p\u003E\n\n\u003Ch2 id=\u0022how-it-runs\u0022\u003EHow migrations run\u003C/h2\u003E\n\u003Col\u003E\n\u003Cli\u003EAt startup the \u003Ccode\u003EGrand.Module.Migration\u003C/code\u003E module (enabled by \u003Ccode\u003EFeatureManagement:Grand.Module.Migration\u003C/code\u003E) reads the version stamp stored in the database.\u003C/li\u003E\n\u003Cli\u003EIt collects every class implementing \u003Ccode\u003EIMigration\u003C/code\u003E whose \u003Ccode\u003EVersion\u003C/code\u003E is \u003Cstrong\u003Egreater\u003C/strong\u003E than that stamp, ordered by version and then by \u003Ccode\u003EPriority\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003EIt skips migrations whose \u003Ccode\u003EIdentity\u003C/code\u003E is already recorded in the migrations collection, runs the others and records each success.\u003C/li\u003E\n\u003Cli\u003EThe last migration of every version is the version stamp itself (\u003Ccode\u003EMigrationUpgradeDbVersion_24\u003C/code\u003E for 2.4), which moves the database to that version.\u003C/li\u003E\n\u003Cli\u003EIf a migration returns \u003Ccode\u003Efalse\u003C/code\u003E, the process stops before the stamp, logs the error and retries on the next start.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe consequence: a migration placed in the folder of the version a database is \u003Cem\u003Ealready\u003C/em\u003E stamped with never runs there. Migrations reach installations that upgrade from an older version.\u003C/p\u003E\n\n\u003Ch2 id=\u0022shape\u0022\u003EThe shape of a migration\u003C/h2\u003E\n\u003Cp\u003EMigrations live in \u003Ccode\u003Esrc/Modules/Grand.Module.Migration/Migrations/\u0026lt;major.minor\u0026gt;/\u003C/code\u003E. The folder \u003Ccode\u003E2.4\u003C/code\u003E maps to the namespace segment \u003Ccode\u003E_2._4\u003C/code\u003E.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003Eusing Grand.Infrastructure.Migrations;\n\nnamespace Grand.Module.Migration.Migrations._2._4;\n\npublic class MigrationUpdateResourceString : IMigration\n{\n    public int Priority =\u0026gt; 0;\n    public DbVersion Version =\u0026gt; new(2, 4);\n    public Guid Identity =\u0026gt; new(\u00226C0F2E4B-9A17-4D65-B3C8-51E0A9D77B42\u0022);\n    public string Name =\u0026gt; \u0022Update resource string for english language 2.4\u0022;\n\n    public bool UpgradeProcess(IServiceProvider serviceProvider)\n    {\n        return serviceProvider.ImportLanguageResourcesFromXml(\u0022App_Data/Resources/Upgrade/en_240.xml\u0022);\n    }\n}\u003C/code\u003E\u003C/pre\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\u003Ctr\u003E\u003Cth\u003EMember\u003C/th\u003E\u003Cth\u003ERule\u003C/th\u003E\u003C/tr\u003E\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EVersion\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EThe release the change ships in. It must match the folder.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EIdentity\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EA freshly generated GUID. The runner uses it to know the migration already ran; a GUID copied from another migration makes one of them silently never run.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EPriority\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EOrder inside the version. Resources, permissions and menu migrations conventionally use \u003Ccode\u003E0\u003C/code\u003E; migrations that depend on them run higher.\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003E\u003Ccode\u003EName\u003C/code\u003E\u003C/td\u003E\u003Ctd\u003EHuman-readable, including the version. It appears in the log.\u003C/td\u003E\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\n\u003Ch2 id=\u0022writing\u0022\u003EWriting UpgradeProcess\u003C/h2\u003E\n\u003Cp\u003EResolve what you need from the \u003Ccode\u003EserviceProvider\u003C/code\u003E. There is no HTTP request, so there is no current store, customer or language \u2014 pass store ids explicitly.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003Epublic bool UpgradeProcess(IServiceProvider serviceProvider)\n{\n    try\n    {\n        var repository = serviceProvider.GetRequiredService\u0026lt;IRepository\u0026lt;ScheduleTask\u0026gt;\u0026gt;();\n        if (repository.Table.Any(x =\u0026gt; x.ScheduleTaskName == \u0022Example task\u0022))\n            return true;   // already there: running twice must be a no-op\n\n        repository.Insert(new ScheduleTask {\n            ScheduleTaskName = \u0022Example task\u0022,\n            Enabled = false,\n            TimeInterval = 60\n        });\n        return true;\n    }\n    catch (Exception ex)\n    {\n        serviceProvider.GetRequiredService\u0026lt;ILogger\u0026lt;MigrationExample\u0026gt;\u0026gt;()\n            .LogError(ex, \u0022Example migration failed\u0022);\n        return false;\n    }\n}\u003C/code\u003E\u003C/pre\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003ENever throw.\u003C/strong\u003E Catch, log and return \u003Ccode\u003Efalse\u003C/code\u003E.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EBe idempotent.\u003C/strong\u003E Check before inserting; a migration can run again after a partial upgrade.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EDo not destroy operator data.\u003C/strong\u003E Change only values an operator cannot have customised.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EPreserve behaviour.\u003C/strong\u003E A new setting gets the default that keeps an upgraded store working as before.\u003C/li\u003E\n\u003C/ul\u003E\n\n\u003Ch2 id=\u0022resources\u0022\u003ELocalization resources\u003C/h2\u003E\n\u003Cp\u003EDo not write resource strings in C#. Add each new key twice: to \u003Ccode\u003EApp_Data/Resources/DefaultLanguage.xml\u003C/code\u003E for new installations and to the upgrade file of the release, for example \u003Ccode\u003EApp_Data/Resources/Upgrade/en_240.xml\u003C/code\u003E, which the version\u0027s \u003Ccode\u003EMigrationUpdateResourceString\u003C/code\u003E imports. \u003Ccode\u003EDefaultLanguage.xml\u003C/code\u003E is saved as UTF-16, so plain \u003Ccode\u003Egrep\u003C/code\u003E finds nothing in it; convert it first or use an editor.\u003C/p\u003E\n\n\u003Ch2 id=\u0022other-kinds\u0022\u003EOther kinds of migration\u003C/h2\u003E\n\u003Cp\u003ECopy the file of the same name from the newest version folder rather than inventing a shape:\u003C/p\u003E\n\u003Ctable\u003E\n\u003Cthead\u003E\u003Ctr\u003E\u003Cth\u003EChange\u003C/th\u003E\u003Cth\u003EFollow\u003C/th\u003E\u003C/tr\u003E\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\u003Ctd\u003EPermissions\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003EMigrationSystemPermission.cs\u003C/code\u003E, \u003Ccode\u003EMigrationUpdateStandardPermissionNames.cs\u003C/code\u003E\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003EAdmin navigation\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003EMigrationUpdateAdminSiteMap.cs\u003C/code\u003E\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003EScheduled tasks\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003EMigrationScheduleTasks.cs\u003C/code\u003E\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003ESettings and store data\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003EMigrationUpdateMediaSettings.cs\u003C/code\u003E, \u003Ccode\u003EMigrationStoreSingleUrl.cs\u003C/code\u003E\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003EIndexes\u003C/td\u003E\u003Ctd\u003E\u003Ccode\u003EMigrationUniqueOrderNumberIndex.cs\u003C/code\u003E\u003C/td\u003E\u003C/tr\u003E\n\u003Ctr\u003E\u003Ctd\u003EA new version folder\u003C/td\u003E\u003Ctd\u003ENeeds its own \u003Ccode\u003EMigrationUpgradeDbVersion_XX\u003C/code\u003E deriving from \u003Ccode\u003EMigrationUpgradeDbVersionBase\u003C/code\u003E.\u003C/td\u003E\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\n\u003Cp\u003EThe runner also picks up \u003Ccode\u003EIMigration\u003C/code\u003E classes from installed plugins, but they are gated by the same GrandNode database version, so they fire only when the store upgrades GrandNode itself. A plugin that changes its own data between plugin versions should handle that in its own code.\u003C/p\u003E\n\n\u003Ch2 id=\u0022testing\u0022\u003ETesting a migration locally\u003C/h2\u003E\n\u003Col\u003E\n\u003Cli\u003EBack up the development database.\u003C/li\u003E\n\u003Cli\u003EIn the \u003Ccode\u003EGrandNodeVersion\u003C/code\u003E collection, set \u003Ccode\u003EInstalledVersion\u003C/code\u003E and \u003Ccode\u003EDataBaseVersion\u003C/code\u003E back one minor version (for example from \u003Ccode\u003E2.4\u003C/code\u003E to \u003Ccode\u003E2.3\u003C/code\u003E).\u003C/li\u003E\n\u003Cli\u003ERestart the application. Migrations already recorded are skipped by \u003Ccode\u003EIdentity\u003C/code\u003E, so only new ones run; the log shows each one.\u003C/li\u003E\n\u003Cli\u003ECheck the result, then make sure the version stamp is back at the current version.\u003C/li\u003E\n\u003C/ol\u003E\n\n\u003Ch2 id=\u0022related\u0022\u003ERelated\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ca href=\u0022/developers-architecture-overview\u0022\u003EArchitecture overview\u003C/a\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/developers-ai-agent-kit\u0022\u003EThe AI Agent Kit\u003C/a\u003E \u2014 \u003Ccode\u003E.ai/templates/migration.md\u003C/code\u003E and \u003Ccode\u003E.ai/prompts/add-migration.md\u003C/code\u003E.\u003C/li\u003E\n\u003C/ul\u003E","ParentCategoryId":"6abdec6d83d2816248229f65","SeName":"developers-upgrade-migrations","MetaKeywords":null,"MetaDescription":"How GrandNode upgrades existing databases: IMigration classes, DbVersion, the version stamp, resource imports and the rules that keep a migration safe.","MetaTitle":null,"AllowComments":false,"Captcha":{"ReCaptchaChallengeField":null,"ReCaptchaResponseField":null,"ReCaptchaResponseValue":null,"ReCaptchaResponse":null},"RelatedArticles":[],"CategoryBreadcrumb":[{"Name":"For developers","Description":null,"IsCurrent":false,"Children":null,"Parent":null,"SeName":"docs-developers","Id":"6abdec6d83d2816248229f65","UserFields":[]}],"AddNewComment":{"CommentText":null,"DisplayCaptcha":false,"Id":null,"UserFields":[]},"Comments":[],"Id":"6abdec6d83d2816248229f73","UserFields":[]}