Popcorn is a .Net Middleware for your RESTful API that allows your consumers to request exactly as much or as little as they need, with no effort from you.
This project is maintained by Skyward App Company
Lazy loading is the pattern of deferring the
materialization of related data until something actually asks for it. For web APIs, the
classic motivation is an Author list page that could cascade into every author’s blog
posts but shouldn’t, because nobody reads those posts on that page.
In Popcorn v8, lazy loading works by construction — there is nothing to configure.
The source generator emits a JsonConverter<T> that writes one property at a time. If a
property isn’t in the ?include= list (or doesn’t match the default set), the generator’s
emitted code never accesses the getter. If that getter is a lazy-loaded EF navigation
property, Entity Framework’s change tracker never fires a load query, and the database is
never touched.
This is a straight consequence of v8’s architecture: there is no intermediate projection
step. The v7 reflection engine built a Dictionary<string, object?> describing the entire
output before serializing — which meant every getter ran, which triggered every navigation
load. v8 skips the dictionary and writes directly to Utf8JsonWriter property-by-property,
so untouched getters stay untouched.
public class Author
{
[Default] public int Id { get; set; }
[Default] public string Name { get; set; } = "";
// EF will lazy-load this when accessed.
public virtual ICollection<Post> Posts { get; set; } = new List<Post>();
}
GET /authors
→ default set: { Id, Name }. Posts getter is never called. Zero Post queries.
GET /authors?include=[Id,Name,Posts[Title]]
→ Posts getter runs per author; EF fires one Post query per author (configurable via eager loading).
If the client doesn’t ask for Posts, Popcorn doesn’t fetch Posts. No framework flags, no
EnableLazyLoading(true), no MapEntityFramework<S, P, Ctx>(...) registration — the
generator’s emission pattern is what gets you the behavior.
The v7 API had a MapEntityFramework<TSource, TProjection, TContext>() configuration method
that accepted an EF DbContext and wired up lazy-load support. v8 has no equivalent —
and doesn’t need one. Drop MapEntityFramework calls from your startup configuration during
migration; everything continues to work.
See MigrationV7toV8.md §7 for the full projection-pattern discussion.
UseLazyLoadingProxies() on the DbContextOptionsBuilder). Popcorn does not change how
EF loads; it only changes which getters run..Include(a => a.Posts), Posts is already in memory — Popcorn will still honor ?include=
and omit it from the response, but the DB round-trip has already happened. Use lazy loading
(or a projection at the query layer) when cost matters.Posts inside a loop of 500 authors, that’s 500
queries. Popcorn skipping the fetch when the client doesn’t ask for Posts is what keeps
you out of the N+1 zone in the common case — but if a client does ask for it, plan for the
query count.