☰
SchemaBuilder 迁移指南:在 OrchardCore 中构建与修改 YesSql 索引表
2026/9/27 1:37:56 网站建设 项目流程
  • CMS
  • 后端
  • Web框架

【免费下载链接】OrchardCore

Orchard Core is an open-source modular and multi-tenant application framework built with ASP.NET Core, and a content management system (CMS) built on top of that framework.

项目地址:https://gitcode.com/gh_mirrors/or/OrchardCore
点击查看免费下载

导读

SchemaBuilder是 OrchardCore 数据迁移体系中的核心工具,用于创建和修改支撑 YesSql map/reduce 索引的 SQL 索引表。本指南以 OrchardCore 数据迁移技能文档为骨架,结合仓库源码(DataMigration.cs、ContentItemIndex.cs 等)深度解析其用法、列定义规则、索引管理及不同数据库提供商的兼容性陷阱。读完本文,你将能够独立编写健壮、跨数据库可用的索引表迁移代码。

SchemaBuilder 从何而来:DataMigration 基类

SchemaBuilder由DataMigration基类提供(通过SchemaBuilder属性暴露),永远不要手动注入它。它专门用于构建和修改支撑 YesSql map/reduce 索引的 SQL索引表。

源码佐证:DataMigration.cs 中定义:

