[CSHARP-1253] Cannot deserialize inherited members with aggregation queries (fluent API) Created: 22/Apr/15  Updated: 09/Nov/22  Resolved: 19/May/15

Status: Closed
Project: C# Driver
Component/s: Serialization
Affects Version/s: 2.0
Fix Version/s: 2.0.1, 2.1

Type: Bug Priority: Major - P3
Reporter: Dmitry Kuznetsov [X] Assignee: Craig Wilson
Resolution: Done Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified
Environment:

Windows 8.1 x64, locally hosted mongodb, VS 2013, MSTest


Issue Links:
Duplicate
is duplicated by CSHARP-1275 Projection by expression where member... Closed
Related
is related to CSHARP-1534 Add a new constructor that take "base... Closed

 Description   

Cannot deserialize inherited members with aggregation queries (fluent API)

Given class hierarchy:

    public class Aggregate : Entity
    {
        public string Id { get; set; }
    }
 
    public class Entity
    {
        public IEnumerable<LocalizableText> Name { get; set; }
    }
 
    public class Branch : Aggregate
    {
        public IEnumerable<LocalizableText> Description { get; set; }
        public string PartnerId { get; set; }
        public double Latitude { get; set; }
        public double Longitude { get; set; }
        public string Timetable { get; set; }
        public IEnumerable<Discount> Discounts { get; set; }
        public IEnumerable<Category> Categories { get; set; }
        public IEnumerable<Phone> Phones { get; set; }
        public byte[] Icon { get; set; }
        public byte[] Image { get; set; }
    }

Given mappings:

 
            BsonClassMap.RegisterClassMap<Entity>(cm =>
            {
                cm.SetIsRootClass(true);
                cm.MapProperty(e => e.Name);
                cm.SetDiscriminator(typeof(Entity).Name);
                cm.AddKnownType(typeof(Aggregate));
                cm.AddKnownType(typeof(Branch));
                
            });
            BsonClassMap.RegisterClassMap<Aggregate>(cm =>
            {
                cm.SetIsRootClass(true);
                cm.SetDiscriminator(typeof(Aggregate).Name);
                cm.SetIdMember(cm.GetMemberMap(a => a.Id));
                cm.AddKnownType(typeof(Branch));
            });
           BsonClassMap.RegisterClassMap<Branch>(cm =>
            {
                cm.AutoMap();
                cm.SetDiscriminator(typeof(Branch).Name);
            });

When running the query:

        public async Task<Branch> GetBranchToolTipAsync(string partnerId, string branchId)
        {
            return await Collection.Aggregate()
                .Match(x => x.PartnerId == partnerId)
                .Group(x => x.PartnerId, g => new Branch()
                {
                    PartnerId = g.Key,
                    Name = g.First(x => x.PartnerId == partnerId).Name,
                    Description = g.First(x => x.Id == branchId).Name,
                    Discounts = g.First(x => x.Id == branchId).Discounts,
                    Id = branchId
                }).FirstOrDefaultAsync();
        }

Then the following exception occurs:

Test method Server.Application.Tests.Services.Partners.PartnerApplicationServiceTests.ShouldGetBranchToolTipAsync threw exception: 
 
System.ArgumentOutOfRangeException: The memberInfo argument must be for class Branch, but was for class Entity.
 
