[CSHARP-4311] LINQ translation bug when document class derives from Dictionary<string, object> Created: 07/Sep/22  Updated: 27/Oct/23  Resolved: 27/Sep/22

Status: Closed
Project: C# Driver
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Question Priority: Unknown
Reporter: Mo B. Assignee: James Kovacs
Resolution: Gone away Votes: 0
Labels: LINQ
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified


 Description   

Summary

When the document class derives from Dictionary<string, object> and additionally has a property that is backed by a dictionary entry, LINQ does not work properly.

Please provide the version of the driver. If applicable, please provide the MongoDB server version and topology (standalone, replica set, or sharded cluster).

MongoDB.Driver 2.17.1 on .NET 6

How to Reproduce

Consider following document class:

public class MyType: Dictionary<string, object>
{{{}}
    [BsonElement("displayname")]
    public string DisplayName
    {
        get => (string)this["displayname"];
        set => this["displayname"] = value;
{{    }}}
}

 

Now if we try to do a LINQ query like 

await collection.AsQueryable()
            .Where(r => r.DisplayName == "Test")
            .ToListAsync();

we get an InvalidOperationException "The operands for operator 'Equal' do not match the parameters of method 'op_Equality'."

Setting the LinqProvider to LinqProvider.V3 eliminates the exception, but the resulting list is always empty.

Using .Where(r => r["displayname"] == "Test") results in a correct non-empty result list.

 



 Comments   
Comment by PM Bot [ 27/Sep/22 ]

There hasn't been any recent activity on this ticket, so we're resolving it. Thanks for reaching out! Please feel free to comment on this if you're able to provide more information.

Comment by James Kovacs [ 12/Sep/22 ]

Hi, Mo,

Please see my comment on CSHARP-4310 for my explanation on why we do not recommend the strongly-typed property bag approach, which is essentially what deriving from Dictionary<string, object> entails.

As to why you get no results back from the query in LINQ3, the reason lies in how we serialize collection classes. When serializing collections, the expectation is that the collection is a list of values or name-value pairs. We do not expect serialization metadata in the form of BSON attributes. Thus [BsonElement("displayname")] is never considered during the serialization and the resulting query is:

test.coll.Aggregate([{ "$match" : { "DisplayName" : "Test" } }])

You will notice that the query is on DisplayName and not displayname. These queries would work if your properties matched your database fields exactly (including casing). We still do not recommend the strongly-typed property bag approach, but you could make it work by any of the following means:

  • Implement a custom IBsonSerializer<T> that handled the casing differences
  • Change your database fields to match your C# property names (including casing)
  • Change your C# property names to match the database fields (including casing)

Please let us know if you have any additional questions.

Sincerely,
James

Comment by Boris Dogadov [ 09/Sep/22 ]

Thanks mywyb2@gmail.com for your report.

Currently custom properties in Dictionary based types are not supported by standards serializers. So the mentioned code would not be supported.

One option would be not using Dictionary bases types, and storing the additional data via BsonExtraElements: https://mongodb.github.io/mongo-csharp-driver/2.17/reference/bson/mapping/#supporting-extra-elements
Another workaround would be implementing custom serializer, and introducing TryGetMemberSerializationInfo method to handle correctly the additional properties naming translations used by LINQ.

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