[CSHARP-3524] LINQ code generates incorrect $filter statement Created: 02/Apr/21  Updated: 28/Oct/23  Resolved: 17/Feb/22

Status: Closed
Project: C# Driver
Component/s: Linq
Affects Version/s: 2.12.1
Fix Version/s: 2.14.0

Type: Bug Priority: Major - P3
Reporter: Christian Flessa Assignee: Robert Stam
Resolution: Fixed Votes: 0
Labels: triaged
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Epic Link: CSHARP-3615

 Description   

Hey guys,

I found a problem with the following code:

namespace MongoError
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
 
    using MongoDB.Bson;
    using MongoDB.Driver;
 
    public class Program
    {
        public static void Main(String[] args)
        {
            var client = new MongoClient();
            var database = client.GetDatabase("MyDB");
            var collection = database.GetCollection<Item>("Items")
                                     .AsQueryable();
            
            var result = collection.SelectMany(x => x.Meta,
                                               (item, meta) => new ProjectedItem
                                               {
                                                   ItemId = item.Id.ToString(),
                                                   Meta = meta,
                                                   Property = item.Properties
                                                                  .First(p => p.Id == meta.PropertyId)
                                               });
 
            Console.WriteLine(result);
        }
 
        private class Item
        {
            public ObjectId Id { get; set; }
            public List<ItemMeta> Meta { get; set; }
            public List<ItemProperty> Properties { get; set; }
        }
 
        private class ProjectedItem
        {
            public String ItemId { get; set; }
            public ItemMeta Meta { get; set; }
            public ItemProperty Property { get; set; }
        }
 
        private class ItemMeta
        {
            public String Meta { get; set; }
            public Int32 PropertyId { get; set; }
        }
 
        private class ItemProperty
        {
            public Int32 Id { get; set; }
            public String Text { get; set; }
        }
    }
}

The generated aggregation pipeline looks like this:

[
    {
        "$unwind": "$Meta"
    },
    {
        "$project": {
            "ItemId": {
                "$toString": "$_id"
            },
            "Meta": "$Meta",
            "Property": {
                "$arrayElemAt": [
                    {
                        "$filter": {
                            "input": "$Properties",
                            "as": "p",
                            "cond": {
                                "$eq": [
                                    "$$p._id",
                                    "$$p.Meta.PropertyId"
                                ]
                            }
                        }
                    },
                    0
                ]
            },
            "_id": 0
        }
    }
]

The condition part of the $filter statement is this:

"$eq": [
    "$$p._id",
    "$$p.Meta.PropertyId"
]

But I think it really should look like this:

"$eq": [
    "$$p._id",
    "$Meta.PropertyId"
]



 Comments   
Comment by James Kovacs [ 17/Feb/22 ]

This issue has been fixed in the new LINQ provider (known as LINQ3), which is included in the 2.14 release.

Configure your MongoClientSettings to use LinqProvider.V3 if you want to use this functionality.

To configure a client to use the LINQ3 provider use code like the following

var connectionString = "mongodb://localhost";
var clientSettings = MongoClientSettings.FromConnectionString(connectionString);
clientSettings.LinqProvider = LinqProvider.V3;
var client = new MongoClient(clientSettings);

Comment by Robert Stam [ 05/Apr/21 ]

I agree with you that it doesn't look correct.

We are currently reimplementing our LINQ translation layer, and probably won't fix this in the current LINQ implementation.

The new LINQ translator results in a slightly different pipeline, but the result looks correct:

[
    { 
        $project : {
            _v : {
                $map : {
                    input : '$Meta',
                    as : 'meta',
                    in : {
                        ItemId : { $toString : '$_id' },
                        Meta : '$$meta',
                        Property : {
                            $arrayElemAt : [
                                { $filter : { input : '$Properties', as : 'p', cond : { $eq : ['$$p._id', '$$meta.PropertyId'] } } },
                                0
                            ]
                        }
                    }
                }
            },
            _id : 0
        }
    },
    { $unwind : '$_v' }
]

In the new LINQ translator the projection is done first and then the $unwind (potentially subject to change as we are still working on it).

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