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 @@
+