diff --git a/src/Marten.AotSmoke/Marten.AotSmoke.csproj b/src/Marten.AotSmoke/Marten.AotSmoke.csproj new file mode 100644 index 0000000000..fcd3133345 --- /dev/null +++ b/src/Marten.AotSmoke/Marten.AotSmoke.csproj @@ -0,0 +1,45 @@ + + + + Exe + enable + enable + false + + + true + full + IL2026;IL2046;IL2055;IL2065;IL2067;IL2070;IL2072;IL2075;IL2090;IL2091;IL2111;IL3050;IL3051 + + + + + + + diff --git a/src/Marten.AotSmoke/Program.cs b/src/Marten.AotSmoke/Program.cs new file mode 100644 index 0000000000..9fc613dee3 --- /dev/null +++ b/src/Marten.AotSmoke/Program.cs @@ -0,0 +1,111 @@ +// AOT smoke test (marten#4349 / JasperFx/jasperfx#213). +// +// This program touches a representative cross-section of the AOT-clean +// Marten surface for a Static-TypeLoadMode consumer. The csproj sets +// IsAotCompatible=true, TrimMode=full, and promotes the AOT analyzer warning +// codes to errors, so any change that adds [RequiresDynamicCode] / +// [RequiresUnreferencedCode] to an API exercised here — or any change to +// this file that calls into a reflective Marten surface — fails the build +// in CI. +// +// What's exercised: +// - services.AddMarten(opts => { ... }) — the DI-facing entry point. +// - One document type with an Id property — exercises the closed-shape +// IDocumentStorage / IIdentification path (post-#4404, no Roslyn emit). +// - One event type + one SingleStreamProjection<,> registration — the +// primary projection-apply surface that Marten 9 routes through +// JasperFx.Events.SourceGenerator's [GeneratedEvolver] discovery +// (Options.Projections.DiscoverGeneratedEvolvers is called at startup +// in DocumentStore.cs). +// - StoreOptions.GeneratedCodeMode = TypeLoadMode.Static — pins the +// consumer profile that AOT-publishing apps would set. +// - Host.CreateApplicationBuilder + Build + DI resolution of +// IDocumentStore — the boot path consumers actually hit. +// +// What's deliberately NOT exercised: +// - Connecting / querying / persisting anything — this is a build-time +// analyzer test, not a runtime test. The connection string is a +// placeholder; the host is built but never actually opens a connection. +// - Compiled queries — Marten.SourceGenerator covers that surface and has +// its own analyzer-level tests in Marten.SourceGenerator.Tests. +// - JasperFx.RuntimeCompiler / services.AddRuntimeCompilation() — Marten 9 +// retired that path (#4454 / #4461). This smoke deliberately does not +// pull it back in. + +using JasperFx.CodeGeneration; +using JasperFx.Events; +using JasperFx.Events.Projections; +using Marten; +using Marten.Events.Aggregation; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +const string placeholderConnectionString = + "Host=localhost;Port=5432;Database=marten_aot_smoke;Username=postgres;password=postgres"; + +var builder = Host.CreateApplicationBuilder(); + +builder.Services.AddMarten(opts => +{ + opts.Connection(placeholderConnectionString); + + // Pin the consumer profile an AOT-publishing app would set. Marten 9 + // retired the runtime-codegen path entirely, so this setting is a + // documented no-op kept for source-compatibility — exercising it here + // pins that compile-time contract. + opts.GeneratedCodeMode = TypeLoadMode.Static; + + // Exercise the document-mapping surface — registers the closed-shape + // IDocumentStorage path (#4404). DocumentMapping infers the Id member + // and the Id type from SmokeDoc.Id reflectively at registration time, + // which is part of Marten's [DynamicallyAccessedMembers]-annotated + // contract; the AOT analyzer is happy when the type is fed as + // typeof(T) literal as it is here. + opts.Schema.For(); + + // Exercise the event-registration surface — RegisterEventType is the + // explicit alias-registration path the JasperFx.Events source generator + // also feeds at module-init time. Done explicitly here so the smoke + // test doesn't rely on assembly scanning. + opts.Events.AddEventType(); + + // Exercise the projection-registration surface — Projections.Add + // closes over the user's SingleStreamProjection<,> type, which the + // JasperFx.Events.SourceGenerator handles via [GeneratedEvolver] + // discovery (DocumentStore.cs calls DiscoverGeneratedEvolvers at + // startup post-#4454). Inline lifecycle keeps the smoke test free of + // the async-daemon surface, which has its own AOT story. + opts.Projections.Add(ProjectionLifecycle.Inline); +}); + +using var host = builder.Build(); +var store = host.Services.GetRequiredService(); + +if (store is null) +{ + Console.Error.WriteLine("DI resolved a null IDocumentStore — regression in AddMarten."); + return 1; +} + +Console.WriteLine("Marten AOT smoke OK — AddMarten + Schema.For + AddEventType + Projections.Add + DI resolve clean."); +return 0; + + +public sealed class SmokeDoc +{ + public Guid Id { get; set; } + public string Name { get; set; } = string.Empty; + public int Count { get; set; } +} + +public sealed record SmokeEvent(int Delta); + +public sealed class SmokeProjection : SingleStreamProjection +{ + public override SmokeDoc Evolve(SmokeDoc? snapshot, Guid id, IEvent e) + { + snapshot ??= new SmokeDoc { Id = id }; + if (e.Data is SmokeEvent inc) snapshot.Count += inc.Delta; + return snapshot; + } +} diff --git a/src/Marten.AspNetCore/Marten.AspNetCore.csproj b/src/Marten.AspNetCore/Marten.AspNetCore.csproj index 9163cac737..c324b9e2b5 100644 --- a/src/Marten.AspNetCore/Marten.AspNetCore.csproj +++ b/src/Marten.AspNetCore/Marten.AspNetCore.csproj @@ -11,6 +11,7 @@ true true enable + true diff --git a/src/Marten.EntityFrameworkCore/EfCoreDbContextFactory.cs b/src/Marten.EntityFrameworkCore/EfCoreDbContextFactory.cs index 6fe31ee706..ceed738730 100644 --- a/src/Marten.EntityFrameworkCore/EfCoreDbContextFactory.cs +++ b/src/Marten.EntityFrameworkCore/EfCoreDbContextFactory.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using Microsoft.EntityFrameworkCore; using Npgsql; @@ -13,7 +14,9 @@ namespace Marten.EntityFrameworkCore; /// internal static class EfCoreDbContextFactory { - public static (TDbContext DbContext, NpgsqlConnection InitialConnection) Create( + public static (TDbContext DbContext, NpgsqlConnection InitialConnection) Create< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + TDbContext>( this Storage.IMartenDatabase database, Action>? configure = null, string? schemaName = null) diff --git a/src/Marten.EntityFrameworkCore/EfCoreEventProjection.cs b/src/Marten.EntityFrameworkCore/EfCoreEventProjection.cs index 17995b0f20..c41e4cdcdf 100644 --- a/src/Marten.EntityFrameworkCore/EfCoreEventProjection.cs +++ b/src/Marten.EntityFrameworkCore/EfCoreEventProjection.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using JasperFx.Events; @@ -15,7 +16,9 @@ namespace Marten.EntityFrameworkCore; /// in the same database transaction. /// /// The EF Core DbContext type to use -public abstract class EfCoreEventProjection: IProjection +public abstract class EfCoreEventProjection< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + TDbContext>: IProjection where TDbContext : DbContext { /// diff --git a/src/Marten.EntityFrameworkCore/EfCoreMultiStreamProjection.cs b/src/Marten.EntityFrameworkCore/EfCoreMultiStreamProjection.cs index 8109641dc8..223dfcc607 100644 --- a/src/Marten.EntityFrameworkCore/EfCoreMultiStreamProjection.cs +++ b/src/Marten.EntityFrameworkCore/EfCoreMultiStreamProjection.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using JasperFx.Core; @@ -23,7 +24,23 @@ namespace Marten.EntityFrameworkCore; /// The aggregate document type persisted by EF Core /// The aggregate identity type /// The EF Core DbContext type to use -public abstract class EfCoreMultiStreamProjection +/// +/// AOT: TDoc carries the DAM flag set required by the wrapped +/// — propagating +/// the EF Core FindEntityType / Find<TEntity> trim contract +/// up to consumers. +/// +public abstract class EfCoreMultiStreamProjection< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors + | DynamicallyAccessedMemberTypes.NonPublicConstructors + | DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.Interfaces)] + TDoc, TId, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + TDbContext> : MultiStreamProjection, IValidatedProjection where TDoc : class where TId : notnull where TDbContext : DbContext { diff --git a/src/Marten.EntityFrameworkCore/EfCoreProjectionExtensions.cs b/src/Marten.EntityFrameworkCore/EfCoreProjectionExtensions.cs index 13a7ad15ff..abf33bf0d7 100644 --- a/src/Marten.EntityFrameworkCore/EfCoreProjectionExtensions.cs +++ b/src/Marten.EntityFrameworkCore/EfCoreProjectionExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using JasperFx.Events.Projections; using Marten.Events.Projections; @@ -20,7 +21,17 @@ public static class EfCoreProjectionExtensions /// Automatically sets up EF Core-based aggregate persistence and Weasel schema migration /// for all entity types in the DbContext. /// - public static void Add(this StoreOptions options, + public static void Add< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors + | DynamicallyAccessedMemberTypes.NonPublicConstructors + | DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.Interfaces)] + TDoc, TId, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + TDbContext>(this StoreOptions options, EfCoreSingleStreamProjection projection, ProjectionLifecycle lifecycle) where TDoc : class where TId : notnull where TDbContext : DbContext @@ -35,7 +46,17 @@ public static void Add(this StoreOptions options, /// Automatically sets up EF Core-based aggregate persistence and Weasel schema migration /// for all entity types in the DbContext. /// - public static void Add(this StoreOptions options, + public static void Add< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors + | DynamicallyAccessedMemberTypes.NonPublicConstructors + | DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.Interfaces)] + TDoc, TId, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + TDbContext>(this StoreOptions options, EfCoreMultiStreamProjection projection, ProjectionLifecycle lifecycle) where TDoc : class where TId : notnull where TDbContext : DbContext @@ -49,7 +70,17 @@ public static void Add(this StoreOptions options, /// Add an to a composite projection. /// Registers EF Core-based aggregate persistence and Weasel schema migration. /// - public static void Add(this CompositeProjection composite, + public static void Add< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors + | DynamicallyAccessedMemberTypes.NonPublicConstructors + | DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.Interfaces)] + TDoc, TId, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + TDbContext>(this CompositeProjection composite, StoreOptions options, EfCoreMultiStreamProjection projection, int stageNumber = 1) @@ -64,7 +95,17 @@ public static void Add(this CompositeProjection composite /// Add an to a composite projection. /// Registers EF Core-based aggregate persistence and Weasel schema migration. /// - public static void Add(this CompositeProjection composite, + public static void Add< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors + | DynamicallyAccessedMemberTypes.NonPublicConstructors + | DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.Interfaces)] + TDoc, TId, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + TDbContext>(this CompositeProjection composite, StoreOptions options, EfCoreSingleStreamProjection projection, int stageNumber = 1) @@ -80,7 +121,9 @@ public static void Add(this CompositeProjection composite /// Weasel migration pipeline. Tables defined in the DbContext's model will be created /// and migrated automatically alongside Marten's own schema objects. /// - public static void AddEntityTablesFromDbContext(this StoreOptions options, + public static void AddEntityTablesFromDbContext< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + TDbContext>(this StoreOptions options, Action>? configure = null) where TDbContext : DbContext { diff --git a/src/Marten.EntityFrameworkCore/EfCoreProjectionStorage.cs b/src/Marten.EntityFrameworkCore/EfCoreProjectionStorage.cs index 112ddf100d..91f98e2df6 100644 --- a/src/Marten.EntityFrameworkCore/EfCoreProjectionStorage.cs +++ b/src/Marten.EntityFrameworkCore/EfCoreProjectionStorage.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using JasperFx.Events; @@ -17,7 +18,22 @@ namespace Marten.EntityFrameworkCore; /// /// swaps to Marten's connection and calls SaveChangesAsync. /// -internal class EfCoreProjectionStorage : IProjectionStorage +/// +/// AOT: TDoc carries the full DAM flag set required by +/// DbContext.Find<TEntity> / FindAsync<TEntity> and +/// IModel.FindEntityType(Type) on the EF Core API surface. The trim +/// requirement propagates to consumers — callers that close TDoc with a +/// concrete entity type satisfy it implicitly. +/// +internal class EfCoreProjectionStorage< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors + | DynamicallyAccessedMemberTypes.NonPublicConstructors + | DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.Interfaces)] + TDoc, TId, TDbContext> : IProjectionStorage where TDoc : class where TId : notnull where TDbContext : DbContext { public TDbContext DbContext { get; } diff --git a/src/Marten.EntityFrameworkCore/EfCoreSingleStreamProjection.cs b/src/Marten.EntityFrameworkCore/EfCoreSingleStreamProjection.cs index f61dc29604..6ed38d574a 100644 --- a/src/Marten.EntityFrameworkCore/EfCoreSingleStreamProjection.cs +++ b/src/Marten.EntityFrameworkCore/EfCoreSingleStreamProjection.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using JasperFx.Core; @@ -23,7 +24,23 @@ namespace Marten.EntityFrameworkCore; /// The aggregate document type persisted by EF Core /// The stream identity type /// The EF Core DbContext type to use -public abstract class EfCoreSingleStreamProjection +/// +/// AOT: TDoc carries the DAM flag set required by the wrapped +/// — propagating +/// the EF Core FindEntityType / Find<TEntity> trim contract +/// up to consumers. +/// +public abstract class EfCoreSingleStreamProjection< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors + | DynamicallyAccessedMemberTypes.NonPublicConstructors + | DynamicallyAccessedMemberTypes.PublicFields + | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties + | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.Interfaces)] + TDoc, TId, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + TDbContext> : SingleStreamProjection, IValidatedProjection where TDoc : class where TId : notnull where TDbContext : DbContext { diff --git a/src/Marten.EntityFrameworkCore/Marten.EntityFrameworkCore.csproj b/src/Marten.EntityFrameworkCore/Marten.EntityFrameworkCore.csproj index 154bbec59d..e391082779 100644 --- a/src/Marten.EntityFrameworkCore/Marten.EntityFrameworkCore.csproj +++ b/src/Marten.EntityFrameworkCore/Marten.EntityFrameworkCore.csproj @@ -11,6 +11,7 @@ true true enable + true diff --git a/src/Marten.Newtonsoft/Marten.Newtonsoft.csproj b/src/Marten.Newtonsoft/Marten.Newtonsoft.csproj index 4b82d6e61d..b23361cd6b 100644 --- a/src/Marten.Newtonsoft/Marten.Newtonsoft.csproj +++ b/src/Marten.Newtonsoft/Marten.Newtonsoft.csproj @@ -11,6 +11,33 @@ true true enable + + diff --git a/src/Marten.NodaTime/Marten.NodaTime.csproj b/src/Marten.NodaTime/Marten.NodaTime.csproj index 1e78118d7b..30c97aa796 100644 --- a/src/Marten.NodaTime/Marten.NodaTime.csproj +++ b/src/Marten.NodaTime/Marten.NodaTime.csproj @@ -11,6 +11,7 @@ true enable Marten.NodaTimePlugin + true diff --git a/src/Marten.slnx b/src/Marten.slnx index bcf234bc13..fd57e4b841 100644 --- a/src/Marten.slnx +++ b/src/Marten.slnx @@ -67,6 +67,7 @@ +