litedb-org/LiteDB · error · ArgumentException

Extend expression must return a document

Error message

Extend expression must return a document

What it means

Thrown by UpdateMany when the transform expression passed to it does not evaluate to a BsonDocument for a given source document. UpdateMany treats the transform as an 'extend' operation — it merges the returned document into each existing document — so a scalar/array/null return value has no valid merge semantics. The check happens per-document during the lazy enumeration of the update.

Source

Thrown at LiteDB/Engine/Engine/Update.cs:75

            IEnumerable<BsonDocument> transformDocs()
            {
                var q = new Query { Select = "$", ForUpdate = true };

                if (predicate != null)
                {
                    q.Where.Add(predicate);
                }

                using (var reader = this.Query(collection, q))
                {
                    while (reader.Read())
                    {
                        var doc = reader.Current.AsDocument;

                        var id = doc["_id"];
                        var value = transform.ExecuteScalar(doc, _header.Pragmas.Collation);

                        if (!value.IsDocument) throw new ArgumentException("Extend expression must return a document", nameof(transform));

                        var result = BsonExpressionMethods.EXTEND(doc, value.AsDocument).AsDocument;

                        // be sure result document will contain same _id as current doc
                        if (result.TryGetValue("_id", out var newId))
                        {
                            if (newId != id) throw LiteException.InvalidUpdateField("_id");
                        }
                        else
                        {
                            result["_id"] = id;
                        }

                        yield return result;
                    }
                }
            }
        }

View on GitHub (pinned to f906a5f850)

Solutions

  1. Ensure the transform expression always returns a BsonDocument (e.g. "{ count: $.count + 1 }" rather than "$.count + 1").
  2. If you only need to update a single field, use a document-projection expression that constructs the new sub-document.
  3. Test the expression in a SELECT first to verify it yields document values for all matched rows.
  4. Catch ArgumentException from UpdateMany and report which document triggered it.

Example fix

// before — transform returns a scalar
db.Execute("UPDATE items SET { count: $.count + 1 } WHERE $.active = true");
// if expression body is just "$.count + 1" it throws

// after — transform returns a document
// correct SQL/expressions construct a full or partial document:
db.GetCollection("items").UpdateMany("{ count: $.count + 1 }", "$.active = true");
Defensive patterns

Strategy: validation

Validate before calling

// Validate the transform expression returns a document for sample inputs before calling UpdateMany.
public bool TransformReturnsDocument(LiteCollection<BsonDocument> col, BsonExpression transform, BsonExpression predicate)
{
    var sample = col.Query().Where(predicate).Limit(1).FirstOrDefault();
    if (sample == null) return true; // nothing to check
    var value = transform.ExecuteScalar(sample, col.Database.GetCollection("$").Equals /*placeholder*/);
    return value.IsDocument;
}

Type guard

static bool IsDocumentResult(BsonValue v) => v != null && v.IsDocument;

Try / catch

try
{
    col.UpdateMany(transformExpr, predicateExpr);
}
catch (ArgumentException ex) when (ex.Message.Contains("Extend expression must return a document"))
{
    throw new InvalidOperationException($"Transform expression '{transformExpr.Source}' returned a non-document value. Rewrite it to return a BsonDocument.", ex);
}

Prevention

When it happens

Trigger: Calling engine.UpdateMany(collection, transform, predicate) where the transform BsonExpression evaluates to a non-document BsonValue (e.g. an expression like "$.count + 1" that yields a number, or "$.name" that yields a string) for at least one matched document.

Common situations: Confusing UpdateMany (document transform) with a field-level update; writing an expression that returns different types for different documents; using a path expression that resolves to a scalar.

Related errors


AI-assisted analysis of litedb-org/LiteDB@f906a5f850 (2026-08-13). Data as JSON: /api/errors/3460594c90d980c8. Report an issue: GitHub.