【问题标题】:MongoDB C# driver type discriminators with generic class inheriting from non-generic base class具有从非泛型基类继承的泛型类的 MongoDB C# 驱动程序类型鉴别器
【发布时间】:2014-09-17 13:04: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"}));

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

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

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

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

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

感谢您的宝贵时间。

【问题讨论】:

    标签: c# .net mongodb generics inheritance


    【解决方案1】:

    经过一些实验,我发现您可以编写自己的鉴别器约定。我真的不明白为什么,但默认的鉴别器约定似乎使用类型类的 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。

    【讨论】:

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

    也许有点晚了,但我们在泛型类型方面遇到了同样的问题,它们被保存为类型名称而不是全名。 我们最初对项目中的所有类型都使用 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);
                }
                );
    

    【讨论】:

      【解决方案3】:

      以上内容对我们进行了一些小的更改。非常感谢您的帮助 DukeOf1Cat!感谢 Jeff Rasmussen 帮助我们解决这个问题!在尝试创建自定义序列化程序、不同的 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;
          }
      }
      

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-10-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-28
        相关资源
        最近更新 更多