diff --git a/src/JasperFx.Events.SourceGenerator/AggregateAnalyzer.cs b/src/JasperFx.Events.SourceGenerator/AggregateAnalyzer.cs index 33fe113..e8b90b5 100644 --- a/src/JasperFx.Events.SourceGenerator/AggregateAnalyzer.cs +++ b/src/JasperFx.Events.SourceGenerator/AggregateAnalyzer.cs @@ -89,6 +89,8 @@ internal sealed class CandidateInfo public List DiscoveredPublishedTypes { get; set; } = new(); // SelfAggregatingEvolve-specific public EvolveMethodInfo? EvolveMethod { get; set; } + // Natural key discovery + public bool HasNaturalKey { get; set; } } internal static class AggregateAnalyzer @@ -209,7 +211,8 @@ internal static class AggregateAnalyzer AggregateType = classSymbol, IdentityType = idType, Methods = methods, - HasDefaultConstructor = HasParameterlessConstructor(classSymbol) + HasDefaultConstructor = HasParameterlessConstructor(classSymbol), + HasNaturalKey = HasNaturalKeyProperty(classSymbol) }; } @@ -242,7 +245,8 @@ internal static class AggregateAnalyzer AggregateType = classSymbol, IdentityType = idType, HasDefaultConstructor = HasParameterlessConstructor(classSymbol), - EvolveMethod = evolveMethod + EvolveMethod = evolveMethod, + HasNaturalKey = HasNaturalKeyProperty(classSymbol) }; } @@ -1069,6 +1073,17 @@ private static bool HasParameterlessConstructor(INamedTypeSymbol type) return constructors.Any(c => c.Parameters.Length == 0 && c.DeclaredAccessibility == Accessibility.Public); } + /// + /// Checks if the type has a property marked with [NaturalKey] attribute + /// + private static bool HasNaturalKeyProperty(INamedTypeSymbol type) + { + return type.GetMembers() + .OfType() + .Any(p => p.GetAttributes() + .Any(a => a.AttributeClass?.Name is "NaturalKeyAttribute" or "NaturalKey")); + } + /// /// Checks if the type has an explicitly declared parameterless constructor (not implicit). /// Used to avoid generating a duplicate constructor in partial classes. diff --git a/src/JasperFx.Events.SourceGenerator/EvolverCodeEmitter.cs b/src/JasperFx.Events.SourceGenerator/EvolverCodeEmitter.cs index 8edc988..74f1e46 100644 --- a/src/JasperFx.Events.SourceGenerator/EvolverCodeEmitter.cs +++ b/src/JasperFx.Events.SourceGenerator/EvolverCodeEmitter.cs @@ -406,6 +406,10 @@ public static string EmitSelfAggregatingEvolver(CandidateInfo info) // Assembly attributes must precede namespace declarations sb.AppendLine($"[assembly: global::JasperFx.Events.Aggregation.GeneratedEvolver(typeof({aggregateFullName}), typeof({GetFullyQualifiedEvolverName(info, evolverName)}))]"); + if (info.HasNaturalKey) + { + sb.AppendLine($"[assembly: global::JasperFx.Events.Aggregation.NaturalKeyAggregate(typeof({aggregateFullName}))]"); + } sb.AppendLine(); var ns = info.ClassSymbol.ContainingNamespace; @@ -455,6 +459,10 @@ public static string EmitSelfAggregatingEvolveEvolver(CandidateInfo info) // Assembly attribute for discovery sb.AppendLine($"[assembly: global::JasperFx.Events.Aggregation.GeneratedEvolver(typeof({aggregateFullName}), typeof({GetFullyQualifiedEvolverName(info, evolverName)}))]"); + if (info.HasNaturalKey) + { + sb.AppendLine($"[assembly: global::JasperFx.Events.Aggregation.NaturalKeyAggregate(typeof({aggregateFullName}))]"); + } sb.AppendLine(); var ns = info.ClassSymbol.ContainingNamespace; diff --git a/src/JasperFx.Events.SourceGenerator/JasperFx.Events.SourceGenerator.csproj b/src/JasperFx.Events.SourceGenerator/JasperFx.Events.SourceGenerator.csproj index f91ec5b..a029ca2 100644 --- a/src/JasperFx.Events.SourceGenerator/JasperFx.Events.SourceGenerator.csproj +++ b/src/JasperFx.Events.SourceGenerator/JasperFx.Events.SourceGenerator.csproj @@ -12,7 +12,7 @@ true Source generator for Fast Aggregate Projections for Marten and future JasperFx Event Stores JasperFx.Events.SourceGenerator - 1.3.0 + 1.4.0 false true diff --git a/src/JasperFx.Events/Aggregation/NaturalKeyAggregateAttribute.cs b/src/JasperFx.Events/Aggregation/NaturalKeyAggregateAttribute.cs new file mode 100644 index 0000000..3d7065e --- /dev/null +++ b/src/JasperFx.Events/Aggregation/NaturalKeyAggregateAttribute.cs @@ -0,0 +1,18 @@ +namespace JasperFx.Events.Aggregation; + +/// +/// Assembly-level attribute emitted by the source generator to indicate that a self-aggregating +/// type has a [NaturalKey] property. This allows event store implementations (e.g., Marten) +/// to auto-register the snapshot projection and natural key infrastructure at startup, +/// without requiring the user to explicitly call Projections.Snapshot<T>(Inline). +/// +[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] +public class NaturalKeyAggregateAttribute : Attribute +{ + public NaturalKeyAggregateAttribute(Type aggregateType) + { + AggregateType = aggregateType; + } + + public Type AggregateType { get; } +} diff --git a/src/JasperFx.Events/JasperFx.Events.csproj b/src/JasperFx.Events/JasperFx.Events.csproj index e7dfbc9..9be594c 100644 --- a/src/JasperFx.Events/JasperFx.Events.csproj +++ b/src/JasperFx.Events/JasperFx.Events.csproj @@ -3,7 +3,7 @@ Foundational Event Store Abstractions and Projections for the Critter Stack JasperFx.Events - 1.24.1 + 1.24.2 diff --git a/src/JasperFx/JasperFx.csproj b/src/JasperFx/JasperFx.csproj index a5cac1a..5bc3e90 100644 --- a/src/JasperFx/JasperFx.csproj +++ b/src/JasperFx/JasperFx.csproj @@ -3,7 +3,7 @@ Foundational helpers and command line support used by JasperFx and the Critter Stack projects JasperFx JasperFx - 1.21.4 + 1.21.5