dotnet/efcore · error · InvalidOperationException

Cosmos SQL does not allow Offset without Limit. Consider spe

Error message

Cosmos SQL does not allow Offset without Limit. Consider specifying a 'Take' operation on the query.

What it means

Thrown by CosmosQuerySqlGenerator when emitting OFFSET and selectExpression.Limit is null. Cosmos SQL (the provider's dialect) requires every OFFSET to be paired with a LIMIT, so when a query carries Skip without Take the generator refuses to synthesize an arbitrary limit. The TODO comment references issue #18923 for tracking.

Source

Thrown at src/EFCore.Cosmos/Query/Internal/CosmosQuerySqlGenerator.cs:440

            if (selectExpression.Offset != null)
            {
                Visit(selectExpression.Offset);
            }
            else
            {
                _sqlBuilder.Append('0');
            }

            _sqlBuilder.Append(" LIMIT ");

            if (selectExpression.Limit != null)
            {
                Visit(selectExpression.Limit);
            }
            else
            {
                // TODO: See Issue#18923
                throw new InvalidOperationException(CosmosStrings.OffsetRequiresLimit);
            }
        }

        return selectExpression;
    }

    /// <summary>
    ///     This is an internal API that supports the Entity Framework Core infrastructure and not subject to
    ///     the same compatibility standards as public APIs. It may be changed or removed without notice in
    ///     any release. You should only use it directly in your code with extreme caution and knowing that
    ///     doing so can result in application failures when updating to a new Entity Framework Core release.
    /// </summary>
    protected virtual Expression VisitFromSql(FromSqlExpression fromSqlExpression)
    {
        var sql = fromSqlExpression.Sql;

        string[] substitutions;

View on GitHub (pinned to dbf9771522)

Solutions

  1. Add a Take(pageSize) after every Skip so the query becomes Skip(n).Take(m).
  2. If you genuinely want all rows after an offset, pass a large explicit Take (e.g. int.MaxValue) — but prefer real pagination with a bounded page size.

Example fix

// before
var page = await db.Items
    .OrderBy(x => x.Id)
    .Skip(50)
    .ToListAsync();

// after
var page = await db.Items
    .OrderBy(x => x.Id)
    .Skip(50)
    .Take(25)
    .ToListAsync();
Defensive patterns

Strategy: validation

Validate before calling

// Guard pagination helpers: require a page size whenever skip > 0
static IQueryable<T> Page<T>(IQueryable<T> q, int skip, int take)
{
    if (skip > 0 && take <= 0) throw new ArgumentOutOfRangeException(nameof(take), "Cosmos requires Take when Skip is used.");
    return q.Skip(skip).Take(take);
}

Prevention

When it happens

Trigger: Calling .Skip(n) on a Cosmos query without a following .Take(m), e.g. db.Items.OrderBy(x => x.Id).Skip(50).ToListAsync().

Common situations: Pagination code that only skips (page-by-offset without page-size). Reusing a Skip-only helper built for SQL Server against the Cosmos provider.

Related errors


AI-assisted analysis of dotnet/efcore@dbf9771522 (2026-08-06). Data as JSON: /api/errors/5df701b4737967d0. Report an issue: GitHub.