1

Issue

We have a custom serializer for an enum (MeasurementValueType), which stores the enum as a string in MongoDB and deserializes it from a string back into the enum when querying.

The enum MeasurementValueType is declared as ushort. If it is not then everything works as expected.

We are using Mongo C# driver version 2.30 and trying to upgrade to linq provider v# from V2.

Setup

A simple class with an enum property:

public class TestA
{
    public MeasurementValueType ValueType { get; set; }
}

public enum MeasurementValueType : ushort
{
   Distance = 1,
   Angle = 2
}

Custom serializer for MeasurementValueType:

public class MeasurementValueTypeSerializer : StructSerializerBase<MeasurementValueType>
{
    public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, MeasurementValueType value)
    {
        BsonSerializer.Serialize(context.Writer, value.ToString());
    }

    public override MeasurementValueType Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
    {
        var measurementValueType = (MeasurementValueType)Enum.Parse(
            args.NominalType, 
            BsonSerializer.Deserialize<string>(context.Reader)
        );
        return measurementValueType;
     }
 }

Registered in startup:

BsonSerializer.RegisterSerializer(typeof(MeasurementValueType), new MeasurementValueTypeSerializer());

Expected behavior

When saving a document, ValueType should be stored as a string in MongoDB.

When querying, the deserializer should convert it back to the enum. When inserting an object the serializer is called and the enum is persisted as a string. When fetching it back as a whole document, the deserializer is called.

Problem

If I query using LINQ, the deserializer is not applied and no match is found:

database.TestACollection.Insert(new TestA { ValueType = MeasurementValueType.Angle });

var match = database.TestACollection
    .AsQueryable()
    .Single(x => x.ValueType == MeasurementValueType.Angle);

This worked fine in LINQ Provider v2, but in LINQ Provider v3, it no longer matches.

Question

Why isn't the right-hand expression (MeasurementValueType.Angle) passed to the serializer so it can be converted to a string before executing the query?

I understand that we do not want to deserialize the stored properties but my right-hand expression i.e. the enum could be serialized to the same format (string) for fast comparison on the database side.

Is there a workaround to ensure the serializer is respected in LINQ queries?

Can I register the serializer in a different way so that LINQ v3 applies it?

1 Answer 1

1

If you do not use a custom serializer, but store the value as string by applying the BsonRepresentation attribute, the LINQ statement works as expected:

public enum TestEnum
{
    Value123 = 123
}

[BsonNoId]
[BsonIgnoreExtraElements]
public class TestDoc
{
    [BsonRepresentation(BsonType.String)]
    public TestEnum TestAsString { get; set; }

    [BsonElement]
    public TestEnum TestAsInt => TestAsString;
}
  
var val123 = await test  
                .AsQueryable()  
                .SingleAsync(x => x.TestAsString == TestEnum.Value123);  

This leads to the following document stored in the database:

{
  "_id": {
    "$oid": "67ef9018ea66d16ac7360313"
  },
  "TestAsString": "Value123",
  "TestAsInt": 123
}
Sign up to request clarification or add additional context in comments.

4 Comments

Thanks. Can the same behavior be injected instead. We've made a decision not to reference mongo specifics in our model projects, they are clean POCO's. Also the enum should be explicitly be defined as ushort for it not to work in my example.
I tested your example with explict enum of ushort and it shows the same behavior as mine. The C# driver looks at the underlying type and in ConvertExpressionToFilterFieldTranslator.Translate selects an Int32Serializer instead of my custom.
It is no problem to configure the representation in a class map by using cm.MapMember(x => x.TestAsString).SetSerializer(new EnumSerializer<TestEnum>(BsonType.String));; unfortunately, if the enum base type if ushort, the query in the SingleAsync does not serialize the condition correctly. Maybe it is an option to base the enum on int and raise a bug with MongoDB.
Thanks again. Unfortunately I cannot change the enum to int. I'll raise a bug. Thanks for your effort!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.