news 2026/9/14 8:11:35

EF Core SQL Server Abstractions 包详解:独立于 EF Core 的 HierarchyId 层次数据结构

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
EF Core SQL Server Abstractions 包详解:独立于 EF Core 的 HierarchyId 层次数据结构

EF Core SQL Server Abstractions 包详解:独立于 EF Core 的 HierarchyId 层次数据结构

【免费下载链接】efcoreEF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.项目地址: https://gitcode.com/GitHub_Trending/ef/efcore

Microsoft.EntityFrameworkCore.SqlServer.Abstractions是 EF Core SQL Server 提供商体系中的一个轻量抽象包,它只暴露极少数核心类型(目前即HierarchyId),专为那些不希望引入完整Microsoft.EntityFrameworkCore.SqlServer依赖、却需要在 POCO 实体或应用服务层表达 SQL Server 层次数据(hierarchyid)的应用而设计。读完本文,你将了解该包的定位与安装时机、HierarchyId类型的完整 API 能力与底层实现细节,以及它与 EF Core 主包、Microsoft.EntityFrameworkCore.SqlServer.HierarchyId提供程序包之间的协作关系。

包的定位:何时只需要 Abstractions 包

根据 src/EFCore.SqlServer.Abstractions/README.md 的说明,该包是一个“包含若干抽象(abstractions)的小包,这些抽象对于在不想依赖完整Microsoft.EntityFrameworkCore.SqlServer的场景中的应用可能有用”。原文给出的典型场景是:在 POCO 实体类型中使用HierarchyId,而这些实体类型本身与 EF Core 无关——例如一个纯领域模型库,只需要表达树形结构中的位置(深度与广度),却不想让整个库背上完整的 EF Core SQL Server 提供商依赖。

该包与主包的关系遵循“自动附带、显式安装”的原则:

  • 自动附带:README 指出该包会作为主包Microsoft.EntityFrameworkCore.SqlServer的依赖自动引入。因此,如果你已经引用了完整的 SQL Server 提供商,通常无需额外安装。
  • 显式安装:只有在刻意避免使用主包的场合(如上文的纯 POCO 场景、跨层共享的领域模型库)才单独安装 Abstractions 包。

包内实际内容:一个公共类型与两个依赖

从源码结构看,该包的公共 API 面非常收敛。EFCore.SqlServer.Abstractions.baseline.json 是 API 一致性基线文件,其中登记的全部公共类型只有Microsoft.EntityFrameworkCore.HierarchyId(实现IComparable<HierarchyId>),与 EFCore.SqlServer.Abstractions.csproj 中Description项的声明一致:

<Description>Provides abstractions that are used by models in conjunction with the SQL Server EF Core provider Commonly Used Types: Microsoft.EntityFrameworkCore.HierarchyId </Description>

csproj 中还揭示了该包仅有的两个 NuGet 依赖(EFCore.SqlServer.Abstractions.csproj):

  • Microsoft.SqlServer.Types:提供底层的SqlHierarchyId类型,HierarchyId的所有计算逻辑都委托给它;
  • System.Text.Json:用于支持内置的 JSON 序列化转换器。

HierarchyId 类型 API 详解

HierarchyId定义于 src/EFCore.SqlServer.Abstractions/HierarchyId.cs,其内部仅持有一个SqlHierarchyId _value字段(L18),是一个对SqlHierarchyId的托管封装。由于主包与 Abstractions 包共用Microsoft.EntityFrameworkCore根命名空间(csproj 中RootNamespace即为此),从代码视角看两者类型是无缝的——这也是该包能作为主包依赖自然融合的原因。

构造与解析

源码(HierarchyId.cs L23-L49)提供三个构造函数:

public HierarchyId() : this(SqlHierarchyId.GetRoot()) { } public HierarchyId(string value) : this(SqlHierarchyId.Parse(value)) { } public HierarchyId(SqlHierarchyId value) { if (value.IsNull) { throw new ArgumentNullException(nameof(value)); } _value = value; }
  • 无参构造等价于GetRoot(),即根节点/
  • 字符串构造接受节点的正则字符串表示(如/1/2/3/);
  • 传入SqlHierarchyId时若其值为Null会抛出ArgumentNullException——这是该封装与底层类型的一个关键差异点:HierarchyId实例不允许承载 SQL NULL,空值语义由外层可空引用类型HierarchyId?表达。

静态解析方法有两个重载(L55-L77):

public static HierarchyId? Parse(string? input) // input 为 null 时返回 null public static HierarchyId Parse(HierarchyId parentHierarchyId, params int[] parentId)

第二重载是SqlHierarchyId本身不具备的能力。从实现 GenerateHierarchyIdBasedOnParent 可以看到,它把父节点字符串与变长路径段用点号拼接后再解析:

var specificPath = new StringBuilder(parent.ToString()); specificPath.Append(string.Join(".", parentId)); specificPath.Append('/'); return Parse(specificPath.ToString());

因此HierarchyId.Parse("/1/", 2, 3)会得到/1/2.3/(多段路径),HierarchyId.Parse("/1/2/", 3)得到/1/2/3/(单段路径)。源码注释也明确指出了这一点:“It can be more than one element if want have path like: /1/2/3.1/, otherwise one element for have path like: /1/2/3/.”

树形操作的典型用法

以下示例演示常见树操作,均直接对应源码中的虚拟方法(virtual修饰意味着在内存提供程序等场景下可以派生替换实现):

var root = HierarchyId.GetRoot(); // "/" var dept = HierarchyId.Parse("/1/"); var emp = HierarchyId.Parse("/1/2/"); emp.GetLevel(); // 2:根节点为第 0 层 emp.GetAncestor(1); // /1/:上溯 1 层 dept.GetDescendant(); // 在 /1/ 下生成一个新子节点 dept.GetDescendant(child1, child2) // 在两个已有子节点之间插入新节点 emp.IsDescendantOf(dept); // true:判断是否位于某子树内 emp.GetReparentedValue(dept, "/3/");// 将节点移动到新的父节点下

关键语义(均见 HierarchyId.cs 的 XML 文档注释):

  • GetAncestor(n):返回上溯 n 层的祖先;若 n 超过GetLevel()则返回null;n 为负数抛出ArgumentOutOfRangeException
  • GetDescendant(child)/GetDescendant(child1, child2):分别用于追加新子节点(传入“最后一个已有子节点”以确保不冲突)和在两个已有子节点之间插入新节点。
  • GetReparentedValue(oldRoot, newRoot):把以oldRoot为终点的路径段重挂到newRoot之下,等价于树的“移动”操作。

比较运算符与类型转换

HierarchyId重载了全部六个比较运算符(L181-L227),实现方式统一为委托给底层SqlHierarchyId.CompareTo

public static bool operator ==(HierarchyId? hid1, HierarchyId? hid2) => ((SqlHierarchyId)hid1).CompareTo(hid2) == 0;

此外提供两个显式方向不同的转换运算符(L234-L243):

public static implicit operator SqlHierarchyId(HierarchyId? value) => value?._value ?? SqlHierarchyId.Null; // null 转回 SqlHierarchyId.Null public static explicit operator HierarchyId?(SqlHierarchyId value) => value.IsNull ? null : new HierarchyId(value);

即:HierarchyId?可以隐式降级为SqlHierarchyId(null 映射为SqlHierarchyId.Null),反向转换是显式的(SqlHierarchyId.Null映射回null)。这组不对称设计与构造函数拒绝 NULL 值的行为相互呼应。

内置 JSON 序列化:canonical 字符串

HierarchyId通过特性声明了自己的 JSON 转换器(HierarchyId.cs L15):

[JsonConverter(typeof(HierarchyIdJsonConverter))] public class HierarchyId : IComparable<HierarchyId>

转换器实现见 src/EFCore.SqlServer.Abstractions/Internal/HierarchyIdJsonConverter.cs:序列化时写出节点的正则字符串表示,反序列化时用HierarchyId.Parse还原,null 值直接透传:

public override HierarchyId? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => reader.GetString() is null ? null : HierarchyId.Parse(value); public override void Write(Utf8JsonWriter writer, HierarchyId? value, JsonSerializerOptions options) => writer.WriteStringValue(value?.ToString());

这意味着System.Text.Json序列化含HierarchyId属性的 POCO 时会得到形如"/1/2/"的 JSON 字符串,而不需要调用方再注册自定义转换器——对 ASP.NET Core 控制器、缓存层或跨服务 DTO 场景是一个实用的细节。

与主包和 HierarchyId 提供程序包的协作

理解 Abstractions 包在整个 EF Core SQL Server 体系中的位置,需要看到两个方向的关系:

1. 主包Microsoft.EntityFrameworkCore.SqlServer自动携带它。README 将其描述为主包依赖的一部分,因此常规使用 EF Core SQL Server 提供商的应用无需单独引入。

2. 专项功能包显式引用它。Microsoft.EntityFrameworkCore.SqlServer.HierarchyId提供程序包(EFCore.SqlServer.HierarchyId.csproj)以带包版本约束的项目引用方式依赖 Abstractions 包:

<ProjectReference Include="..\EFCore.SqlServer.Abstractions\EFCore.SqlServer.Abstractions.csproj" PackageVersion="[$(Version), $(NextMajorVersion))" />

