priehl
priehl

Reputation: 664

BsonClassMap for Types Containing Generics

We are using Bson to serialize/deserialize on either side of our RabbitMq Rpc client server calls. We have a implemented our SimpleRpcClient/Server as suggested here:

https://www.rabbitmq.com/releases/rabbitmq-dotnet-client/v3.1.5/rabbitmq-dotnet-client-3.1.5-client-htmldoc/html/type-RabbitMQ.Client.MessagePatterns.SimpleRpcServer.html

However, our Subscription object is implemented as so:

public class SubscriberRequest<TKey> : SubscriberRequest
{
    public TKey[] Keys { get; set; }

    public SubscriberRequest(){}

    public SubscriberRequest(IEnumerable<TKey> keys) : base()
    {
        this.Keys = keys.ToArray();
        this.Hostname = Dns.GetHostName();
    }
}

public class SubscriberRequest
{
    public static SubscriberRequest Default = new SubscriberRequest() { Hostname = Dns.GetHostName() };

    public string Hostname { get; set; }
}

Allowing us to send request response objects back and forth in a typed manor. This seems to work well, when our serializer is only responsible for dealing with one type of "Request" objects.

BsonClassMap.LookupClassMap(typeof(SubscriberRequest<MessageType1Request>));

However, when we try to use the serializer for multiple types of request objects, i cannot seem to set up the ClassMap enough to satisfy the deserializer.

BsonClassMap.LookupClassMap(typeof(SubscriberRequest<MessageType1Request>));
BsonClassMap.LookupClassMap(typeof(SubscriberRequest<MessageType2Request>));

I consistently get a MongoDB.Bson.BsonSerializationException: Ambiguous discriminator 'SubscriberRequest`1'

I have tried to explicitly tell the BsonClassMap how to handle this like so:

        BsonClassMap.RegisterClassMap<SubscriberRequest<MessageType1Request>>(cm =>
            {
                cm.MapCreator(p => new SubscriberRequest<MessageType1Request>(p.Keys));
            }); 

with no avail.

How can i properly satisfy the discriminator?

Upvotes: 3

Views: 3677

Answers (3)

SJFJ
SJFJ

Reputation: 667

I found this and made a minor modification MongoDB C# driver type discriminators with generic class inheriting from non-generic base class

Setting the disciminator as suggested by @Bruce Nielsen is not very flexible

public class DiscriminatorConvention<T> : IDiscriminatorConvention
{
    public string ElementName => "_t";

    public Type GetActualType(IBsonReader bsonReader, Type nominalType)
    {
        if (!typeof(T).IsAssignableFrom(nominalType))
                throw new Exception($"Cannot use DiscriminatorConvention<{typeof(T).Name}> for type " + nominalType);

        var ret = nominalType;

        var bookmark = bsonReader.GetBookmark();
        bsonReader.ReadStartDocument();
        if (bsonReader.FindElement(ElementName))
        {
            var value = bsonReader.ReadString();
            ret = Type.GetType(value);

            if (ret == null)
                throw new Exception("Could not find type from " + value);

            if (!typeof(T).IsAssignableFrom(ret) && !ret.IsSubclassOf(typeof(T)))
                throw new Exception("type is not an IRestriction");
        }

        bsonReader.ReturnToBookmark(bookmark);

        return ret;
    }

    public BsonValue GetDiscriminator(Type nominalType, Type actualType)
    {
        if (nominalType != typeof(Setting))
            throw new Exception($"Cannot use {GetType().Name} for type " + nominalType);

        return actualType.AssemblyQualifiedName; // ok to use since assembly version is ignored when deserialized
    }
}

Upvotes: 1

Bruce Nielsen
Bruce Nielsen

Reputation: 1758

Just ran into this myself.

The solution is to override the discriminator for each generic type, so when serialized they get unique values.

For your case, something like this should work:

BsonClassMap.RegisterClassMap<SubscriberRequest<MessageType1Request>>(cm =>
{
    cm.AutoMap();
    cm.SetDiscriminator("SubscriberRequest`MessageType1Request");
};
BsonClassMap.RegisterClassMap<SubscriberRequest<MessageType2Request>>(cm =>
{
    cm.AutoMap();
    cm.SetDiscriminator("SubscriberRequest`MessageType2Request");
};

Upvotes: 3

ajaybee
ajaybee

Reputation: 146

I think you need something like this: Serialize Documents with the C# Driver - Specifying Known Types

Basically, you need to map all of your known types in order for the discriminator to determine what you want.

BsonClassMap.RegisterClassMap<SubscriberRequest<MessageType1Request>>();
BsonClassMap.RegisterClassMap<SubscriberRequest<MessageType2Request>>();

Upvotes: 0

Related Questions