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

Duk*_*Cat 6 .net c# generics inheritance mongodb

我正在尝试使用官方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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

{
"_id": "c5cf5cd1-843f-4d5d-ba8f-5859ae62fd1d",
"ValueCollections": [
    {
        "_t": "MyGenericClass`1",
        "Values": [
            "foo",
            "bar",
            "baz"
        ]
    }
]
}
Run Code Online (Sandbox Code Playgroud)

类型鉴别器获取类型"MyGenericClass'1"而不是"MyGenericClass'1 [System.String]",这意味着它不能再次反序列化.

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

所以我的问题是:1.为什么我会收到此错误?2.为什么不正确地序列化鉴别器?

感谢您的时间.

Duk*_*Cat 8

经过一些实验,我发现你可以编写自己的鉴别器约定.我无法理解为什么,但默认的鉴别器约定似乎使用类型类的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;
    }
}
Run Code Online (Sandbox Code Playgroud)

并注册

BsonSerializer.RegisterDiscriminatorConvention(typeof(MyGenericClass<>), new FooDiscriminatorConvention()); //is this needed?
BsonSerializer.RegisterDiscriminatorConvention(typeof(MyAbstractClass), new FooDiscriminatorConvention());
Run Code Online (Sandbox Code Playgroud)

我还必须使基类非抽象,以避免"无法克里特实例的抽象类"错误.能够拥有一个抽象基类会很好,但由于派生类是通用的,我不能使用BsonKnownTypes.

  • 对于您最后一个代码块中的问题是否需要这样做,答案是否定的。只需要基类。在您的第一次注册中,您已经放置了泛型类型定义。这实际上永远不会触发,因为没有对象按原样继承该类型。它可以继承从此类型定义生成的泛型类型。 (2认同)
  • 我必须增强这个条件: `if(nominalType!=typeof(MyAbstractClass))` 与此: `if(nominalType!=typeof(MyAbstractClass) &amp;&amp; !nominalType.IsSubclassOf(typeof(MyAbstractClass)))` 才能使其工作。希望一切顺利:-) (2认同)

xis*_*ket 5

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

非工作方法...

      KnownPayloadTypes
            .Concat(KnownMessageTypes)
            .ConcatOne(typeof(object))
            .ConcatOne(typeof(Message))
            .ForEach(t => BsonSerializer.RegisterDiscriminator(t, t.FullName));
Run Code Online (Sandbox Code Playgroud)

在职的...

    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);
            }
            );
Run Code Online (Sandbox Code Playgroud)