该提供程序包负责把HierarchyId接入 EF Core 查询管道:类型映射(SqlServerHierarchyIdTypeMapping.cs)、LINQ 方法翻译(SqlServerHierarchyIdMethodTranslator.cs)等。它内部使用ValueConverter<HierarchyId?, SqlHierarchyId>完成模型类型与存储类型的互转,见 SqlServerHierarchyIdValueConverter.cs:

private static SqlHierarchyId ToProvider(HierarchyId? value) => (SqlHierarchyId)value; // 隐式转换 private static HierarchyId? FromProvider(SqlHierarchyId value) => (HierarchyId?)value; // 显式转换

这正是前文那组转换运算符在框架内部的落地位置。若要启用完整功能,按 src/EFCore.SqlServer.HierarchyId/README.md 的说明,在配置DbContext时调用HierarchyId()选项即可:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseSqlServer( "Server=localhost;Database=MyDatabase;Trusted_Connection=True;", b => b.HierarchyId());

正确性验证:基线测试与单元测试

仓库中的测试代码印证了该包 API 的完整性:

  • test/EFCore.SqlServer.HierarchyId.Tests/SqlServerAbstractionsApiConsistencyTest.cs 基于 API 一致性基线框架,对照 baseline.json 校验公共 API 与基线一致,防止公共成员被意外增删;
  • test/EFCore.SqlServer.HierarchyId.Tests/WrapperTests.cs 针对HierarchyId作为SqlHierarchyId封装层的行为做回归验证;
  • test/EFCore.SqlServer.HierarchyId.Tests/HierarchyIdJsonConverterTest.cs 覆盖 JSON 序列化/反序列化往返。

值得注意的是测试工程放在EFCore.SqlServer.HierarchyId.Tests下而非独立的 Abstractions 测试工程——从这一组织结构可以推断,Abstractions 包的测试与 HierarchyId 提供程序包的测试合并维护,因为它们共同服务于HierarchyId这一个核心类型。

适用前提与限制小结

要点说明依据
目标框架包编译目标为仓库统一的$(NetMinimum)版本(见 csprojTargetFrameworkEFCore.SqlServer.Abstractions.csproj
底层依赖依赖Microsoft.SqlServer.Types提供SqlHierarchyId,该包本身带有平台相关的本机组件,跨平台环境需按其官方说明安装EFCore.SqlServer.Abstractions.csproj
空值语义HierarchyId实例不可为 SQL NULL,用HierarchyId?可空引用表达空HierarchyId.cs L43-L46
可派生性实例方法均为virtual,可在特殊场景(如内存提供程序)下派生替换HierarchyId.cs
使用边界仅含HierarchyId抽象;查询翻译、类型映射等完整功能需另行引入 HierarchyId 提供程序包baseline.json

一句话总结该包的设计取舍:把“层次位置的数学”下沉到一个只依赖Microsoft.SqlServer.TypesSystem.Text.Json的最小包里,让领域模型可以零成本表达 hierarchyid,而把“如何存入数据库、如何翻译查询”留给主包与专项提供程序包——这正是 EF Core 提供商体系“抽象与实现分离”原则的一个典型样本。

【免费下载链接】efcoreEF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.项目地址: https://gitcode.com/GitHub_Trending/ef/efcore

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 8:11:01

3.17打卡的科学管理与习惯养成实践

1. 项目概述&#xff1a;3.17打卡的深层价值解析在数字化生活全面渗透的今天&#xff0c;"3.17打卡"这个看似简单的行为背后&#xff0c;隐藏着现代人自我管理体系的进化轨迹。作为一名连续7年坚持每日打卡的实践者&#xff0c;我发现打卡行为已经从最初的工作考勤工…

作者头像 李华
网站建设 2026/9/14 8:10:08

ESP32-S3 N16R8入手指南:8MB PSRAM与16MB Flash配置详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 8:09:30

Objective-C集合类型NSSet与NSMutableSet详解

1. Objective-C集合类型概述在Objective-C开发中&#xff0c;集合类型是处理对象组的基础工具。Foundation框架提供了三种主要集合类&#xff1a;NSArray、NSDictionary和NSSet。前两者广为人知&#xff0c;而NSSet及其可变版本NSMutableSet却常被开发者忽视。实际上&#xff0…

作者头像 李华
网站建设 2026/9/14 8:07:24

Cognition 利用 GPT-6 Astra 增强 Devin 代码测试能力,提升工程效率

Cognition 利用 GPT-6 Astra 增强 Devin 代码测试能力&#xff0c;提升工程效率在 AI 编程工具迅速演进的当下&#xff0c;Cognition 公司正通过 GPT-6 Astra 的引入&#xff0c;重新定义自主工程师 Agent 的测试验证能力与工程交付流程。一、GPT-6 Astra 的技术突破&#xff1…

作者头像 李华