Orchard Core Data Migrations - Prompt Templates
Create Data Migrations
You are an Orchard Core expert. Generate data migration code for Orchard Core modules.
Guidelines
- Data migrations inherit from
DataMigration. - Place migrations in a
Migrationsfolder in the feature project. - Name migration classes clearly and keep them
internal sealed. - Migrations define content types, parts, fields, and database indexes.
- Use
CreateAsync()for initial migration,UpdateFrom1Async(),UpdateFrom2Async()for incremental updates. Create,CreateAsync,UpdateFromX,UpdateFromXAsync,Uninstall, andUninstallAsynccan be markedstaticwhen they do not use injected services, inherited instance members, or other instance state.- Keep migration methods instance methods when they rely on constructor-injected services like
IContentDefinitionManageror inherited members likeSchemaBuilder. - Register migrations in
Startup.csusingservices.AddScoped<IDataMigration, Migrations>(). IContentDefinitionManageris used to define content types and parts.SchemaBuilderis used to create and alter YesSql index tables.- Use literal column names in
CreateMapIndexTableAsync()andAlterIndexTableAsync(); do not usenameof(...)for YesSql migration column names. MapIndextables already get aDocumentIdcolumn from YesSql. Never declare or alter that column manually in the migration.- If an index needs access to the YesSql document id, expose
public long DocumentId { get; set; }on theMapIndextype and let YesSql populate it. - Prefer keeping reusable content-part models in the corresponding
*.Coreproject when they are used outside the feature wiring layer.
Basic Migration with Content Type
using OrchardCore.ContentManagement.Metadata;
using OrchardCore.ContentManagement.Metadata.Settings;
using OrchardCore.Data.Migration;
public sealed class Migrations : DataMigration
{
private readonly IContentDefinitionManager _contentDefinitionManager;
public Migrations(IContentDefinitionManager contentDefinitionManager)
{
_contentDefinitionManager = contentDefinitionManager;
}
public async Task<int> CreateAsync()
{
await _contentDefinitionManager.AlterTypeDefinitionAsync("{{ContentType}}", type => type
.DisplayedAs("{{DisplayName}}")
.Creatable()
.Listable()
.Draftable()
.Versionable()
.WithPart("TitlePart", part => part.WithPosition("0"))
.WithPart("AutoroutePart", part => part
.WithPosition("1")
.WithSettings(new AutoroutePartSettings
{
AllowCustomPath = true,
Pattern = "{{ ContentItem | display_text | slugify }}"
})
)
.WithPart("HtmlBodyPart", part => part
.WithPosition("2")
.WithEditor("Wysiwyg")
)
.WithPart("{{ContentType}}Part", part => part.WithPosition("3"))
);
return 1;
}
}
Folder and Class Conventions
src/Modules/{{FeatureName}}/
Migrations/
{{FeatureName}}Migrations.cs
using OrchardCore.Data.Migration;
namespace CrestApps.Sports.Teams.Migrations;
internal sealed class TeamMigrations : DataMigration
{
public static Task<int> CreateAsync()
{
// migration steps
return Task.FromResult(1);
}
}
Use static for migration methods only when the body does not touch injected services, SchemaBuilder, or any other instance members.
Migration with Custom Content Part and Fields
public sealed class Migrations : DataMigration
{
private readonly IContentDefinitionManager _contentDefinitionManager;
public Migrations(IContentDefinitionManager contentDefinitionManager)
{
_contentDefinitionManager = contentDefinitionManager;
}
public async Task<int> CreateAsync()
{
// Define the custom part with fields
await _contentDefinitionManager.AlterPartDefinitionAsync("{{PartName}}", part => part
.WithField("{{FieldName}}", field => field
.OfType("TextField")
.WithDisplayName("{{FieldDisplayName}}")
.WithPosition("0")
.WithSettings(new TextFieldSettings
{
Required = true,
Hint = "{{FieldHint}}"
})
)
.WithField("Description", field => field
.OfType("TextField")
.WithDisplayName("Description")
.WithPosition("1")
.WithEditor("TextArea")
)
.WithField("Image", field => field
.OfType("MediaField")
.WithDisplayName("Image")
.WithPosition("2")
)
);
// Define the content type with the custom part
await _contentDefinitionManager.AlterTypeDefinitionAsync("{{ContentType}}", type => type
.DisplayedAs("{{DisplayName}}")
.Creatable()
.Listable()
.WithPart("TitlePart", part => part.WithPosition("0"))
.WithPart("{{PartName}}", part => part.WithPosition("1"))
);
return 1;
}
}
Migration with YesSql Index Table
using OrchardCore.Data.Migration;
using YesSql.Sql;
public sealed class Migrations : DataMigration
{
public async Task<int> CreateAsync()
{
await SchemaBuilder.CreateMapIndexTableAsync<{{IndexName}}>(table => table
.Column<string>("ContentItemId", col => col.WithLength(26))
.Column<string>("{{PropertyName}}", col => col.WithLength(256))
.Column<bool>("Published")
.Column<DateTime>("CreatedUtc")
);
await SchemaBuilder.AlterIndexTableAsync<{{IndexName}}>(table => table
.CreateIndex(
"IDX_{{IndexName}}_{{PropertyName}}",
"{{PropertyName}}",
"Published"
)
);
return 1;
}
}
Incremental Migration (UpdateFrom)
public sealed class Migrations : DataMigration
{
private readonly IContentDefinitionManager _contentDefinitionManager;
public Migrations(IContentDefinitionManager contentDefinitionManager)
{
_contentDefinitionManager = contentDefinitionManager;
}
public async Task<int> CreateAsync()
{
// Initial migration
await _contentDefinitionManager.AlterTypeDefinitionAsync("{{ContentType}}", type => type
.DisplayedAs("{{DisplayName}}")
.Creatable()
.Listable()
.WithPart("TitlePart")
);
return 1;
}
public async Task<int> UpdateFrom1Async()
{
// Add a new field in version 2
await _contentDefinitionManager.AlterPartDefinitionAsync("{{ContentType}}", part => part
.WithField("Category", field => field
.OfType("TextField")
.WithDisplayName("Category")
.WithPosition("2")
)
);
return 2;
}
public async Task<int> UpdateFrom2Async()
{
// Add an index table in version 3
await SchemaBuilder.CreateMapIndexTableAsync<{{ContentType}}Index>(table => table
.Column<string>("ContentItemId", col => col.WithLength(26))
.Column<string>("Category", col => col.WithLength(256))
.Column<bool>("Published")
);
return 3;
}
}
Registering Migrations and Indexes
using OrchardCore.Data.Migration;
using YesSql.Indexes;
public enum {{IndexName}}Status
{
One,
Two,
}
public sealed class {{IndexName}} : MapIndex
{
public long DocumentId { get; set; }
public string ContentItemId { get; set; }
public {{IndexName}}Status Status { get; set; }
}
await SchemaBuilder.CreateMapIndexTableAsync<{{IndexName}}>(table => table
.Column<string>("ContentItemId", col => col.WithLength(26))
.Column<{{IndexName}}Status>("Status")
);
public sealed class Startup : StartupBase
{
public override void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IDataMigration, Migrations>();
services.AddIndexProvider<{{IndexName}}Provider>();
}
}
Uninstall Migration
Handle cleanup when a feature is disabled:
public sealed class Migrations : DataMigration
{
public async Task UninstallAsync()
{
await SchemaBuilder.DropMapIndexTableAsync<{{IndexName}}>();
}
}