Uploaded image for project: 'C# Driver'
  1. C# Driver
  2. CSHARP-4332

Casting/serialization issue after switching to LINQ3

    • Type: Icon: Bug Bug
    • Resolution: Works as Designed
    • Priority: Icon: Unknown Unknown
    • None
    • Affects Version/s: None
    • Component/s: LINQ3
    • Labels:
      None

      In my codebase I have a custom type defined together with the custom serializer for that type. After switching to linq v3 several types of queries are exploding with the following error:

      Unhandled exception. System.ArgumentException: Invalid toType: System.Guid. (Parameter ‘toType’)
      at MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions.AstExpression.Convert(AstExpression input, Type toType, AstExpression onError, AstExpression onNull)
      …
      

      Is there a way to configure the driver so the query rewrite won’t be necessary? Below full repro (driver 2.17.1):

      using System;
      using System.Linq;
      using System.Linq.Expressions;
      using System.Security.Authentication;
      using MongoDB.Bson;
      using MongoDB.Bson.Serialization;
      using MongoDB.Bson.Serialization.Serializers;
      using MongoDB.Driver;
      using MongoDB.Driver.Linq;
      
      BsonSerializer.RegisterSerializer(typeof(Guid), new GuidSerializer(BsonType.String));
      BsonSerializer.RegisterSerializer(typeof(InvoiceId), new MyGuidSerializer());
      BsonTypeMapper.RegisterCustomTypeMapper(typeof(InvoiceId), new MyGuidBsonTypeMapper());
      
      
      var settings = MongoClientSettings.FromUrl(new MongoUrl("mongodb://localhost:27017/test"));
      settings.SslSettings = new SslSettings {EnabledSslProtocols = SslProtocols.Tls12};
      settings.LinqProvider = LinqProvider.V3;
      var mongoClient = new MongoClient(settings);
      var mongoDatabase = mongoClient.GetDatabase("test");
      mongoDatabase.DropCollection("test");
      var collection = mongoDatabase.GetCollection<Document>("test");
      
      
      var guid = Guid.NewGuid();
      var invoiceId = new InvoiceId(guid);
      var guidNullable = (Guid?) guid;
      var invoiceIdNullable = (InvoiceId?) invoiceId;
      var document = new Document
      {
          InvoiceId = invoiceId,
          InvoiceIdNullable = invoiceId,
          Guid = guid,
          GuidNullable = guid
      };
      collection.InsertOne(document);
      
      Expression<Func<Document, bool>>[] f =
      {
          c => c.Guid == guid,
          c => c.GuidNullable == guid,
          c => c.Guid == invoiceId,
          c => c.GuidNullable == invoiceId,
          c => c.InvoiceId == invoiceId,
          c => c.InvoiceIdNullable == invoiceId,
          
          c => c.Guid == guidNullable,
          c => c.GuidNullable == guidNullable,
          c => c.Guid == invoiceIdNullable,
          c => c.GuidNullable == invoiceIdNullable,
          c => c.InvoiceId == invoiceIdNullable,
          c => c.InvoiceIdNullable == invoiceIdNullable,
          
          c => c.InvoiceId == guidNullable, // explodes in V3
          c => c.InvoiceIdNullable == guidNullable, // explodes in V3
          c => c.InvoiceId == guid, // explodes in V3
          c => c.InvoiceIdNullable == guid, // explodes in V3
      };
      
      foreach (var expression in f)
      {
          Console.Out.WriteLine(expression.ToString());
              
          var results = collection.AsQueryable().Where(expression).ToCursor().ToList();
          var result = results.FirstOrDefault() ?? throw new Exception("Not found!");
          if (result.InvoiceId != invoiceId)
          {
              throw new Exception("Mismatch!");
          }
      
          Console.Out.WriteLine("All good");
      }
      
      public class Document
      {
          public ObjectId Id { get; set; }
          public InvoiceId InvoiceId { get; set; }
          public InvoiceId? InvoiceIdNullable { get; set; }
          public Guid Guid { get; set; }
          public Guid? GuidNullable { get; set; }
      }
      
      public readonly record struct InvoiceId(Guid Value)
      {
          public static implicit operator Guid(InvoiceId s) => s.Value;
      }
      
      public class MyGuidSerializer : SerializerBase<InvoiceId>
      {
          public override InvoiceId Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
          {
              if (context.Reader.CurrentBsonType == BsonType.Null)
              {
                  context.Reader.ReadNull();
                  return default;
              }
      
              if (Guid.TryParse(context.Reader.ReadString(), out var guid))
              {
                  return new InvoiceId(guid);
              }
      
              return new InvoiceId(default);
          }
      
          public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, InvoiceId value)
          {
              context.Writer.WriteString(value.Value.ToString());
          }
      }
          
      public class MyGuidBsonTypeMapper : ICustomBsonTypeMapper
      {
          public bool TryMapToBsonValue(object value, out BsonValue bsonValue)
          {
              bsonValue = (BsonString)((InvoiceId)value).Value.ToString();
              return true;
          }
      }
      

      Originally from this MongoDB Community Forums post filed by Marek Olszewski.

            Assignee:
            robert@mongodb.com Robert Stam
            Reporter:
            james.kovacs@mongodb.com James Kovacs
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: