Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions src/JasperFx.Events.SourceGenerator/AggregateAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ internal sealed class CandidateInfo
public List<ITypeSymbol> DiscoveredPublishedTypes { get; set; } = new();
// SelfAggregatingEvolve-specific
public EvolveMethodInfo? EvolveMethod { get; set; }
// Natural key discovery
public bool HasNaturalKey { get; set; }
}

internal static class AggregateAnalyzer
Expand Down Expand Up @@ -209,7 +211,8 @@ internal static class AggregateAnalyzer
AggregateType = classSymbol,
IdentityType = idType,
Methods = methods,
HasDefaultConstructor = HasParameterlessConstructor(classSymbol)
HasDefaultConstructor = HasParameterlessConstructor(classSymbol),
HasNaturalKey = HasNaturalKeyProperty(classSymbol)
};
}

Expand Down Expand Up @@ -242,7 +245,8 @@ internal static class AggregateAnalyzer
AggregateType = classSymbol,
IdentityType = idType,
HasDefaultConstructor = HasParameterlessConstructor(classSymbol),
EvolveMethod = evolveMethod
EvolveMethod = evolveMethod,
HasNaturalKey = HasNaturalKeyProperty(classSymbol)
};
}

Expand Down Expand Up @@ -1069,6 +1073,17 @@ private static bool HasParameterlessConstructor(INamedTypeSymbol type)
return constructors.Any(c => c.Parameters.Length == 0 && c.DeclaredAccessibility == Accessibility.Public);
}

/// <summary>
/// Checks if the type has a property marked with [NaturalKey] attribute
/// </summary>
private static bool HasNaturalKeyProperty(INamedTypeSymbol type)
{
return type.GetMembers()
.OfType<IPropertySymbol>()
.Any(p => p.GetAttributes()
.Any(a => a.AttributeClass?.Name is "NaturalKeyAttribute" or "NaturalKey"));
}

/// <summary>
/// Checks if the type has an explicitly declared parameterless constructor (not implicit).
/// Used to avoid generating a duplicate constructor in partial classes.
Expand Down
8 changes: 8 additions & 0 deletions src/JasperFx.Events.SourceGenerator/EvolverCodeEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<IsRoslynComponent>true</IsRoslynComponent>
<Description>Source generator for Fast Aggregate Projections for Marten and future JasperFx Event Stores</Description>
<PackageId>JasperFx.Events.SourceGenerator</PackageId>
<Version>1.3.0</Version>
<Version>1.4.0</Version>
<IncludeBuildOutput>false</IncludeBuildOutput>
<DevelopmentDependency>true</DevelopmentDependency>
</PropertyGroup>
Expand Down
18 changes: 18 additions & 0 deletions src/JasperFx.Events/Aggregation/NaturalKeyAggregateAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace JasperFx.Events.Aggregation;

/// <summary>
/// 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&lt;T&gt;(Inline).
/// </summary>
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public class NaturalKeyAggregateAttribute : Attribute
{
public NaturalKeyAggregateAttribute(Type aggregateType)
{
AggregateType = aggregateType;
}

public Type AggregateType { get; }
}
2 changes: 1 addition & 1 deletion src/JasperFx.Events/JasperFx.Events.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<Description>Foundational Event Store Abstractions and Projections for the Critter Stack</Description>
<PackageId>JasperFx.Events</PackageId>
<Version>1.24.1</Version>
<Version>1.24.2</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/JasperFx/JasperFx.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<Description>Foundational helpers and command line support used by JasperFx and the Critter Stack projects</Description>
<AssemblyName>JasperFx</AssemblyName>
<PackageId>JasperFx</PackageId>
<Version>1.21.4</Version>
<Version>1.21.5</Version>
</PropertyGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
Expand Down
Loading