XiHan Framework 处于预览阶段

查看获取方式
跳转到内容

XiHan.Framework.Persistence.Generators ​

持久化的 Roslyn 源生成器:从本程序集实现 IEntity<TKey> 的实体类生成仓储登记、EF Core 实体登记与软删除 / 租户过滤规约。以分析器形式引用,不进入运行时依赖。

项内容
包 IDXiHan.Framework.Persistence.Generators
环层工具链
能力—
特性入口—
配置节—
依赖Microsoft.CodeAnalysis.CSharp(仅编译期);生成的代码引用 XiHan.Framework.Domain 与 XiHan.Framework.Persistence.Abstractions
AOT不兼容(IsAotCompatible 为 false:生成器在编译期运行,以 netstandard2.0 发布);生成的代码不含反射,可用于 AOT 应用
稳定性Preview

概述 ​

实体多了以后,逐个写 AddRepository<TEntity, TKey>() 与 modelBuilder.Entity<T>() 容易漏。生成器扫描当前项目里实现 IEntity<TKey>(直接实现或经 IAggregateRoot<TKey>)的非抽象、非静态的顶层类(含 record 与 record class),生成一个文件 GeneratedEntities.g.cs:

生成物位置内容
GeneratedEntities.AddGeneratedRepositories(this IServiceCollection)项目 RootNamespace(没有时用程序集名)为每个实体调用一次 AddRepository<TEntity, TKey>()
GeneratedEntities.ApplyGeneratedEntities(this ModelBuilder)同上;项目引用了 EF Core 时才生成为每个实体 modelBuilder.Entity<T>();项目还引用了 Persistence.EntityFrameworkCore 时签名多一个 DatabaseFacade database,接着调用 ApplyPersistenceConventions(database)(按 Provider 写软删除实体部分唯一索引的条件)
<实体>Filters 静态类实体所在命名空间,可访问性与实体相同;实体实现 ISoftDeletable 或 IMultiTenant 时才生成NotDeleted(属性)、ForTenant(tenantId)、两者都实现时还有 ActiveFor(tenantId),都是 Specification<T>

生成的登记代码与手写的 AddRepository 等价,仍是逐个闭合泛型的显式注册,Native AOT 可用。

何时使用 ​

实体超过几个、或分散在多个功能区时用它代替手写登记。只有一两个实体的项目手写 AddRepository 即可,效果相同。

安装与启用 ​

以分析器引用,不把生成器程序集带进运行时:

bash
dotnet add package XiHan.Framework.Persistence.Generators --prerelease

当前是预览版,安装要带 --prerelease;从源码或本地包源引用见快速上手。在仓库里以项目引用时写成:

xml
<ProjectReference Include="../../src/XiHan.Framework.Persistence.Generators/XiHan.Framework.Persistence.Generators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />

然后在特性的 Configure 里调用生成的登记方法(SimpleApp 的 OrdersFeature):

csharp
public void Configure(FeatureContext context)
{
    // 登记代码由 Persistence.Generators 从实体类生成(Order : IAggregateRoot<Guid>)
    context.Services.AddGeneratedRepositories();
}

EF Core 应用在 OnModelCreating 里调用生成的模型登记:

csharp
using Microsoft.EntityFrameworkCore;

namespace SimpleApp;

public sealed class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
{
    // 登记本程序集全部实体,并应用持久化约定
    protected override void OnModelCreating(ModelBuilder modelBuilder) => modelBuilder.ApplyGeneratedEntities(Database);
}

主要类型 ​

本包的公开类型只有生成器本身 EntityGenerator,应用不直接使用它。应用使用的是生成出来的代码:

生成的成员说明
GeneratedEntities.AddGeneratedRepositories(services)登记本程序集全部实体的仓储,返回 services
GeneratedEntities.ApplyGeneratedEntities(modelBuilder[, database])把本程序集全部实体登记进 EF Core 模型,返回 modelBuilder;引用了 Persistence.EntityFrameworkCore 时带 database 并接着应用持久化约定
<实体>Filters.NotDeleted未软删除:!entity.IsDeleted
<实体>Filters.ForTenant(tenantId)属于指定租户:entity.TenantId == tenantId
<实体>Filters.ActiveFor(tenantId)NotDeleted.And(ForTenant(tenantId))

使用示例 ​

仓储查询已经自动按当前租户与软删除过滤,生成的规约用于内存判定,或在 Provider 原生查询里复用同一条件:

csharp
using Microsoft.EntityFrameworkCore;
using SimpleApp.Orders;

// 内存里筛一批已经取出的订单
var active = loaded.Where(OrderFilters.ActiveFor("acme").IsSatisfiedBy).ToList();

// 直接查 DbContext 时没有仓储的过滤,用同一条规约补上
var count = await db.Set<Order>().Where(OrderFilters.ActiveFor("acme").Criteria).CountAsync(cancellationToken);

loaded 是一组 Order,db 是应用的 AppDbContext。

行为与限制 ​

  • 扫描当前项目里的 class 与 record(record class)声明:抽象的、静态的不生成;分部类型的多个声明只登记一次。值类型(struct、record struct)实现 IEntity<TKey> 时报 XHP004 错误、编译失败:仓储要求实体是类。
  • 实体按完整类型名排序后输出,生成结果稳定。
  • 没有实体时不生成任何文件,引用生成器的项目也就没有 GeneratedEntities。
  • RootNamespace 中的非法字符会被整理成合法的命名空间名。
诊断级别含义处理
XHP001Error实体类经多个 IEntity<TKey> 声明了不同的键类型只实现一个 IEntity<TKey>;该实体不生成
XHP002Error实体类是嵌套类型,或不是 public / internal改为顶层的 public 或 internal 类;该实体不生成
XHP003Warning要生成的 <实体>Filters 与已有类型同名该实体的过滤规约不生成,仓储登记照常生成
XHP004Error值类型(struct、record struct)实现了 IEntity<TKey>改成 class 或 record class;该类型不生成登记

相关 ​

Released under The MIT License