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;
}
}