[CSHARP-1521] ElemMatch requires IBsonArraySerializer for DictionaryInterfaceImplementerSerializer Created: 04/Jan/16  Updated: 03/Mar/20  Resolved: 11/Jan/16

Status: Closed
Project: C# Driver
Component/s: BSON, Operations
Affects Version/s: 2.2
Fix Version/s: 2.3

Type: Bug Priority: Minor - P4
Reporter: Daniel Polistchuck Assignee: Craig Wilson
Resolution: Done Votes: 0
Labels: regression
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Duplicate
Related
related to CSHARP-2991 Regression of CSHARP-1521. ElemMatch ... Closed

 Description   

In a usage scenario that worked perfectly on 1.x, I am getting System.InvalidOperationException : The serializer for field 'EnabledForProduct' must implement IBsonArraySerializer and provide item serialization info.

Where EnabledForProduct is mapped like this:

BsonClassMap.RegisterClassMap<TestClass>(cm =>
            {
                cm.AutoMap();
                cm.MapMember(c => c.EnabledForProduct)
                    .SetSerializer(new DictionaryInterfaceImplementerSerializer<Dictionary<TestProduct, bool>>(
                        DictionaryRepresentation.ArrayOfDocuments, BsonSerializer.LookupSerializer<int>(),
                        BsonSerializer.LookupSerializer<bool>())
                    );
            });

A Full NUnit test that reproduces the error follows:

using System;
using System.Collections.Generic;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Options;
using MongoDB.Bson.Serialization.Serializers;
using MongoDB.Driver;
using MongoDB.Driver.Builders;
using NUnit.Framework;
 
namespace CredMarket.Tests
{
    [TestFixture]
    public class ElemMatchTest
    {
        [Test]
        public void TestElemMatch()
        {
            BsonClassMap.RegisterClassMap<TestClass>(cm =>
            {
                cm.AutoMap();
                cm.MapMember(c => c.EnabledForProduct)
                    .SetSerializer(new DictionaryInterfaceImplementerSerializer<Dictionary<TestProduct, bool>>(
                        DictionaryRepresentation.ArrayOfDocuments, BsonSerializer.LookupSerializer<int>(),
                        BsonSerializer.LookupSerializer<bool>())
                    );
            });
            var client = new MongoClient("mongodb://localhost");
            var database = client.GetDatabase("test");
            var collection = database.GetCollection<TestClass>("testcollection");
            collection.DeleteMany(FilterDefinition<TestClass>.Empty);
 
            //Creating Document
            var doc = new TestClass
            {
                Id = Guid.NewGuid().ToString(),
                EnabledForProduct = new Dictionary<TestProduct, bool>
                {
                    {TestProduct.Product1, true}
                }
            };
 
            collection.InsertOne(doc);
 
            var foundDoc = collection.Find(d=>d.Id == doc.Id).Limit(1).FirstOrDefault();
            AssertDoc(foundDoc);
 
            var filter = Builders<TestClass>.Filter.ElemMatch(d => d.EnabledForProduct,
                k => k.Key == TestProduct.Product1 && k.Value);
            foundDoc = collection.Find(filter).Limit(1).FirstOrDefault();
            AssertDoc(foundDoc);
        }
 
        private static void AssertDoc(TestClass foundDoc)
        {
            Assert.IsNotNull(foundDoc);
            Assert.IsNotNull(foundDoc.EnabledForProduct);
            Assert.IsTrue(foundDoc.EnabledForProduct[TestProduct.Product1]);
        }
    }
 
    public class TestClass
    {
        public string Id { get; set; }
        public Dictionary<TestProduct,bool> EnabledForProduct { get; set; }
    }
 
    public enum TestProduct
    {
        Product1,Product2
    }
}



 Comments   
Comment by Daniel Polistchuck [ 11/Jan/16 ]

I saw the implementation and tests... looking forward for the release of 2.3.0

By the way, this kind of transparency with your customers is fantastic!

Comment by Githook User [ 11/Jan/16 ]

Author:

{u'username': u'craiggwilson', u'name': u'Craig Wilson', u'email': u'craiggwilson@gmail.com'}

Message: CSHARP-1521: added support for querying dictionaries serialized as array of documents.
Branch: master
https://github.com/mongodb/mongo-csharp-driver/commit/8f570e67c7d4a567301c370c62af40681446e340

Comment by Daniel Polistchuck [ 04/Jan/16 ]

I don't know if this is a workaround or it's the right way to do it but I declared a new

var bsonCollection = database.GetCollection<BsonDocument>("testcollection");

And changed the filter to

var filter = Builders<BsonDocument>.Filter.ElemMatch("EnabledForProduct", Builders<BsonDocument>.Filter.And(Builders<BsonDocument>.Filter.Eq("k",(int)TestProduct.Product1),
                Builders<BsonDocument>.Filter.Eq("v",true)));

I then found and deserialized the result like this and the assert passed:

            foundDoc = BsonSerializer.Deserialize<TestClass>(bsonCollection.Find(filter).Limit(1).FirstOrDefault());
            AssertDoc(foundDoc);

Is this the only and/or the correct way to implement ElemMatch using DictionaryRepresentation.ArrayOfDocuments)?

Comment by Daniel Polistchuck [ 04/Jan/16 ]

The 1.x version of the test that passed correctly is here:

using System;
using System.Collections.Generic;
using System.Configuration;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Options;
using MongoDB.Driver;
using MongoDB.Driver.Builders;
using NUnit.Framework;
 
namespace CredMarket.Tests
{
    [TestFixture]
    public class ElemMatchTest
    {
        [Test]
        public void TestElemMatch()
        {
            BsonClassMap.RegisterClassMap<TestClass>(cm =>
            {
                cm.AutoMap();
                cm.MapMember(c => c.EnabledForProduct).SetSerializationOptions(new DictionarySerializationOptions(DictionaryRepresentation.ArrayOfDocuments));
            });
            var client = new MongoClient("mongodb://localhost");
            var server = client.GetServer();
            var database = server.GetDatabase("test");
            var collection = database.GetCollection<TestClass>("testcollection");
            collection.RemoveAll();
 
            //Creating Document
            var doc = new TestClass
            {
                Id = Guid.NewGuid().ToString(),
                EnabledForProduct = new Dictionary<TestProduct, bool>
                {
                    {TestProduct.Product1, true}
                }
            };
 
            collection.Save(doc);
 
            var foundDoc = collection.FindOneById(doc.Id);
            AssertDoc(foundDoc);
 
            var query = Query.ElemMatch("EnabledForProduct",
                Query.And(Query.EQ("k", (int) TestProduct.Product1), Query.EQ("v", true)));
            foundDoc = collection.FindOne(query);
            AssertDoc(foundDoc);
        }
 
        private static void AssertDoc(TestClass foundDoc)
        {
            Assert.IsNotNull(foundDoc);
            Assert.IsNotNull(foundDoc.EnabledForProduct);
            Assert.IsTrue(foundDoc.EnabledForProduct[TestProduct.Product1]);
        }
    }
 
    public class TestClass
    {
        public string Id { get; set; }
        public IDictionary<TestProduct,bool> EnabledForProduct { get; set; }
    }
 
    public enum TestProduct
    {
        Product1,Product2
    }
}

Generated at Wed Feb 07 21:39:51 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.