- 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.
导读
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.
相关推荐
OrchardCore 数据迁移实战:DataMigration 版本链、SchemaBuilder 与内容修补全指南
OrchardCore 数据迁移实战:DataMigration 版本链、SchemaBuilder 与内容修补全指南 OrchardCore 作为基于 ASP
CMS后端Web框架GRDB.swift 数据库 Schema 修改实战指南:建表、改表与索引的类型安全方案
GRDB.swift 数据库 Schema 修改实战指南:建表、改表与索引的类型安全方案 GRDB.swift 为 SQLite 数据库提供了完整的、以 Swi
数据库ORMdrizzle-kit 0.30.3:SingleStore 表重建迁移与索引同名类型错误的修复解析
drizzle kit 0.30.3:SingleStore 表重建迁移与索引同名类型错误的修复解析 本篇技术指南以 drizzle kit 0.30.3 版本
后端数据库ORM
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考