dotnet/efcore · error · InvalidOperationException

Cosmos SQL does not allow Offset without Limit. Consider…

Error message

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

What it means

Cosmos SQL syntax requires that any OFFSET clause be paired with a LIMIT clause. The CosmosQuerySqlGenerator emits OFFSET and then a LIMIT token, and when a SelectExpression carries an Offset but no Limit it throws InvalidOperationException at runtime during SQL generation. This is a hard restriction of the Cosmos DB SQL API surface, not an EF choice.

Solutions

  1. Add a .Take(pageSize) immediately after every .Skip(skip) in the query chain.
  2. Cap the skip with a sane Take (e.g. int.MaxValue) if you genuinely want all remaining rows after the offset.
  3. If paging is optional, ensure Take is only omitted when Skip is also omitted.

Example fix

// before
var page = db.Users.Skip(20).ToList();
// after
var page = db.Users.Skip(20).Take(10).ToList();
Defensive patterns

Strategy: validation

Validate before calling

// before executing, assert every Skip is paired with Take
static IQueryable<T> Paged<T>(IQueryable<T> q, int skip, int? take)
{
    var withSkip = q.Skip(skip);
    return take is { } t ? withSkip.Take(t) : withSkip.Take(int.MaxValue);
}

Prevention

When it happens

Trigger: A LINQ query uses .Skip(n) (which becomes Offset) without a following .Take(m) (which becomes Limit), and that query is executed against the Cosmos provider. EF cannot infer a safe default upper bound, so it refuses to emit OFFSET-only SQL.

Common situations: Pagination code that only forwards a page offset, hand-rolled paging that splits Skip/Take across conditional branches, or third-party paging helpers that omit Take when page size is null.

Related errors


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

Appendix: 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 3a2006ef56)