源生成器无法识别依赖项目中的类,如何解决?
模块化单体+整洁架构下的源生成器自动生成删除端点问题
问题背景
我基于模块化单体结合整洁架构开发:Application项目依赖Domain项目,端点定义在Application层,实体类存放在Domain层。由于删除端点逻辑高度一致,我尝试通过源生成器自动生成删除端点,但遇到了识别问题:
- 实体的抽象基类
AuditableEntity定义在Common.Domain项目:
public abstract class AuditableEntity : IAuditableEntity { .... }
- 所有模块的Domain项目均依赖Common.Domain,实体类都继承自
Common.Domain.Entities.AuditableEntity(例如Inventory模块的Product实体,通过AggregateRoot<ProductId>间接继承):
public class Product : AggregateRoot<ProductId> // AggregateRoot继承自AuditableEntity { .... }
- 当前的
DeleteEndpointSourceGenerator在Domain项目运行时,能正常识别所有继承AuditableEntity的实体;但在Application项目运行时(这是符合需求的场景),却无法识别Domain层的实体,导致无法生成对应删除端点。
问题根源
当前源生成器的SyntaxReceiver仅收集**当前编译项目(Application项目)**内的ClassDeclarationSyntax语法节点,但实体类存在于Domain项目中,Application项目的编译上下文不包含Domain项目的语法树,因此CandidateClasses集合中不会有Domain层的实体类。
此外,虽然Application项目依赖Domain项目,但源生成器默认只能访问当前编译项目的语法节点,不会自动遍历引用程序集中的类型符号。
解决方案
1. 替换语法节点收集为符号遍历
放弃SyntaxReceiver的语法节点收集逻辑,改为在Execute阶段直接遍历编译上下文的所有类型符号(包括引用程序集里的):
[Generator] public class DeleteEndpointSourceGenerator : ISourceGenerator { private const string AuditableEntityFullName = "Common.Domain.Entities.AuditableEntity"; public void Initialize(GeneratorInitializationContext context) { // 不再需要SyntaxReceiver,移除注册逻辑 } public void Execute(GeneratorExecutionContext context) { var compilation = context.Compilation; var auditableEntitySymbol = compilation.GetTypeByMetadataName(AuditableEntityFullName); if (auditableEntitySymbol == null) { context.ReportDiagnostic(Diagnostic.Create(_auditableEntityNotFoundDescriptor, Location.None)); return; } // 遍历引用程序集的类型 foreach (var assemblySymbol in compilation.SourceModule.ReferencedAssemblySymbols) { TraverseNamespace(assemblySymbol.GlobalNamespace, auditableEntitySymbol, context); } // 遍历当前项目的类型 TraverseNamespace(compilation.SourceModule.GlobalNamespace, auditableEntitySymbol, context); } private void TraverseNamespace(INamespaceSymbol namespaceSymbol, INamedTypeSymbol baseTypeSymbol, GeneratorExecutionContext context) { foreach (var member in namespaceSymbol.GetMembers()) { if (member is INamespaceSymbol nestedNamespace) { TraverseNamespace(nestedNamespace, baseTypeSymbol, context); continue; } if (member is INamedTypeSymbol classSymbol && classSymbol.TypeKind == TypeKind.Class && !classSymbol.IsAbstract) { if (IsDerivedFrom(classSymbol, baseTypeSymbol)) { context.ReportDiagnostic(Diagnostic.Create(_classInheritsDescriptor, Location.None, classSymbol.Name, baseTypeSymbol.Name)); var namespaceName = classSymbol.ContainingNamespace.ToDisplayString(); var className = classSymbol.Name; var source = GenerateDeleteEndpointCode(namespaceName, className); context.AddSource($"{className}_DeleteEndpoint.g.cs", SourceText.From(source, Encoding.UTF8)); context.ReportDiagnostic(Diagnostic.Create(_endpointGeneratedDescriptor, Location.None, className)); } } } } // 保留原有的IsDerivedFrom、GenerateDeleteEndpointCode、Pluralize等方法 // 保留原有的DiagnosticDescriptor定义 }
2. 过滤目标模块实体(可选)
如果不需要为所有模块的实体生成端点,可以通过命名空间过滤目标模块,比如只处理Inventory.Domain下的实体:
// 在TraverseNamespace的类型判断中添加 if (classSymbol.ContainingNamespace.ToDisplayString().StartsWith("Inventory.Domain")) { // 执行生成逻辑 }
3. 修正源生成器项目配置
- 源生成器项目必须引用Common.Domain项目,确保能获取
AuditableEntity的类型符号 - 在Application项目中引用源生成器时,设置
PrivateAssets="all"避免依赖污染:
<ProjectReference Include="..\YourSourceGeneratorProject.csproj" PrivateAssets="all" />
4. 移除模块硬编码(可选)
生成代码中的[FromKeyedServices(nameof(Inventory))]是硬编码的模块名,可以通过实体命名空间动态提取模块名:
private static string GetModuleName(string namespaceName) { var parts = namespaceName.Split('.'); return parts.Length >= 1 ? parts[0] : "Default"; }
然后在生成代码时替换:
[FromKeyedServices(nameof({GetModuleName(namespaceName)}))] IUnitOfWork unitOfWork,
内容的提问来源于stack exchange,提问作者Baran
相关产品推荐
相关产品推荐

