[CSHARP-2107] [BsonRepresentation(BsonType.String)]-attribute not respected in projection Created: 20/Nov/17  Updated: 28/Oct/23  Resolved: 29/Jun/22

Status: Closed
Project: C# Driver
Component/s: Linq, Serialization
Affects Version/s: 2.4.2
Fix Version/s: 2.17.0

Type: Bug Priority: Major - P3
Reporter: Vegar Vikan Assignee: Robert Stam
Resolution: Fixed Votes: 2
Labels: triaged
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Epic Link: CSHARP-3615
Backwards Compatibility: Fully Compatible

 Description   

I have a class called Identity with two properties: IdentityType and IdentityNumber. The Type property is an enum with the [BsonRepresentation(BsonType.String)] attribute set, so it's stored as strings instead of integers.

I now have a aggregation ending in a projection. In that projection, I have a $filter on an array of identities. That filter fails, and from what I can see, it fails because it tries to compare the stored string values to integer values.

I'll try to give an representation of my data and types:

Classes

//root class for documents stored
class Customer
  [BsonRepresentation(BsonType.ObjectId)]      
  string Id;
  User[] Users;
  Items[] Items;
end;
 
class User
  Identity Identity;
end;
 
class Identity
  [BsonRepresentation(BsonType.String)]
  IdentityType IdentityType;
  string IdentityNumber;
end;
 
class Item
  [BsonRepresentation(BsonType.ObjectId)]      
  string Id;
end;

My aggregation

_collection.Aggregate()
  .Match(Builders<Customer>.Filter.In("Items.Id", ["id1", "id2"]))
  .Unwind<UnwindedCustomer>("Items")
  .Match(Builders<UnwindedCustomer>.Filter.In("Items.Id",  ["id1", "id2"]))
  .Project(doc => new
  {
    Id = doc.Items.Id,
    CustomerId = doc.Id,
    UserIsCustomer = doc.Users.Where(user =>
      user.Identity.IdentityNumber == "123" &&
      user.Identity.IdentityType == IdentityType.SomeType)
  });

So I'm looking for some specific items, and for those items, I want to know which customer they belongs to, and if the given user is a listed user of that customer.

If I render the last stage of the projection, I get something like

{ "$project" : 
  { 
    "Id" : "$Items.Id", 
    "CustomerId" : "$_id", 
    "UserIsCustomer" : 
    { "$filter" : 
        { 
          "input" : "$Users", 
          "as" : "user", 
          "cond" : { "$and" : [
            { "$eq" : ["$$user.Identity.IdentityNumber", "123"] }, 
            { "$eq" : ["$$user.Identity.IdentityType", 1] }
          ] } 
    } }, 
    "_id" : 0 
  } 
}

I would expect the projection to respect the BsonRepresentation attribute and render the last part of the condition as `

{ "$eq" : ["$$user.Identity.IdentityType", "SomeType"] }

`



 Comments   
Comment by Robert Stam [ 29/Jun/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 Githook User [ 29/Jun/22 ]

Author:

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

Message: CSHARP-2107: Verify that issue is not present in LINQ3.
Branch: master
https://github.com/mongodb/mongo-csharp-driver/commit/5008cb2b081c20d863e880540480caa7a51d52f5

Comment by James Kovacs [ 09/Feb/22 ]

The above noted bug is related to fluent aggregate (e.g. coll.Aggregate()). The same query can be implemented using LINQ via coll.AsQueryable().Select() (LINQ's Select is equivalent to fluent aggregate's Project). This issue has been fixed in the new LINQ provider (known as LINQ3), which is included in the 2.14 release. We will leave this bug open to track the issue in fluent aggregate.

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 bashir ansari [ 07/Oct/19 ]

Any updates on this one? 

Comment by Vegar Vikan [ 01/Apr/19 ]

Thanks. 

Luckily, we managed to find a workaround our self...

Comment by Wan Bachtiar [ 27/Mar/19 ]

Hi Vegar,

Thanks for reporting this issue. Indeed there is a bug in the driver where in this case the Enum is serialized as integer value rather than the string name value.

In the meantime, as workarounds you could either construct the projection pipeline using BsonDocument as below:

var pipeline = new BsonDocument[] 
{
    new BsonDocument ( "$project", new BsonDocument(
        "UserIsCustomer", new BsonDocument(
            "$filter", new BsonDocument(
                "input", "$Users")
            .Add("as", "user")
            .Add("cond", new BsonDocument(
                    "$and", new BsonArray()
                        .Add(new BsonDocument(
                            "$eq", new BsonArray().Add("$$user.Identity.IdentityNumber").Add("1")))
                        .Add(new BsonDocument(
                            "$eq", new BsonArray().Add("$$user.Identity.IdentityType").Add("TypeA")))
                    )
                )
            )
        )
    )
};

Or, create a set of classes for the aggregation to change the type of IdentityType from Idenfity to string. Then you could get the string name from the enum as below example:

 
public class CustomerAgg
{
    public ObjectId Id {get;set;}
    public UserAgg[] Users {get;set;}
}
public class UserAgg
{
    public IdentityAgg Identity {get; set;}
}
public class IdentityAgg 
{
    public string IdentityType {get;set;}
    public string IdentityNumber {get;set;}
}
 
var typeA = IdentityType.TypeA; 
var aggregation = database.GetCollection<CustomerAgg>("collection")
                .Aggregate()
                .Project(doc => new
                    {
                        UserIsCustomer = doc.Users.Where(user =>
                                user.Identity.IdentityNumber == "1" &&
                                user.Identity.IdentityType == Enum.GetName(typeof(IdentityType), typeA))
                    });

Regards,
Wan.

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