
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/efcoreMicrosoft.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实现IComparableHierarchyId与 EFCore.SqlServer.Abstractions.csproj 中Description项的声明一致DescriptionProvides abstractions that are used by models in conjunction with the SQL Server EF Core provider Commonly Used Types: Microsoft.EntityFrameworkCore.HierarchyId /Descriptioncsproj 中还揭示了该包仅有的两个 NuGet 依赖EFCore.SqlServer.Abstractions.csprojMicrosoft.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-L77public 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()则返回nulln 为负数抛出ArgumentOutOfRangeException。GetDescendant(child)/GetDescendant(child1, child2)分别用于追加新子节点传入“最后一个已有子节点”以确保不冲突和在两个已有子节点之间插入新节点。GetReparentedValue(oldRoot, newRoot)把以oldRoot为终点的路径段重挂到newRoot之下等价于树的“移动”操作。比较运算符与类型转换HierarchyId重载了全部六个比较运算符L181-L227实现方式统一为委托给底层SqlHierarchyId.CompareTopublic static bool operator (HierarchyId? hid1, HierarchyId? hid2) ((SqlHierarchyId)hid1).CompareTo(hid2) 0;此外提供两个显式方向不同的转换运算符L234-L243public 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?可以隐式降级为SqlHierarchyIdnull 映射为SqlHierarchyId.Null反向转换是显式的SqlHierarchyId.Null映射回null。这组不对称设计与构造函数拒绝 NULL 值的行为相互呼应。内置 JSON 序列化canonical 字符串HierarchyId通过特性声明了自己的 JSON 转换器HierarchyId.cs L15[JsonConverter(typeof(HierarchyIdJsonConverter))] public class HierarchyId : IComparableHierarchyId转换器实现见 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等。它内部使用ValueConverterHierarchyId?, SqlHierarchyId完成模型类型与存储类型的互转见 SqlServerHierarchyIdValueConverter.csprivate 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( Serverlocalhost;DatabaseMyDatabase;Trusted_ConnectionTrue;, 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.Types和System.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),仅供参考