Spun out of #3353 (see "Separately..." in that issue's report) so it doesn't get lost.
Summary
GenerationRulesExtensions.TryFindPersistenceFrameProvider resolves the provider for a storage action / [Entity] load by filtering PersistenceProviders() with CanPersist and taking candidates.First() (src/Wolverine/Persistence/Sagas/GenerationRulesExtensions.cs:61-78). Two facts make that order-dependent in mixed Marten + EF Core applications:
MartenPersistenceFrameProvider.CanPersist returns true for every type (src/Persistence/Wolverine.Marten/Persistence/Sagas/MartenPersistenceFrameProvider.cs:24-28) — Marten can genuinely persist any document, so it claims anything it is asked about.
- Provider order is set by
InsertFirstPersistenceStrategy from each integration's IWolverineExtension, and extensions are applied in DI registration order — so whichever integration the application happens to register last sits at index 0 and claims every entity.
Consequence: in a Marten + EF Core application, a Storage.Insert(entity) / IStorageAction<T> / [Entity] over an EF-mapped entity resolves to Marten (generating IDocumentSession code against a type the app persists with EF Core) or to EF Core purely depending on whether AddMarten().IntegrateWithWolverine() appears before or after AddDbContextWithWolverineIntegration<T>() in Program.cs. The #3353 report hit this directly: its first reproducer needed an extension that manually reordered PersistenceProviders() because Marten silently claimed the EF-mapped entity.
The chain-level twin, GetPersistenceProviders(chain, container) → FirstOrDefault(x => x.CanApply(chain, container)), has the same ordering sensitivity.
Desired behavior
EF Core must be evaluated before Marten whenever both are active, regardless of user registration order. EF Core's CanPersist is selective — it answers true only for types actually mapped in a registered DbContext model — while Marten's is a catch-all. With EF Core first, EF-mapped entities deterministically resolve to their DbContext and everything else still falls through to Marten; nothing that only Marten can persist is ever taken away from it.
Implementation options, in rough order of preference:
- Give the provider list a deterministic ordering rule: selective providers (EF Core, and any provider whose
CanPersist inspects the type) ahead of catch-all providers (Marten, Polecat?), applied when the list is consulted rather than trusting insertion order.
- Or have the Marten integration register itself with lowest precedence (append rather than
InsertFirst) / have the EF Core integration force itself ahead of Marten at bootstrap.
Test automation
Whatever the mechanism, this needs registration-order permutation tests: Marten-then-EFCore and EFCore-then-Marten hosts, each asserting (a) a storage action / [Entity] over a DbContext-mapped entity generates DbContext code, and (b) a non-EF-mapped document still resolves to Marten. The Wolverine.Http.Tests.EfCoreOnly + pinned-ApplicationAssembly pattern from #3357 can host the HTTP variants without disturbing the main test assembly.
References
🤖 Generated with Claude Code
Spun out of #3353 (see "Separately..." in that issue's report) so it doesn't get lost.
Summary
GenerationRulesExtensions.TryFindPersistenceFrameProviderresolves the provider for a storage action /[Entity]load by filteringPersistenceProviders()withCanPersistand takingcandidates.First()(src/Wolverine/Persistence/Sagas/GenerationRulesExtensions.cs:61-78). Two facts make that order-dependent in mixed Marten + EF Core applications:MartenPersistenceFrameProvider.CanPersistreturnstruefor every type (src/Persistence/Wolverine.Marten/Persistence/Sagas/MartenPersistenceFrameProvider.cs:24-28) — Marten can genuinely persist any document, so it claims anything it is asked about.InsertFirstPersistenceStrategyfrom each integration'sIWolverineExtension, and extensions are applied in DI registration order — so whichever integration the application happens to register last sits at index 0 and claims every entity.Consequence: in a Marten + EF Core application, a
Storage.Insert(entity)/IStorageAction<T>/[Entity]over an EF-mapped entity resolves to Marten (generatingIDocumentSessioncode against a type the app persists with EF Core) or to EF Core purely depending on whetherAddMarten().IntegrateWithWolverine()appears before or afterAddDbContextWithWolverineIntegration<T>()inProgram.cs. The #3353 report hit this directly: its first reproducer needed an extension that manually reorderedPersistenceProviders()because Marten silently claimed the EF-mapped entity.The chain-level twin,
GetPersistenceProviders(chain, container)→FirstOrDefault(x => x.CanApply(chain, container)), has the same ordering sensitivity.Desired behavior
EF Core must be evaluated before Marten whenever both are active, regardless of user registration order. EF Core's
CanPersistis selective — it answers true only for types actually mapped in a registeredDbContextmodel — while Marten's is a catch-all. With EF Core first, EF-mapped entities deterministically resolve to theirDbContextand everything else still falls through to Marten; nothing that only Marten can persist is ever taken away from it.Implementation options, in rough order of preference:
CanPersistinspects the type) ahead of catch-all providers (Marten, Polecat?), applied when the list is consulted rather than trusting insertion order.InsertFirst) / have the EF Core integration force itself ahead of Marten at bootstrap.Test automation
Whatever the mechanism, this needs registration-order permutation tests: Marten-then-EFCore and EFCore-then-Marten hosts, each asserting (a) a storage action /
[Entity]over aDbContext-mapped entity generatesDbContextcode, and (b) a non-EF-mapped document still resolves to Marten. TheWolverine.Http.Tests.EfCoreOnly+ pinned-ApplicationAssemblypattern from #3357 can host the HTTP variants without disturbing the main test assembly.References
CanPersist => truecombined withcandidates.First()may be worth a look on its own" note)GenerationRulesExtensions.TryFindPersistenceFrameProvider/InsertFirstPersistenceStrategy/GetPersistenceProvidersMartenPersistenceFrameProvider.CanPersist🤖 Generated with Claude Code