Using Linq filter queries with custom value object causes: 'Serializer does not represent members as fields', but the suggested solution does not work.

Hello there!

I am working on our OpenSource project for ValueObjects. We have a special kind of ValueObject that only hats one primitive (int, string, etc.) value. A typical primitive looks like this:

public sealed class Age : PrimitiveValueObject<Age, int>
{
	/// <inheritdoc />
	public Age(int value) : base(value)
	{
	}
}

The base class provides the Value property. In this case of type int.

The entity class looks like this:

public class Person
{
	public ObjectId Id { get; set; }

	public string Name { get; set; }

	public Age Age { get; set; }
}

I implemented a generic custom serializer to allow storing the value just as a simple integer and not as a complex type. This works fine, but when we try to query data it caused the error: “Serializer does not represent members as fields”.

The topic here fixed this problem, but now I don’t get any value back. The result is just null.

I could confirm, that the Deserialize method is now no longer called.

The serializer class looks like this:

public sealed class PrimitiveValueObjectSerializer<TValueObject, TValue> : SerializerBase<TValueObject>, IBsonDocumentSerializer
	where TValueObject : PrimitiveValueObject<TValueObject, TValue>
	where TValue : IComparable
{
	/// <inheritdoc />
	public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TValueObject value)
	{
		if(value is null)
		{
			context.Writer.WriteNull();
		}
		else
		{
			BsonSerializer.Serialize(context.Writer, value.Value);
		}
	}

	/// <inheritdoc />
	public override TValueObject Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
	{
		if(context.Reader.CurrentBsonType == BsonType.Null)
		{
			context.Reader.ReadNull();
			return null;
		}

		TValue value = BsonSerializer.Deserialize<TValue>(context.Reader);
		object instance = Activator.CreateInstance(args.NominalType, BindingFlags.Public | BindingFlags.Instance, null, [value], null);
		return (TValueObject)instance;
	}

	/// <inheritdoc />
	public bool TryGetMemberSerializationInfo(string memberName, out BsonSerializationInfo serializationInfo)
	{
		if(memberName == "Value")
		{
			IBsonSerializer<TValue> serializer = BsonSerializer.LookupSerializer<TValue>();
			serializationInfo = new BsonSerializationInfo(memberName, serializer, typeof(TValue));
			return true;
		}

		serializationInfo = null;
		return false;
	}
}

In the example BsonSerializer.LookupSerializer<TValue>() correctly gets the Int32Serializer instance for the TValue type (in this case int).

I tried the the following query types:

Person stringFilterResult = await collection
   .Find(Builders<Person>.Filter.Lt("Age.Value", 40))
   .FirstOrDefaultAsync();

Person expressionFilterResult = await collection
	.Find(Builders<Person>.Filter.Lt(p => p.Age.Value, 40))
	.FirstOrDefaultAsync();

Person linqFilterResult = await collection
	.AsQueryable()
	.Where(x => x.Age.Value < 40)
	.FirstOrDefaultAsync();

Every query lust return null. Not error, no exception.

When I use the stringly-typed filter without the navigation to the Value property, I get the correct instance back:

Person stringFilterResult = await collection
   .Find(Builders<Person>.Filter.Lt("Age", 40))
   .FirstOrDefaultAsync();

What am I missing here? How can I fix this?

Cheers,
Matthias

Hi @MadEyeMatt,

Thanks for raising this question. This specific use case is unfortunately not supported by the Driver yet. We only support nullableField.Value. I’ve created a ticket CSHARP-5161 to track this work. Please feel free to comment there.

Thanks,

Rishit.