Skip to content

Persistence provider resolution: EF Core must be evaluated before Marten when both are registered (Marten's blanket CanPersist wins on registration order) #3359

Description

@jeremydmiller

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:

  1. 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.
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions