[CSHARP-2149] A class inside an embedded array is not being serialized when Projection.ElemMatch is used Created: 14/Jan/18  Updated: 27/Oct/23  Resolved: 11/Feb/19

Status: Closed
Project: C# Driver
Component/s: Read Operations
Affects Version/s: 2.4.4, 2.5
Fix Version/s: None

Type: Bug Priority: Critical - P2
Reporter: Itzhak Kagan Assignee: Wan Bachtiar
Resolution: Works as Designed Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified
Environment:

windows 7sp1, visual studio 2017, driver 2.4.4, 2.5.0, server 3.4.10


Attachments: PNG File account-member-result.png    

 Description   

A class inside an embedded array is not being serialized when Projection.ElemMatch is used

I have classes like this:

	internal class AccountDal
    {
        public Guid Id { get; set; }
		// more properties 
		
        public List<AccountMemberDal> Members { get; set; }
    }
 
	internal class AccountMemberDal
    {
        public RegisteredEntityDal Member { get; set; }
        public EAccountPermissions Permissions { get; set; } // some enum
        public bool IsAdmin { get; set; }
        public bool IsDefaultAccount { get; set; }
    }
	
	public class RegisteredEntityDal
    {
        public Guid Id { get; set; }
        public EEntityTypeDal Type { get; set; } // some enum
	}

I constructed a query:

FilterDefinition<AccountDal> filter = Builders<AccountDal>.Filter.And(
                Builders<AccountDal>.Filter.Eq(a => a.IsBusiness, true),
                Builders<AccountDal>.Filter.ElemMatch(a => a.Members, Builders<AccountMemberDal>.Filter
                .And(
                    Builders<AccountMemberDal>.Filter.Eq(a => a.IsAdmin, true),
                    Builders<AccountMemberDal>.Filter.Eq(a => a.IsDefaultAccount, false)
                    ))
                );
 
	ProjectionDefinition<AccountDal, AccountMemberDal> project = Builders<AccountDal>
        .Projection.ElemMatch(
                    a => a.Members, Builders<AccountMemberDal>.Filter.And(
                    Builders<AccountMemberDal>.Filter.Eq(a => a.IsAdmin, true),
                    Builders<AccountMemberDal>.Filter.Eq(a => a.IsDefaultAccount, false)
                    ));
// or
// ProjectionDefinition<AccountDal, AccountMemberDal> project = Builders<AccountDal>
// .Projection.Include(a => a.Members[-1]);
// which one is recommendes?
	
//and execute the query:
IMongoCollection<AccountDal> accounts = DbGeneralContext.Accounts;
var result = await accounts.Find(filter).Project(project).ToListAsync();
 
// the query that is being generated is:
db.accounts.find({ "isBusiness" : true, "members" : { "$elemMatch" : { "isAdmin" : true, "isDefaultAccount" : false } } },{ "members" : { "$elemMatch" : { "isAdmin" : true, "isDefaultAccount" : false } } })

with the following result:

{
        "_id" : BinData(4,"KKExCpz25hGbRAAkmxAf2Q=="),
        "members" : [
                {
                        "member" : {
                                "id" : BinData(4,"Y+gHIJv25hGbRAAkmxAf2Q=="),
                                "type" : 1
                        },
                        "permissions" : 1,
                        "isAdmin" : true,
                        "isDefaultAccount" : false
                }
        ]
}
{
        "_id" : BinData(4,"L/pKS93w5hGfMgAkmxAf2Q=="),
        "members" : [
                {
                        "member" : {
                                "id" : BinData(4,"JAUR4Hbc5hGfyQAkmxAf2Q=="),
                                "type" : 1
                        },
                        "permissions" : 1,
                        "isAdmin" : true,
                        "isDefaultAccount" : false
                }
        ]
}

However the c# object does not serialize the 'Member' property and it remains null
see attached 'account-member-result.png' result

Thanks,
Itzhak



 Comments   
Comment by Wan Bachtiar [ 17/Jun/19 ]

I did not understand this sentence: however AccountMemberDal doesn't have an attribute to deserialise this field into List<AccountMemberDal> Members.

Hi Itzhak,

The syntax for ProjectionDefinition accepts two parameters, TSource as source map and TProjection as the projection map. In your example you specify the projection map type to be AccountMemberDal. However the returning document from the server is still in the shape of AccountDal, having a field Members.