public abstract class DataMigration : IDataMigration { /// <inheritdocs /> public ISchemaBuilder SchemaBuilder { get; set; } }

该属性来自IDataMigration接口(见 IDataMigration.cs),由迁移管理器负责赋值。

一个典型的迁移类(以 OrchardCore.ContentManagement/Records/Migrations.cs 为参考):

public sealed class Migrations : DataMigration { public async Task<int> CreateAsync() { await SchemaBuilder.CreateMapIndexTableAsync<ContentItemIndex>(table => table .Column<string>("ContentItemId", c => c.WithLength(26)) .Column<string>("ContentItemVersionId", c => c.WithLength(26)) .Column<bool>("Latest") .Column<bool>("Published") .Column<string>("ContentType", column => column.WithLength(ContentItemIndex.MaxContentTypeSize)) .Column<DateTime>("ModifiedUtc", column => column.Nullable()) // ... ); // ... return 1; } }

关于同步/异步方法:所有SchemaBuilder方法都有对应的异步形式(...Async),新代码中应优先使用异步形式。迁移的入口方法也遵循同样的约定——DataMigrationExtensions(见 DataMigrationExtensions.cs)会优先查找同步Create()/UpdateFromX(),找不到时才回退到CreateAsync()/UpdateFromXAsync()。

创建 map 索引表

CreateMapIndexTableAsync<TIndex>用于为 map 索引创建对应的索引表,泛型参数指向模块中定义的索引 POCO。索引 POCO(MemberIndex : MapIndex)和它的IndexProvider在模块中单独定义,迁移只负责创建表,不涉及映射逻辑。

await SchemaBuilder.CreateMapIndexTableAsync<MemberIndex>(table => table .Column<string>("ContentItemId", column => column.WithLength(26)) .Column<string>(nameof(MemberIndex.SocialSecurityNumber), column => column.WithLength(11)) .Column<string>(nameof(MemberIndex.Name), column => column.WithLength(26)) .Column<bool>("Published", column => column.Nullable()) .Column<decimal>("Amount", column => column.Nullable()) .Column<string>("BigText", column => column.Nullable().Unlimited()) );

要点说明:

  • 列名可以直接写字符串字面量,也可以用nameof(索引POCO属性)保持与索引定义同步,避免改名时遗漏;
  • 布尔列Latest/Published是 YesSql 索引的常见约定列,用于区分草稿与已发布版本;
  • CreateReduceIndexTableAsync<TIndex>用于 reduce 索引,用法一致。仓库中的 OpenIdMigrations.cs 就同时展示了 map 与 reduce 索引表的创建,以及collection:命名参数(用于将索引表放入指定的集合/文档容器)。

列定义规则

需求代码
带长度的字符串.Column<string>("Name", c => c.WithLength(26))
无长度限制的文本.Column<string>("Body", c => c.Nullable().Unlimited())
可空值类型.Column<bool>("Latest", c => c.Nullable())
数值类型.Column<decimal>(...),.Column<int>(...),.Column<DateTime>(...),.Column<TimeSpan>(...)

务必为所有字符串列显式指定.WithLength(n)或.Unlimited()。未指定长度的字符串列在不同数据库提供商之间的默认长度不一致,会导致某些数据库上的迁移失败。此外,需要唯一约束的列可以追加.Unique()(见 OpenIdMigrations.cs 中ClientId列的定义)。

OrchardCore 的标准长度约定:

  • ContentItemId/ContentItemVersionId= 26(内容项标识符的固定长度);
  • 内容类型、部件、字段名称等使用ContentItemIndex.Max*Size常量,见 ContentItemIndex.cs:
public const int MaxContentTypeSize = 255; public const int MaxContentPartSize = 255; public const int MaxContentFieldSize = 255; public const int MaxOwnerSize = 255; public const int MaxAuthorSize = 255; public const int MaxDisplayTextSize = 255;

实践中推荐直接引用这些常量,例如.Column<string>("ContentType", column => column.WithLength(ContentItemIndex.MaxContentTypeSize)),避免魔法数字散落各处。

修改表结构:添加 / 删除列与索引

AlterIndexTableAsync<TIndex>是升级步骤的核心工具,支持对已有索引表进行增量修改。

升级步骤中新增列:

await SchemaBuilder.AlterIndexTableAsync<LinkFieldIndex>(table => table .AddColumn<string>("BigUrl", column => column.Nullable().Unlimited()));

为加速查询创建 SQL 索引:

await SchemaBuilder.AlterIndexTableAsync<LinkFieldIndex>(table => table .CreateIndex("IDX_LinkFieldIndex_DocumentId", "DocumentId", "ContentItemId", "ContentItemVersionId", "Published", "Latest"));

真实仓库中,ContentManagement 的 Migrations.cs 在创建ContentItemIndex表后立即创建了IDX_ContentItemIndex_DocumentId、IDX_ContentItemIndex_DocumentId_ContentType等复合索引,这是 OrchardCore 的标准做法——索引表创建后马上为高频查询路径建立复合索引。

删除索引:

await SchemaBuilder.AlterIndexTableAsync<TimeFieldIndex>(table => table .DropIndex("IDX_TimeFieldIndex_DocumentId_Time"));

数据库提供商兼容性陷阱

SQLite 无法删除列

SQLite 对DROP COLUMN支持有限(旧版本不支持),删除列及其后的列重建操作必须包裹在try/catch中:

try { await SchemaBuilder.AlterIndexTableAsync<TimeFieldIndex>(table => table .DropColumn("Time")); await SchemaBuilder.AlterIndexTableAsync<TimeFieldIndex>(table => table .AddColumn<TimeSpan>("Time", column => column.Nullable())); } catch { _logger.LogWarning("Failed to alter 'Time' column. This is not an error when using SqLite"); }

这是刻意为之的设计:在 SQLite 下失败属于预期行为,捕获后仅记录警告,不中断迁移流程。

删除可能不存在的索引

某些升级场景中,索引可能已在先前步骤或不同版本中被删除。对“幂等性”不确定的索引删除操作同样建议用try/catch包裹:

try { await SchemaBuilder.AlterIndexTableAsync<TimeFieldIndex>(table => table .DropIndex("IDX_TimeFieldIndex_Time")); } catch { _logger.LogWarning("Failed to drop an index that does not exist 'IDX_TimeFieldIndex_Time'"); }

MySQL 索引键长度限制

MySQL 中复合索引键上限为768 字符 / 3072 字节。当索引中包含长字符串列时,必须在索引定义中使用前缀长度限制:

await SchemaBuilder.AlterIndexTableAsync<TextFieldIndex>(table => table .CreateIndex("IDX_TextFieldIndex_DocumentId_Text", "DocumentId", "Text(764)", // prefix length keeps the key under the limit "Published", "Latest"));

OrchardCore 会在每个此类索引上方以注释形式记录其长度算术,例如:// DocumentId (2) + Text (764) + Published and Latest (1) = 767 (< 768).

注意:Text(764)这种“列名+前缀长度”的写法仅在CreateIndex的列参数中生效,用于指示数据库只对Text列的前 764 个字符建索引;.Column<string>定义列本身时不能用这种写法。

删除整张索引表

await SchemaBuilder.DropMapIndexTableAsync<MemberIndex>(); // or await SchemaBuilder.DropTableAsync("MyCustomTable");

两种方式的区别:

  • DropMapIndexTableAsync<TIndex>()按索引 POCO 类型定位并删除对应索引表,类型安全、推荐优先使用;
  • DropTableAsync("表名")直接按表名删除,适用于删除自定义表(非索引表)或泛型方式难以表达的场景。

典型使用场景是模块的UninstallAsync()(卸载步骤),在功能卸载时清理数据库残留。可参考 DataMigration.cs 中关于Uninstall()/UninstallAsync()方法的文档注释约定。

迁移方法命名约定速查

迁移类的每个方法名承载了版本语义,由 DataMigrationManager 通过UpdateFrom前缀与Async后缀的反射机制识别:

方法签名语义
int Create()/Task<int> CreateAsync()初始迁移,返回 1
int UpdateFrom1()/Task<int> UpdateFrom1Async()1 → 2 的升级,返回 2
void Uninstall()/Task UninstallAsync()卸载清理(如删除索引表)

方法的返回值代表迁移后的版本号,OrchardCore 将其记录在DataMigrationRecord(见 Records/DataMigrationRecord.cs)中,作为后续升级判断的依据。

小结

  • SchemaBuilder是DataMigration基类内置属性,负责 YesSql 索引表的建表、改表与删表,新代码优先使用...Async形式;
  • 字符串列必须显式声明.WithLength(n)或.Unlimited();标准标识符用 26,类型/字段名用ContentItemIndex.Max*Size常量(255);
  • 升级用AlterIndexTableAsync增量添加列、创建/删除复合索引;
  • 记住三大兼容性陷阱:SQLite 删列需try/catch、删除不存在索引需容错、MySQL 复合索引键需前缀限长(如Text(764));
  • 卸载时用DropMapIndexTableAsync或DropTableAsync清理索引表。

将以上规则与真实迁移(如 ContentManagement Migrations、OpenIdMigrations)对照阅读,即可快速掌握 OrchardCore 数据迁移的完整实践。

  • CMS
  • 后端
  • Web框架

【免费下载链接】OrchardCore

Orchard Core is an open-source modular and multi-tenant application framework built with ASP.NET Core, and a content management system (CMS) built on top of that framework.

项目地址:https://gitcode.com/gh_mirrors/or/OrchardCore
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询