Can Skip/Take reliably paginate many‑to‑many queries?
0 reputation · 18 Jul 2026, 23:48 UTC
0 reputation · 18 Jul 2026, 23:48 UTC
The objective is to retrieve a specific page—say the 10th page of 1,000 records—from a very large table that participates in a many‑to‑many relationship. The query must avoid loading the entire dataset into memory, prevent duplicate root entities, and maintain acceptable performance on SQL Server 2019 using NHibernate 6.
Skip() and Take() on an IQueryable.fetch="join" to eagerly load the related collection.Skip() and Take() into SQL when a fetch join is present, ensuring that pagination occurs after the join?IQueryable extensions like Skip() and Take(), or does it require separate query construction?No – using Skip() and Take() on an IQueryable that includes a fetch="join" for a many‑to‑many collection does not reliably return the requested page of distinct root entities.
Skip/Take to an OFFSET … FETCH clause that is applied after the FROM and JOIN but before any client‑side distinct root filtering.DistinctRootEntityResultTransformer.Skip/Take (no fetch join). Example using HQL or Criteria:var idQuery = session.CreateQuery(
"select e.Id from Entity e join e.Collection c where …")
.SetFirstResult(page * pageSize)
.SetMaxResults(pageSize);
var ids = idQuery.List<int>(); // or long depending on Id type
var entities = session.CreateQuery(
"from Entity e join fetch e.Collection where e.Id in (:ids)")
.SetParameterList("ids", ids)
.List<Entity>();
Restrictions.In on the identifier.StatelessSession works with the same IQueryable extensions; it does not eliminate the duplicate‑root issue, so apply the ID‑subquery pattern above when using it as well.
If you observe that the join cardinality is always 1‑to‑1 (each root has at most one collection row), the naive Skip/Take with fetch join would be safe. Confirm the maximum rows per root before deciding to skip the subquery step.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.