2

I get following error for property referrals only when I query the object

An error occurred while deserializing the referrals property of class User: Cannot deserialize a List<Nullable<ObjectId>> from BsonType String.

C# class

 [BsonIgnoreExtraElements]
 public class User : MongoEntity
 {
 [BsonDefaultValue(null)]
 public List<ObjectId?> referrals { get; set; }
 }

Querying the documents

 var users = MongoConnectionHandler.GetDB().GetCollection<User>("users");
 var myusers = users.AsQueryable<User>();

As soons as foreach loop iterates this exception occurs

foreach (var item in myusers){}

It doesn't occur for all documents only for some. Is there any way I can ignore the documents causing that error?

1 Answer 1

4

Fortunately, the error message is clear enough for understanding the problem root cause. You have some documents in users collection that were saved with different schema, where referrals was just a string, not an array of ObjectId. And now, when you try to deserialize those objects, you get fair error that String could not be deserialized to List<ObjectId?>.

If currently your database contains just some test data, you could clear the collection and start from the scratch. As far as documents with old schema will not be saved anymore, you will not face the error again.

If you have to preserve existing data, you should manually delete documents with old schema or migrate their data to new schema, so that these documents could be successfully deserialized.

If however you can't delete data with outdated schema, you should implement custom serializer so that reading the data from DB could succeed. Here is a sample serializer that ignores String values during deserialization:

public class FixingReferralsSerializer : EnumerableSerializerBase<List<ObjectId?>>
{
    public override List<ObjectId?> Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
    {
        if (context.Reader.CurrentBsonType == BsonType.String)
        {
            context.Reader.ReadString();
            return null;
        }

        return base.Deserialize(context, args);
    }

    protected override void AddItem(object accumulator, object item)
    {
        ((List<ObjectId?>)accumulator).Add((ObjectId?)item);
    }

    protected override object CreateAccumulator()
    {
        return new List<ObjectId?>();
    }

    protected override IEnumerable EnumerateItemsInSerializationOrder(List<ObjectId?> value)
    {
        return value;
    }

    protected override List<ObjectId?> FinalizeResult(object accumulator)
    {
        return (List<ObjectId?>)accumulator;
    }
}

You could set this serializer for referrals field through BsonSerializer attribute:

[BsonIgnoreExtraElements]
public class User : Document
{
    [BsonDefaultValue(null)]
    [BsonSerializer(typeof(FixingReferralsSerializer))]
    public List<ObjectId?> referrals { get; set; }
}

Serializer class would be much simpler if you could change the type of referrals field from List<ObjectId?> to Array<ObjectId?>. In this case you could base serializer implementation on ArraySerializer<T>:

public class FixingReferralsSerializer : ArraySerializer<ObjectId?>
{
    public override ObjectId?[] Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
    {
        if (context.Reader.CurrentBsonType == BsonType.String)
        {
            context.Reader.ReadString();
            return null;
        }

        return base.Deserialize(context, args);
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

I 'll have to manually delete the data but its production db so I am not certain if I can do that.Isn't there a way to ignore such documents with srting type before deserialization phase?
Luckily there was only one document where referrals was BSONType string. Here is the query I used to find that db.users.find( { "referrals" : { $type : 2 } } );
I've almost finished updating my answer with custom serializer when you posted last comment. I'll leave it in my answer, may be you or someone else will find it useful.

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.