资讯动态

Orchard Core 数据存储原理:YesSql 文档数据库、索引与会话机制完全指南

发布时间:2026/10/7 2:21:58 来源:尧图企业网站定制
CMS后端Web框架【免费下载链接】OrchardCoreOrchard 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点击查看免费下载Orchard Core 并不使用 Entity Framework 等传统 ORM而是基于 YesSql——一个运行在关系数据库之上的 .NET 文档数据库接口——来持久化大部分应用数据内容项、用户、设置、工作流等。本文以官方文档 How YesSql works 为主线结合仓库源码深入讲解文档表结构、索引MapIndex / ReduceIndex的定义与迁移、ISession 会话工作单元以及数据库提供者与 YesSqlOptions 的配置方式帮助你理解内容类型加字段/部件无需数据库迁移这一 Orchard Core 核心特性的底层原理并掌握自定义索引与高效查询的完整实战路径。为什么 Orchard Core 不选 ORM 而是文档数据库传统 ORM 将对象模型映射到关系表对象结构一变就需要改表结构、写迁移。Orchard Core 反其道而行数据以JSON 文档形式整体落库对象结构的变化只影响 JSON 的形态表结构完全不用动。这一设计带来两个直接收益灵活给内容类型添加字段Field或部件Part时不需要任何数据库迁移新字段只是 JSON 里多了一个键值兼容底层仍然是标准的 SQL Server、SQLite、MySQL 或 PostgreSQL可以沿用既有的数据库运维与备份方案。而代价也显而易见无法用 SQL 高效查询 JSON 内部的内容。这正是索引Index存在的意义——把需要查询的属性投影到常规的关系表里。DocumentsJSON 文档如何落库当一个对象被保存时YesSql 会把它序列化为 JSON并作为一行写入文档表document table。默认集合使用Document表命名集合named collection则使用各自独立的文档表。每个租户还可以使用表前缀table prefix使多租户共用同一个数据库时表名互不冲突。文档表的行结构如下IdTypeContentVersion42OrchardCore.ContentManagement.ContentItem, OrchardCore.ContentManagement.Abstractions{ ContentItemId: 4tavbc..., DisplayText: My blog post, ... }3其中Id文档自增主键Type存储的 CLR 类型全名程序集限定名用于反序列化时还原对象类型Content对象序列化后的 JSON 内容Version文档版本号配合并发控制与乐观锁使用。ContentItem是 Orchard Core 中最重要的文档类型。它的 JSON 形态可以查阅 ContentItem.cs 中的定义而文档与类型信息之间的序列化约定由 DefaultContentJsonSerializer 负责。Indexes让 JSON 可被高效查询索引是一个普通的 C# 类其中只包含你希望用来查询的属性。YesSql 为索引数据维护常规 SQL 表Map 索引每一行索引记录对应一个文档映射Map可以为单个文档发射一行或多行记录Reduce 索引每一行索引记录聚合一组文档类似于 SQL 的GROUP BY用于计数、分组等场景。Map 索引行包含一个DocumentId列指向文档表Reduce 索引则使用单独的表来关联文档与聚合行。当文档被创建、更新或删除时其索引行会在同一个事务内重新计算。一条重要的规则是只有进入索引的属性才能被查询其余数据只存在于 JSON 文档中。因此在设计索引时应当把会出现在查询条件或排序里的字段全部纳入索引。MapIndex 与 ReduceIndex 的区别维度MapIndexReduceIndex行与文档关系一行映射一个文档可一对多发射一行聚合多个文档典型用途属性查询、过滤、排序计数、分组统计示例ContentItemIndex映射每个内容项的ContentType、Published、Owner等如按状态统计文档数量例如ContentItemIndex把每个内容项映射成一行索引记录其ContentType、Published、Latest、Owner、Author、DisplayText等可查询字段AliasPartIndex则只映射那些带有AliasPart的内容项及其别名。定义一个索引以商品Product为例先定义继承自MapIndex的索引类using YesSql.Indexes; public class ProductIndex : MapIndex { public string Sku { get; set; } public decimal Price { get; set; } }再通过IndexProviderT描述如何把某类文档映射为索引行public class ProductIndexProvider : IndexProviderContentItem { public override void Describe(DescribeContextContentItem context) { context.ForProductIndex() .When(contentItem contentItem.HasProductPart()) .Map(contentItem { var part contentItem.AsProductPart(); return new ProductIndex { Sku part.Sku, Price part.Price, }; }); } }这里When(...)是可选过滤条件只有满足条件本例为内容项包含ProductPart的文档才会被投影Map(...)返回null则表示不生成索引行可用于清理软删除记录的索引。注册索引提供者在模块的Startup.ConfigureServices()中注册services.AddIndexProviderProductIndexProvider();AddIndexProviderT定义在 IndexServiceCollectionExtensions.cs实现是将提供者注册为单例的IIndexProviderservices.TryAddEnumerable(ServiceDescriptor.SingletonIIndexProvider, TIndexProvider());同文件还提供了AddScopedIndexProviderT将实现IScopedIndexProvider的提供者注册为Scoped生命周期适用于需要依赖ISession等 scoped 服务的场景详见下文 AliasPartIndex 案例。创建索引表数据迁移索引表通过数据迁移创建。在 Migrations 数据迁移 中调用SchemaBuilder.CreateMapIndexTableAsyncT()public async Taskint CreateAsync() { await SchemaBuilder.CreateMapIndexTableAsyncProductIndex(table table .Columnstring(Sku, column column.WithLength(64)) .Columndecimal(Price) ); return 1; }CreateMapIndexTableAsync会自动为索引类生成一个包含Id与DocumentId列的常规 SQL 表表名默认取自索引类名WithLength(64)用于指定字符串列长度并通常配合唯一约束使用。真实案例ContentItemIndex 与 AliasPartIndex仓库中有两个典型的 MapIndex 实现可供参考1.ContentItemIndexRecords/ContentItemIndex.cs它映射每个内容项的ContentItemId、ContentItemVersionId、Published、Latest、ContentType、Owner、Author、DisplayText及三个时间戳。其ContentItemIndexProvider的Map方法还会对超长字符串做截断保护——类中定义了MaxContentTypeSize、MaxOwnerSize等常量均为 255映射时超过长度的值会被裁切避免超出列宽if (contentItemIndex.ContentType?.Length ContentItemIndex.MaxContentTypeSize) { contentItemIndex.ContentType contentItem.ContentType[..ContentItemIndex.MaxContentTypeSize]; }2.AliasPartIndexIndexes/AliasPartIndex.cs它映射每个带AliasPart的内容项的Alias、ContentItemId、Latest、Published并做了两处有意思的处理实现IScopedIndexProvider而非直接继承IndexProviderT并在UpdatedAsync内容处理钩子中做懒加载校验确保被移除出类型定义的部件不再被索引Map中对既未发布也未保留最新版本的软删除项返回null从而清除其索引记录if (!contentItem.Published !contentItem.Latest) { return null; }对应的建表迁移在 Migrations.csCreateAsync中不仅建表还通过AlterIndexTableAsync创建复合索引IDX_AliasPartIndex_DocumentId覆盖DocumentId、Alias、ContentItemId、Published、Latest并且UpdateFrom1Async等后续迁移用AddColumnbool演进表结构——这正说明索引表是按需演进的关系表而文档表始终无需迁移。The session查询与写入的工作单元YesSql 的文档读写都通过ISession进行。它是注册在依赖注入容器中的Scoped 工作单元SaveAsync与Delete只会在内存中缓冲真正的 SQL 命令要等调用SaveChangesAsync()时才在单个事务内批量执行。Orchard Core 会在租户 shell scope 结束时通常是请求结束时自动提交从该 scope 解析出的 session。通过索引查询文档控制器中注入ISession后可以用强类型查询直接过滤索引表public sealed class MyController : Controller { private readonly ISession _session; public MyController(ISession session) { _session session; } public async TaskIActionResult Cheap() { // Query documents through an index. var cheapProducts await _session .QueryContentItem, ProductIndex(index index.Price 10) .ListAsync(); return View(cheapProducts); } }该查询先在索引表上用常规 SQL 过滤出符合条件的DocumentId再回文档表加载对应 JSON 并反序列化为ContentItem。ISession还会缓存文档同一请求内重复加载同一文档返回的是同一个实例保证状态一致并减少数据库往返。何时用 ISession、何时用 IContentManager对于内容项ContentItem这类由 Orchard Core 管理的文档应优先使用更高层的IContentManager或IOrchardHelper的扩展方法如QueryContentItemsAsync——它们封装了加载、版本管理Published / Latest与内容处理管道handlers。只有在查询你自己定义的索引时才需要下沉到ISession直接操作。Configuration数据库提供者与 YesSql 选项按租户选择数据库提供者数据库提供者、连接字符串、表前缀和 Schema 都是在租户设置Setup阶段选定的。根据 Data 模块文档Orchard Core 内置四种提供者数据库Provider 值连接字符串表前缀与 SchemaSQLiteSqlite不使用不使用SQL ServerSqlConnection必填支持MySQLMySql必填支持PostgreSQLPostgres必填支持SQLite 是默认提供者每个租户的数据库文件存放在该租户的 shell 数据目录中文件名由DatabaseName设置控制默认OrchardCore.db。SQL Server、MySQL、PostgreSQL 需要先手动建库并给配置的账号授予建表/改表权限——Orchard Core 只负责校验连接并创建自己的表不会替你创建数据库。YesSqlOptionsYesSqlOptions定义在 YesSqlOptions.csOrchard Core 将配置节OrchardCore:YesSql绑定到它设置默认值说明CommandsPageSize500YesSql 命令页的最大命令数更大的集合会被拆分到多个页执行QueryGatingEnabledtrue合并并发环境中相同的查询工作使其只执行一次并共享结果EnableThreadSafetyChecksfalse开启后帮助诊断 YesSql 会话被并发使用的场景IsolationLevelReadCommitted传给数据库提供者的默认事务隔离级别配置示例可通过任意受支持的租户配置源下发如根应用的appsettings.json{ OrchardCore: { YesSql: { CommandsPageSize: 1000, QueryGatingEnabled: true, EnableThreadSafetyChecks: false, IsolationLevel: ReadCommitted } } }此外YesSqlOptions还暴露了IdGenerator、IdentifierAccessorFactory、VersionAccessorFactory、ContentSerializer等属性用于注入无法通过配置绑定创建的服务实现需要以代码方式配置。原始 SQL 访问与查询模块如需对同一数据库执行原始 SQL可以使用IDbConnectionAccessor定义于 IDbConnectionAccessor.cs其CreateConnection()返回租户对应的DbConnection详见 Data 模块文档也可以在管理界面中使用 SQL 查询模块 直接编写和执行 SQL。更进一步阅读 Migrations 数据迁移掌握SchemaBuilder建表/改表的完整 APICreateMapIndexTableAsync、AlterIndexTableAsync、AddColumn等阅读 Data 模块文档了解表命名约定、SQLite 连接池开关UseConnectionPooling以及租户数据库的完整配置方式阅读 SQL 查询模块了解如何在不写 C# 代码的情况下直接查询文档与索引在仓库源码中继续追踪ContentItemIndexProviderRecords/ContentItemIndex.cs展示了标准映射写法AliasPartIndexProviderIndexes/AliasPartIndex.cs展示了带清理逻辑与懒加载的 Scoped 提供者写法Migrations.cs 则完整演示了索引表的创建与后续演进。理解文档表存 JSON、索引表存查询列、会话按事务提交这三层模型是掌握 Orchard Core 数据层的钥匙它能解释为什么内容建模如此轻量也能指导你为自己的业务数据设计高效、可查询的索引。赞分享CMS后端Web框架【免费下载链接】OrchardCoreOrchard 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点击查看免费下载相关推荐Orchard Core 数据访问实战YesSql 文档存储、索引与会话以及 GraphQL 数据暴露Orchard Core 数据访问实战YesSql 文档存储、索引与会话以及 GraphQL 数据暴露 Orchard Core 的持久化数据层并不使用 ECMS后端Web框架Orchard Core Data 模块深度指南YesSql 数据库配置、表命名预设与原生 SQL 查询Orchard Core Data 模块深度指南YesSql 数据库配置、表命名预设与原生 SQL 查询 Orchard Core 的 Data 模块 OrCMS后端Web框架Orchard Core 内容定义存储Content Definition Store完全指南从文件存储到数据库的迁移实战Orchard Core 内容定义存储Content Definition Store完全指南从文件存储到数据库的迁移实战 Content DefinitCMS后端Web框架上一篇Windows系统优化新选择Windows Cleaner让你的电脑重获新生下一篇终极指南如何在Windows上免费安装ViGEmBus虚拟手柄驱动解决游戏兼容性问题创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