[CSHARP-4809] If a string serializer doesn't implement IHasRepresentationSerializer don't assume string values are actually represented as strings Created: 13/Oct/23  Updated: 26/Oct/23  Resolved: 26/Oct/23

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

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

Attachments: PNG File image-2023-10-13-11-16-47-287.png    
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   

Summary

Apologies if I miss something expected, just joined the project so I could report this.  I'm encountering a bug where the LINQ3 provider is not matching a document for update, but LINQ2 provider works.  
I think it has something to do with the string -> ObectId conversion not woking with Linq3, maybe bsonserializer attribute is being ignored?

 

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.22.0 (worked with MongoDB.Driver 2.16.1)

MongoDB 6.04 replicaset

How to Reproduce

Steps to reproduce. If possible, please include a Short, Self Contained, Correct (Compilable), Example.
create a .net 6 console application
add nuget for:
  MongoDB.Driver 2.22
  Microsoft.Extensions.Configuration 7.0
  _  _Microsoft.Extensions.Configuration.Json 7.0

add an appsettings.json file with 


_

{   "ConnectionStrings": \{     "MongoConnectionString": "<<<mongo connection string here>>>"   }

}_

in Program.cs add this code

 

 


_using MongoDB.Bson.Serialization.Serializers;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Bson.Serialization;
using Microsoft.Extensions.Configuration;
using MongoDB.Bson.Serialization.Attributes;/**
 * 
 * 
 * toggle the value for useLinq3 to true to reproduce bug
 * toggle the vaue for useLinq3 to false to show working in legacy
 * 
 */_
_var useLinq3 = false;
#region mainvar configuration = new ConfigurationBuilder()
     .AddJsonFile($"appsettings.json");var config = configuration.Build();
var connectionString = config.GetConnectionString("MongoConnectionString");MongoClientSettings settings = MongoClientSettings.FromConnectionString(connectionString);
if (!useLinq3)
{
    settings.LinqProvider = MongoDB.Driver.Linq.LinqProvider.V2;
}
MongoClient dbClient = new MongoClient(settings);var database = dbClient.GetDatabase("sample_update_bug");
var collection = database.GetCollection<RootDocument>("RootDocuments");
var newDoc = new RootDocument() { Id = ObjectId.GenerateNewId().ToString() };
await collection.InsertOneAsync(newDoc);
var newSubDoc = new SubDocument()
{
    SdId = ObjectId.GenerateNewId().ToString(),
    DateAdded = DateTime.UtcNow
};FilterDefinition<RootDocument> filter = Builders<RootDocument>.Filter.Where(x => x.Id == newDoc.Id);
UpdateDefinition<RootDocument> update = Builders<RootDocument>.Update.Set(x => x.SubDoc, newSubDoc);var updateResult = await collection.UpdateOneAsync(filter, update).ConfigureAwait(false);
#endregion main
public static class Extensions
{
    public static ObjectId ToObjectId(this string source)
    {
        if (ObjectId.TryParse(source, out ObjectId returnId))
       

{             return returnId;         }

        else
        {
            return ObjectId.Empty;        }
    }
}#region string to ObjectId serializer
public class ObjectIdSerializer : SerializerBase<string>
{
    public override string Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
    {
        BsonType type = context.Reader.GetCurrentBsonType();
        switch (type)
        {
            case BsonType.ObjectId: return context.Reader.ReadObjectId().ToString();
            default:
                var message = $"Cannot convert a

{type}

to a String.";
                throw new NotSupportedException(message);
        }
    }    public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, string value)
    {
        context.Writer.WriteObjectId(value.ToObjectId());
    }
}
#endregion#region entities
[BsonIgnoreExtraElements]
public class RootDocument
{
    public RootDocument()
   

{     }

    [BsonSerializer(typeof(ObjectIdSerializer))]
    public string Id { get; set; }    [BsonElement("sd")]
    public SubDocument SubDoc { get; set; }
}public class SubDocument
{
    /// <summary>The _id of the document in GridFS</summary>
    [BsonElement("s")]
    [BsonSerializer(typeof(ObjectIdSerializer))]
    public string SdId

{ get; set; }

    [BsonElement("d")]
    public DateTime DateAdded { get; set; }}
#endregion_

Run with useLinq3 = false

Run with useLinq3 = true

open a mongodb client and query the docs in the RootDocuments collection

expected:
  the docs should be equivalent (diff id's, and times, but same fields should be populated)

actual:

  the doc when linq3 is used still has "sd" : null instead of the subdocument being saved

 

Additional Background

Please provide any additional background information that may be helpful in diagnosing the bug.



 Comments   
Comment by Githook User [ 26/Oct/23 ]

Author:

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

Message: CSHARP-4809: If a string serializer doesn't implement IHasRepresentationSerializer don't assume string values are actually represented as strings.
Branch: master
https://github.com/mongodb/mongo-csharp-driver/commit/c249251997170f50a08945d7e0f2b57d6c2e13c5

Comment by Robert Stam [ 16/Oct/23 ]

The core issue here is that the LINQ translator has no way of determining whether your custom serializer actually stores C# string values as strings in the database, which in your case it does not (it converts them to ObjectIds).

The way the LINQ provider tries to determine whether a string serializer actually stores string values as strings in the database is by checking to see if the serializer implements the IRepresentationConfigurable interface, which your custom serializer does not.

The current code assumes that a string serializer does in fact store strings as strings (a reasonable assumption). Since this assumption is not guaranteed to be correct, we will be changing the code to assume the opposite: unless we can determine for sure that a string serializer actually stores string values as strings we will assume it does not. This is the safer assumption. The consequence is that in such a case we won't ever translate any C# string expressions to MQL regexes.

Note: while the current code uses IRepresentationConfigurable to determine how a string serializer actually represents string values in the database, we will be switching to using the IHasRepresentationSerializer interface. In other words, we only need to determine the serialized representation, it doesn't matter if it is configurable or not.

Comment by Boris Dogadov [ 13/Oct/23 ]

Thank you for submitting this issue and the reproduction code frederick.donald@gmail.com.

We have reproduced this behavior and will be looking further into this.
Meanwhile we can suggest a workaround to use ObjectId type directly instead of string, without custom serializer (and using ToString on demand).

Please follow this ticket for further updates.

Comment by Donald Frederick [ 13/Oct/23 ]

Apologies, discovered new information while creating this bug, and forgot to update the title.  If someone is able to edit, please change it to something more descriptive.  I suggest "LINQ3 filter on id with custom bsonserializer doesn't seem to be respected causing no match"

Comment by PM Bot [ 13/Oct/23 ]

Hi frederick.donald@gmail.com, thank you for reporting this issue! The team will look into it and get back to you soon.

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