Parameter name: memberInfo
    at MongoDB.Bson.Serialization.BsonClassMap.EnsureMemberInfoIsForThisClass(MemberInfo memberInfo)
   at MongoDB.Bson.Serialization.BsonClassMap.MapMember(MemberInfo memberInfo)
   at MongoDB.Driver.Linq.Translators.AggregateProjectionTranslator.SerializerBuilder.BuildProjectedSerializer(ProjectionMapping mapping)
   at MongoDB.Driver.Linq.Translators.AggregateProjectionTranslator.SerializerBuilder.BuildMemberInit(MemberInitExpression node)
   at MongoDB.Driver.Linq.Translators.AggregateProjectionTranslator.SerializerBuilder.Build(Expression node)
   at MongoDB.Driver.Linq.Translators.AggregateProjectionTranslator.SerializerBuilder.Build(Expression node, IBsonSerializerRegistry serializerRegistry)
   at MongoDB.Driver.Linq.Translators.AggregateProjectionTranslator.TranslateGroup(Expression`1 idProjector, Expression`1 groupProjector, IBsonSerializer`1 parameterSerializer, IBsonSerializerRegistry serializerRegistry)
   at MongoDB.Driver.IAggregateFluentExtensions.GroupExpressionProjection`3.Render(IBsonSerializer`1 documentSerializer, IBsonSerializerRegistry serializerRegistry)
   at MongoDB.Driver.AggregateFluent`2.<>c__DisplayClass1`1.<Group>b__0(IBsonSerializer`1 s, IBsonSerializerRegistry sr)
   at MongoDB.Driver.DelegatedPipelineStageDefinition`2.Render(IBsonSerializer`1 inputSerializer, IBsonSerializerRegistry serializerRegistry)
   at MongoDB.Driver.PipelineStageDefinition`2.MongoDB.Driver.IPipelineStageDefinition.Render(IBsonSerializer inputSerializer, IBsonSerializerRegistry serializerRegistry)
   at MongoDB.Driver.PipelineStagePipelineDefinition`2.Render(IBsonSerializer`1 inputSerializer, IBsonSerializerRegistry serializerRegistry)
   at MongoDB.Driver.MongoCollectionImpl`1.<AggregateAsync>d__7`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at MongoDB.Driver.IAggregateFluentExtensions.<FirstOrDefaultAsync>d__b`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at SCNDISC.Server.Infrastructure.Persistence.Queries.Partners.BranchToolTipQuery.<GetBranchToolTipAsync>d__2.MoveNext() in BranchToolTipQuery.cs: line 16
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at SCNDISC.Server.Application.Services.Partners.PartnerApplicationService.<GetBranchToolTipAsync>d__9.MoveNext() in PartnerApplicationService.cs: line 46
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at SCNDISC.Server.Application.Tests.Services.Partners.PartnerApplicationServiceTests.<ShouldGetBranchToolTipAsync>d__35.MoveNext() in PartnerApplicationServiceTests.cs: line 87
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()



 Comments   
Comment by Craig Wilson [ 19/May/15 ]

Author:

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

Message: CSHARP:1253: Fixed issue with projecting into a derived class.
Conflicts:
src/MongoDB.Driver/Linq/Processors/SerializerBuilder.cs
Branch: master
https://github.com/mongodb/mongo-csharp-driver/commit/16f12a9b4746ac2d1a9311cf43dce722e5539189

Comment by Craig Wilson [ 19/May/15 ]

Author:

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

Message: CSHARP:1253: Fixed issue with projecting into a derived class.
Branch: master
https://github.com/mongodb/mongo-csharp-driver/commit/1a72bf8a87f9ec8b3c557df73e62c7b54432bf92

Comment by Craig Wilson [ 27/Apr/15 ]

Hi Dmitry,

Let's try and keep only 1 issue per ticket, as these problems are completely unrelated. In this case, as I mentioned before, you can't use a lambda expression in the First method. MongoDB doesn't have support for that. An exception should be throw (CSHARP-1257). In addition, when you do a sort prior to a group, the sort is lost, so there isn't a reason to do that.

I would advise you to, in this case, first compose your aggregation pipeline in the shell and prove that it's working, and then come back and do it in .NET.

Craig

Comment by Dmitry Kuznetsov [X] [ 27/Apr/15 ]

The other problem I've noticed with the grouping. Take a look at this query:

            var sortBuilder = new SortDefinitionBuilder<Branch>().Ascending(b => b.Id).Ascending(b => b.PartnerId);
            var groups = await Collection.Aggregate().Sort(sortBuilder).Group(x => x.PartnerId, g => new
            { 
                                                                                           PartnerId = g.Key,
                                                                                           g.First(x => x.Id == x.PartnerId).Name,
                                                                                           g.First(x => x.Id == x.PartnerId).Description,
                                                                                           g.First(x => x.Id == x.PartnerId).Icon,
                                                                                           g.First(x => x.Id == x.PartnerId).Image,
                                                                                           g.First(x => x.Id == x.PartnerId).Phones,
                                                                                           g.First(x => x.Id == x.PartnerId).Timetable,
                                                                                           g.First(x => x.Id == x.PartnerId).Categories,
                                                                                           Discounts = g.Select(x => x.Discounts)
                                                                                       }).ToListAsync();

This will produce wrong results if you omit the Sort. I was under the impression that Sort is not required prior for the Group.

Comment by Dmitry Kuznetsov [X] [ 27/Apr/15 ]

I have the following query that grabs some fields from Branch but also gets Name property from Branches Partner

        public async Task<Branch> GetBranchToolTipAsync(string partnerId, string branchId)
        {
            var res = await Collection.Find(b => b.PartnerId == partnerId).ToListAsync();
            return res.GroupBy(b => b.PartnerId).Select(g => new Branch()
            {
                PartnerId = g.Key,
                Name = g.First(x => x.PartnerId == partnerId).Name,
                Description = g.First(x => x.Id == branchId).Name,
                Discounts = g.First(x => x.Id == branchId).Discounts,
                Id = branchId
            }).FirstOrDefault();
        }

What I would want to do - do the groupping and projection in mongo and not in linq-to-objects but I cant due to the error:

System.ArgumentOutOfRangeException: The memberInfo argument must be for class Branch, but was for class Entity.

Comment by Dmitry Kuznetsov [X] [ 27/Apr/15 ]

Craig,

To understand the enitiy logic: I have one collection that store only one type of document - Branch. But on the business side there are two kinds of them - Partner and its Branches. This relation is being tracked by the additional (foreign like) key PartnerId. For Partner PartnerId== Id, for Branch, PartnerId specifies its parent Partner. The hierarchy is only one level deep.

Comment by Dmitry Kuznetsov [X] [ 25/Apr/15 ]

Hi Craig.

I need to retrieve aggregated info from 2 objects in the collection, therefore 2 keys - partnerId and branchId. They all belong to the group by x.PartnerId == partnerId.

I will have more input when I'll get to office on Monday. I will show workarounds that make this work and also another issue with groupping.

PS. Exception message points to serialization problems, not query problems.

Comment by Craig Wilson [ 24/Apr/15 ]

I actually don't understand the query you are wanting to run. It seems like this pipeline could simply be run like this.

public async Task<Branch> GetBranchToolTipAsync(string partnerId, string branchId)
        {
            return await Collection.Aggregate()
                .Match(x => x.PartnerId == partnerId && x.Id == branchId).
                .FirstOrDefaultAsync();
        }

I don't know what generic type collection is using, but this certainly seems like it solves your problem. However, I do think we have an issue using inherited members anyways.

Comment by Craig Wilson [ 24/Apr/15 ]

Hi Dmitry,

I think the problem in this case is that we simply can't perform the query you are wanting. Specifically, the predicates in the First method calls inside the grouping can't be done. I might be wrong, but I can't figure out how I would write this pipeline inside the shell.

The exception message here certainly isn't helpful, so, at the very least, we can get that cleaned up. If you can figure out how to write this in the shell, then we might be able to support this.

Craig

Comment by A. Jesse Jiryu Davis [ 22/Apr/15 ]

C# issue, barrie could you please move it? Thanks.

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