[CSHARP-1559] Impossible to create BsonClassMap for Immutable Type that Inherits Created: 10/Feb/16  Updated: 15/Jan/20  Resolved: 15/Jan/20

Status: Closed
Project: C# Driver
Component/s: BSON
Affects Version/s: 2.11.0
Fix Version/s: None

Type: Bug Priority: Minor - P4
Reporter: Forest Johnson Assignee: Unassigned
Resolution: Duplicate Votes: 4
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Duplicate
duplicates CSHARP-2889 BsonClassMap.LookupClassMap supports ... Closed

 Description   

See the function I have that creates a class map for my immutable types below.

I am getting the error

The memberInfo argument must be for class {0}, but was for class {1}

from EnsureMemberInfoIsForThisClass when I try to map an immutable inheriting type. (Employee in my example.) I think that maybe there should be a new function in BsonClassMap which maps a member to a constructor argument. Inside that function, it should use a different version of EnsureMemberInfoIsForThisClass that checks

if (memberInfo.ReflectedType == _classType)

rather than

if (memberInfo.DeclaringType == _classType)

. Unless I am missing something here.

I am working on implementing this on my fork and will submit a PR once done.

        private static void MapClass<T>(BsonClassMap<T> map)
        {
            var type = typeof(T);
            map.AutoMap();
 
            // the SelectConstructor function finds the right constructor which can be used for mapping. 
            var ctor = SelectConstructor(type);
            if (ctor != null)
            {
                map.MapConstructor(ctor.Info)
                    .SetArguments(ctor.Members);
                ctor.Members.Run(memberInfo => {
                    if(memberInfo.ReflectedType != memberInfo.DeclaringType)
                    {
                        // What to do??
                        // Say we have: 
                        // public class Person { 
                        //   public Person(string name) {
                        //     Name = name;
                        //   }
                        //   public string Name { get; } 
                        // } 
 
                        // and 
 
                        // class Employee : Person { 
                        //   public Employee(string name, string title) : base(name) {
                        //     Title = title;
                        //   }
                        //   string Title { get; } 
                        // }
 
                        // then we have BsonClassMap<Employee>.MapConstructor() for the Employee(string, string) ctor.
                        // Then we also have to BsonClassMap<Employee>.MapMember() for Person.Name 
                        // so that it will be passed to the constructor (at least that is my understanding of whats going on)
 
                    } else {
                        map.MapMember(memberInfo);
                    }
                });
            }
 
            var idProperty = type.GetProperties().SingleOrDefault(x => x.IsDefined(typeof(QueryIdAttribute)));
            if (idProperty != null)
                map.MapIdMember(idProperty);
        }



 Comments   
Comment by Aristarkh Zagorodnikov [ 15/Jan/20 ]

Looking forward for this improvement as we were considering forking the driver and maintaining a separate build to fix this.

Comment by Dmitry Lukyanov (Inactive) [ 15/Jan/20 ]

This ticket will be implemented in the scope of CSHARP-2889

Comment by Aristarkh Zagorodnikov [ 19/Dec/19 ]

It would be very beneficial to fix this, since currently modelling using inherited immutable types is next to impossible.

Comment by Robert Stam [ 11/Jan/18 ]

I can reproduce this using the following minimal scenario:

namespace TestCSharp1559
{
    public class B
    {
        private readonly int _x;
        public B(int x)
        {
            _x = x;
        }
        public int X => _x;
    }
 
    public class D : B
    {
        private readonly int _y;
        public D(int x, int y)
            : base(x)
        {
            _y = y;
        }
        public int Y => _y;
    }
 
    public static class Program
    {
        public static void Main(string[] args)
        {
            var d = new D(1, 2);
            var json = d.ToJson();
            var r = BsonSerializer.Deserialize<D>(json);
        }
    }
}

Comment by Forest Johnson [ 10/Feb/16 ]

Hey yall. I implemented this on my fork and it works in our app!

https://github.com/forestjohnsonilm/mongo-csharp-driver/commit/f6fd52c138bd09de0db437335e49cca503162a58

For now I have just added a new public method MapMemberSpecificallyForImmutableTypes to BsonClassMap as a quick hacky fix.

Maybe we can instead make that one private, and define a new public method MapConstructorForImmutableType(ConstructorInfo, MemberInfo[]) which in turn calls MapMemberSpecificallyForImmutableTypes for each MemberInfo listed.

Alternatively nothing changes and we just use MapCreator(Delegate @delegate) in our app. Would that work? I don't know. We might end up doing that anyway later on.

Comment by Forest Johnson [ 10/Feb/16 ]

Initially when I posted this I had seriously messed up the markup, but it looks like someone corrected it for me. Thanks. Here is a gist version of the description that I created since I couldn't edit the description:

https://gist.github.com/forestjohnsonilm/2d9c023e5f486198c01e

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