Based on your example class mappings, we can use example documents as below in accounts collection:

{
  "_id": 1,
  "Members": [
    {"Member": { "_id": 100, "Name": "foo" }, "IsAdmin": true, "IsDefaultAccount": false}
  ]
}
{
  "_id": 2,
  "Members": [
    {"Member": { "_id": 102, "Name": "baz" }, "IsAdmin": true, "IsDefaultAccount": false },
    {"Member": { "_id": 103, "Name": "qux" }, "IsAdmin": false, "IsDefaultAccount": false}
  ]
}
{
  "_id": 3,
  "Members": [
    {"Member": { "_id": 104, "Name": "min" }, "IsAdmin": false, "IsDefaultAccount": false },
    {"Member": { "_id": 105, "Name": "oop" }, "IsAdmin": false, "IsDefaultAccount": true }
  ]
}

If you were to run a similar query in mongo shell, it would look something as below:

db.accounts.find({ "Members" : { 
                    "$elemMatch" : { 
                        "IsAdmin" : true, 
                        "IsDefaultAccount" : false 
                    } 
                }},
                { "Members" : { 
                    "$elemMatch" : { 
                        "IsAdmin" : true, 
                        "IsDefaultAccount" : false 
                    } 
                }}
)

The resulting documents would look as below:

{
  "_id": 1,
  "Members": [
    {"Member": {"_id": 100, "Name": "foo" }, "IsAdmin": true, "IsDefaultAccount": false}
  ]
}
{
  "_id": 2,
  "Members": [
    {"Member": {"_id": 102, "Name": "baz" }, "IsAdmin": true, "IsDefaultAccount": false}
  ]
}

As you can see the results contain Members but the mapping AccountDalMembers does not have Members property to deserialise the results.

As you can see from the attachment picture: The result is indeed a list of AccountMemberDal, the only problem is that the Member property is null and is not being populated.

Your example query is looking for IsAdmin=true but the documents shown in Watch have false. In addition, the value Permissions is also None. Perhaps they're just showing default values due to failed deserialisation ?

I want to project only the AccountMemberDal objects, because I want to minimize the traffic from the databse.

Changing the TProjection type of the ProjectionDefinition doesn't change the amount of data traffic from the database. This is just changing the deserialisation class map from AccountMemberDal to AccountDal.

Regards,
Wan.

Comment by Itzhak Kagan [ 12/Feb/19 ]

Hi Wan,

Thanks for your answer.

I know that it is not because of you, that this issue was addressed more than a year after it was opened.
I rely on the c# documentation at most times.

Pay attention that your suggestion changes completely the projection of the query.
I want to project only the  AccountMemberDal objects, because I want to minimize the traffic from the databse.

Your suggestion is to return the entire AccountDal objects which will increase the network traffic.

I did not understand this sentence: however AccountMemberDal doesn't have an attribute to deserialise this field into List<AccountMemberDal> Members.

The {{AccountMemberDal is a class that is used in the }}AccountDal class to define a list

public List<AccountMemberDal> Members { get; set; }

As you can see from the attachment picture: The result is indeed a list of AccountMemberDal, the only problem is that the Member property is null and is not being populated.

Thanks,
Itzhak

Comment by Wan Bachtiar [ 04/Feb/19 ]

However the c# object does not serialize the 'Member' property and it remains null

Hi Itzhak,

I think the problem here is due to the projection from AccountDal to AccountMemberDal. As can be seen from your result example, it contains field called members, however AccountMemberDal doesn't have an attribute to deserialise this field into List<AccountMemberDal> Members.

You could try replacing the projection code to:

ProjectionDefinition<AccountDal, AccountDal> project = Builders<AccountDal>
    .Projection.ElemMatch(
                a => a.Members, Builders<AccountMemberDal>.Filter.And(
                Builders<AccountMemberDal>.Filter.Eq(a => a.IsAdmin, true),
                Builders<AccountMemberDal>.Filter.Eq(a => a.IsDefaultAccount, false)
                ))

Please note that the CSHARP project is for reporting bugs or feature suggestions for the MongoDB .NET/C# driver. If you have any follow-up questions on the use of the driver, please post a question on mongodb-user group with relevant the information.

Regards,
Wan.

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