[CSHARP-4776] Support additional array length comparisons Created: 29/Aug/23  Updated: 28/Oct/23  Resolved: 25/Sep/23

Status: Closed
Project: C# Driver
Component/s: LINQ3
Affects Version/s: 2.19.0
Fix Version/s: 2.22.0

Type: Bug Priority: Unknown
Reporter: James Kovacs Assignee: Robert Stam
Resolution: Fixed Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Problem/Incident
Backwards Compatibility: Fully Compatible
Documentation Changes: Not Needed
Documentation Changes Summary:

1. What would you like to communicate to the user about this feature?
2. Would you like the user to see examples of the syntax and/or executable code and its output?
3. Which versions of the driver/connector does this apply to?


 Description   

When using upsert operation with a filter statement that contains .Count, the following error is thrown:

Unhandled exception. MongoDB.Driver.MongoWriteException: A write operation resulted in an error. WriteError: { Category : "Uncategorized", Code : 224, Message : "$expr is not allowed in the query predicate for an upsert" }.
 ---> MongoDB.Driver.MongoBulkWriteException`1[MongoDBUpdateExample.DbTargetRecord]: A bulk write operation resulted in one or more errors. WriteErrors: [ { Category : "Uncategorized", Code : 224, Message : "$expr is not allowed in the query predicate for an upsert" } ].

This does not happen on versions below 2.19, such as 2.18.

The following code reproduces the issue. It works without issues for versions < 2.19.0 and starts throwing the error for any version >= 2.19.0.

using MongoDB.Driver;
using System;
using System.Threading.Tasks;
 
namespace MongoDBUpdateExample
{
    class Program
    {
        static async Task Main(string[] args)
        {
            // Initialize MongoDB client and get the collection
            var client = new MongoClient("connection_string");  // Replace with your MongoDB connection string
            var database = client.GetDatabase("db");  // Replace with your database name
            var collection = database.GetCollection<DbTargetRecord>("collection");  // Replace with your collection name
 
            FilterDefinition<DbTargetRecord> filter = Builders<DbTargetRecord>.Filter.Where(
                x => x._id == "1"
                && x.DistinctAnchors.Count <= 3
            );
 
            UpdateDefinition<DbTargetRecord> updateDefinition = Builders<DbTargetRecord>.Update.Set(z => z.LastUpdateTime, DateTime.UtcNow);
 
            UpdateResult result = await collection.UpdateOneAsync(filter, updateDefinition, new UpdateOptions() { IsUpsert = true });
        }
    }
 
    public class DbTargetRecord
    {
        public string _id { get; set; }
        public List<string> DistinctAnchors { get; set; }
        public DateTime LastUpdateTime { get; set; }
    }
 
    public class TargetAnchor
    {
        public string Target { get; set; }
        public string Anchor { get; set; }
    }
}



 Comments   
Comment by Githook User [ 25/Sep/23 ]

Author:

{'name': 'rstam', 'email': 'robert@robertstam.org', 'username': 'rstam'}

Message: CSHARP-4776: Support additional array length comparisons.
Branch: master
https://github.com/mongodb/mongo-csharp-driver/commit/cf6561109674f1abb1b3e4ccdde170d0fd954485

Comment by James Kovacs [ 29/Aug/23 ]

The LINQ provider is used to translate the lambda in the Where clause to MQL. In LINQ2 (prior to 2.19.0), it translates this Where clause to the following:

{ "_id" : "1", "DistinctAnchors.3" : { "$exists" : false } }

In LINQ3 (after 2.19.0), it translates it to:

{ "$and" : [{ "_id" : "1" }, { "$expr" : { "$lte" : [{ "$size" : "$DistinctAnchors" }, 3] } }] }

As you can see, LINQ3 translates this using $expr which the server does not permit in an upsert.

You can work around this issue by using builder syntax without the Where clause similar to the following:

var builder = Builders<DbTargetRecord>.Filter;
var filter1 = builder.Eq(x => x.Id, "1");
var filter2 = builder.SizeLte(x => x.DistinctAnchors, 3);
var filter = builder.And(filter1, filter2);

Generated at Wed Feb 07 21:49:20 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.