MongoDB C#驱动程序类型鉴别器,泛型类继承自非泛型基类

本文关键字:继承 泛型 基类 泛型类 驱动程序 类型 鉴别 MongoDB | 更新日期: 2023-09-27 18:22:15

我正试图使用官方的C#驱动程序在mongodb中存储一个从非泛型基类继承的泛型类的对象列表。

我的代码如下:

abstract class MyAbstractClass {}
class MyGenericClass<T>: MyAbstractClass
{
    [BsonRequired]
    public List<T> Values = new List<T>();
    public MyGenericClass(IEnumerable<T> values) 
    {
        Values.AddRange(values);
    }
}
class MyContainerClass
{
    [BsonId]
    public string Id;
    [BsonRequired]
    public List<MyAbstractClass> ValueCollections = new List<MyAbstractClass>();
    public MyContainerClass()
    {
        Id = Guid.NewGuid().ToString();
    }
}

测试时,我创建一个容器对象,并用泛型类的实例填充它,如下所示:

var container = new MyContainerClass();
container.ValueCollections.Add(new MyGenericClass<string>(new[]{"foo","bar","baz"}));

当我将其保存到DB时,添加的文档如下所示:

{
"_id": "c5cf5cd1-843f-4d5d-ba8f-5859ae62fd1d",
"ValueCollections": [
    {
        "_t": "MyGenericClass`1",
        "Values": [
            "foo",
            "bar",
            "baz"
        ]
    }
]
}

类型鉴别器获取的类型为"MyGenericClass'1",而不是"MyGeneicClass'1[System.String]",这意味着它不可能再次对此进行反序列化。

此外,当尝试从DB加载这些对象时,我收到一条错误消息:无法创建抽象类的实例。但是类型鉴别器(如果它是正确的)应该允许驱动程序看到它不应该创建MyAbstractClass类型的对象,而是创建MyGenericClass 类型的对象

所以我的问题是:1.为什么会出现此错误?2.为什么它没有正确地序列化鉴别器?

谢谢你抽出时间。

MongoDB C#驱动程序类型鉴别器,泛型类继承自非泛型基类

经过一些实验,我发现您可以编写自己的鉴别器约定。我真的不明白为什么,但默认的鉴别器约定似乎使用了类型类的Name属性,而不是FullName,这使得它对泛型类毫无用处。

我最终使用了这个代码:

class FooDiscriminatorConvention : IDiscriminatorConvention
{
    public string ElementName
    {
        get { return "_t"; }
    }
    public Type GetActualType(MongoDB.Bson.IO.BsonReader bsonReader, Type nominalType)
    {
        if(nominalType!=typeof(MyAbstractClass))
            throw new Exception("Cannot use FooDiscriminator 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 " + value);
            if(!ret.IsSubclassOf(typeof(MyAbstractClass)))
                throw new Exception("Database type does not inherit from MyAbstractClass.");
        }
        bsonReader.ReturnToBookmark(bookmark);
        return ret;
    }
    public BsonValue GetDiscriminator(Type nominalType, Type actualType)
    {
        if (nominalType != typeof(MyAbstractClass))
            throw new Exception("Cannot use FooDiscriminator for type " + nominalType);
        return actualType.FullName;
    }
}

并将其注册到

BsonSerializer.RegisterDiscriminatorConvention(typeof(MyGenericClass<>), new FooDiscriminatorConvention()); //is this needed?
BsonSerializer.RegisterDiscriminatorConvention(typeof(MyAbstractClass), new FooDiscriminatorConvention());

我还必须使基类非抽象,以避免出现"抽象类的实例不能具体化"的错误。如果能够有一个抽象基类,那就太好了,但由于派生类是泛型的,我不能使用BsonKnownTypes。

也许有点晚了,但我们在泛型类型方面遇到了同样的问题,它们被保存为类型Name,而不是FullName。我们最初对项目中的所有类型使用BsonSerializer.RegisterDiscriminator,甚至使所有类型都是通用的,但经过大量测试和失败,我们最终使用了BsonClassMap

非工作方法。。。

      KnownPayloadTypes
            .Concat(KnownMessageTypes)
            .ConcatOne(typeof(object))
            .ConcatOne(typeof(Message))
            .ForEach(t => BsonSerializer.RegisterDiscriminator(t, t.FullName));

正在工作。。。

    private static readonly IEnumerable<Type> KnownMessageTypes = KnownPayloadTypes
        .Select(t => typeof(Message<>).MakeGenericType(t));
      KnownPayloadTypes
            .Concat(KnownMessageTypes)
            .ConcatOne(typeof(object))
            .ConcatOne(typeof(Message))
            .ForEach(t =>
            {
                var bsonClassMap = new BsonClassMap(t);
                bsonClassMap.AutoMap();
                bsonClassMap.SetDiscriminator(t.FullName);
                BsonClassMap.RegisterClassMap(bsonClassMap);
            }
            );

上面对我们做了一些小改动。非常感谢您对DukeOf1Cat的帮助!感谢杰夫·拉斯穆森帮助我们解决了这个问题!在尝试创建自定义序列化程序、不同的RegisterClassMap lambda等之后,我们终于发现了这一点

我们对上述解决方案的更改如下。

class IRestrictionDiscriminatorConvention : IDiscriminatorConvention
{
    public string ElementName
    {
        get { return "_t"; }
    }
    public Type GetActualType(BsonReader bsonReader, Type nominalType)
    {
        //Edit: added additional check for list
        if (nominalType != typeof(IRestriction) && nominalType != typeof(List<IRestriction>))
            throw new Exception("Cannot use IRestrictionDiscriminatorConvention 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);
            //Edit: doing the checking a little different
            if (!typeof(IRestriction).IsAssignableFrom(ret) && !ret.IsSubclassOf(typeof(IRestriction)))
                throw new Exception("type is not an IRestriction");
        }
        bsonReader.ReturnToBookmark(bookmark);
        return ret;
    }
    public BsonValue GetDiscriminator(Type nominalType, Type actualType)
    {
        if (nominalType != typeof(IRestriction) && nominalType != typeof(List<IRestriction>))
            throw new Exception("Cannot use FooDiscriminator for type " + nominalType);
        /*Edit: had to change this because we were getting Unknown discriminator value 
       'PackNet.Common.Interfaces.RescrictionsAndCapabilities.BasicRestriction`1[[System.String, ... */
        return actualType.AssemblyQualifiedName;
    }
}

我们遇到的问题是:

public class BasicRestriction<T> : IRestriction
{
    public T Value { get; set; }
    public BasicRestriction()
    {
    }
    public BasicRestriction(T value)
    {
        Value = value;
    }
}

包含限制列表的类永远无法反序列化,因为当它到达I限制列表时,每个类的泛型类型都有一个_t鉴别器值"BasicRestriction`1":

public class Carton{ public List<IRestriction> Restrictions { get; set; }}