【问题标题】:Deserialize object as an interface with MongoDB C# Driver将对象反序列化为 MongoDB C# 驱动程序的接口
【发布时间】:2013-01-28 12:13:46
【问题描述】:

我正在开发一个使用 MongoDB(带有 C# 驱动程序)和 DDD 的项目。

我有一个类 (aggregate),它有一个类型是接口的属性。在另一个类中,我实现了这个接口。这个类有另一个属性,它的类型是一个接口,并且是用另一个实现的类来设置的。

下面的代码解释得更好:

// Interfaces
public interface IUser {
    Guid Id { get; set;}
    IPartner Partner{ get; set; }
}

public interface IPartner {
    IPhone Mobile { get; set; }
}

public interface IPhone {
    string number { get; set; }
}

// Implemented Classes
public class User: IUser {
    [BsonId(IdGenerator = typeof(GuidGenerator))]
    public Guid Id { get; set; }

    [BsonIgnoreIfNull]
    public IPartner Partner { get; set; }
}

public struct Partner : IPartner {
    public IPhone Mobile { get; set; }
}

public struct Phone : IPhone {
    public string Number { get; set; }
}

好吧,当我调用MongoCollection<User>.Insert() 方法时,它会抛出两个异常:

System.IO.FileFormatException:反序列化时出错 .User 类的 Partner 属性:错误 反序列化类的电话属性时发生 .Partner:价值类 .Mobile 无法反序列化。 ---> System.IO.FileFormatException:反序列化时出错 .Partner 类的 Mobile 属性:值 class .Phone 无法反序列化。 ---> MongoDB.Bson.BsonSerializationException:值类 .Phone 无法反序列化。

然后,我在互联网上搜索了如何将类型反序列化为接口,我想我必须有办法做到这一点:使用强制转换映射属性,使用 BsonClassMap.RegisterClassMap 或编写自定义 BSON 序列化程序。

我需要知道这两种方式中哪一种更好以及如何实施。

注意:我需要一个不修改接口的解决方案,因为他们的项目不能包含任何外部引用。

【问题讨论】:

  • 我会尝试他们两个,看看哪个更容易。显然,编写自定义序列化程序会很困难。我们正在研究如何更好地反序列化这种类型的结构。
  • 再次感谢您,克雷格。在尝试自定义序列化程序之前,我将尝试注册类映射。那么,驱动程序没有这个功能来反序列化到接口吗?如果计划好了,能否给我一个来自 MongoDB Jira 的链接,例如 LinQ 投影?
  • 非常感谢。我现在正在投票并关注这个问题。

标签: c# mongodb mongodb-.net-driver


【解决方案1】:

好吧,我在尝试得到这个答案时发现了很多问题。

首先,MongoDB C# 驱动程序确实存在一些问题在反序列化接口时,就像 Craig Wilson 在这个问题 cmets 中所说的那样,正如 the issue page 中所述。

正如我之前所说,这个问题的安全实现可能是自定义 BSON 序列化器或特定类映射,使用 BsonClassMap.RegisterClassMap

所以,我已经实现了类映射,问题仍然存在。

期待这个问题,我发现这个异常与驱动程序的另一个问题有关:反序列化structs时的问题

我已将项目回滚到初始状态(没有类映射或自定义序列化程序)并将结构类型更改为类类型,它工作了

在简历中,此异常错误与结构反序列化有关,与接口反序列化无关。


无论如何,这是一个真正的问题,第二个问题需要被视为一个错误而不是改进,就像第一个问题一样。

您可以在这些链接中找到问题:

【讨论】:

【解决方案2】:

[BsonSerializer(typeof(ImpliedImplementationInterfaceSerializer<IReviewExpert, ReviewExpert>))] public IReviewExpert Expert { get; set; }

为我工作

【讨论】:

  • 非常感谢,这是我遇到的接口问题的解决方案。像魅力一样工作。
  • 谁是救星?他是.. 谢谢!
【解决方案3】:

我们在 mongo 驱动程序的 1.x 分支上,遗憾的是没有罗伯特贝克建议的ImpliedImplementationInterfaceSerializer,这似乎是一个很好的解决方案。为此,我创建了自己的序列化程序,允许您为接口成员指定具体类型。

public class ConcreteTypeSerializer<TInterface, TImplementation> : BsonBaseSerializer where TImplementation : TInterface
{
    private readonly Lazy<IBsonSerializer> _lazyImplementationSerializer;

    public ConcreteTypeSerializer()
    {
        var serializer = BsonSerializer.LookupSerializer(typeof(TImplementation));

        _lazyImplementationSerializer = new Lazy<IBsonSerializer>(() => serializer);
    }

    public override object Deserialize(BsonReader bsonReader, Type nominalType, Type actualType, IBsonSerializationOptions options)
    {
        if (bsonReader.GetCurrentBsonType() == BsonType.Null)
        {
            bsonReader.ReadNull();
            return default(TInterface);
        }
        else
        {
            return _lazyImplementationSerializer.Value.Deserialize(bsonReader, nominalType, typeof(TImplementation), options);
        }
    }

    public override void Serialize(BsonWriter bsonWriter, Type nominalType, object value, IBsonSerializationOptions options)
    {
        if (value == null)
        {
            bsonWriter.WriteNull();
        }
        else
        {
            var actualType = value.GetType();
            if (actualType == typeof(TImplementation))
            {
                _lazyImplementationSerializer.Value.Serialize(bsonWriter, nominalType, (TImplementation)value, options);
            }
            else
            {
                var serializer = BsonSerializer.LookupSerializer(actualType);
                serializer.Serialize(bsonWriter, nominalType, value, options);
            }
        }
    }
}

用法如下:

[BsonSerializer(typeof(ConcreteTypeSerializer<IMyInterface,MyClass>))]
public IMyInterface MyProperty {get; set;}

关于代码的一些注释 - 它真正做的只是延迟加载适当具体类型的序列化程序,然后将所有序列化/反序列化调用传递给适当的具体类型而不是接口。

它还检查该类型实际上是预期的类型,如果不是,则只找到该类型的默认序列化程序。

【讨论】:

    【解决方案4】:

    注册一个具体的序列化程序对我不起作用,因为我想要存储的对象包括一个具有 6 个不同实现的接口列表。使用 CosmosDB 可以很好地存储一些 Newtonsoft 设置。它将实现的类型序列化到对象中,并将其命名为$type。所以,我想我会做同样的事情来让它在 MongoDB 中工作。

    public class BsonTypeSerializer<T> : IBsonSerializer<T>
    {
        public Type ValueType { get => typeof(T); }
    
        public T Deserialize(
            BsonDeserializationContext context, 
            BsonDeserializationArgs args)
        {
            var document = BsonSerializer.Deserialize<BsonDocument>(context.Reader);
            var typeStr = document.GetValue("$type").AsString;
            var type = Type.GetType(typeStr);
            var result = (T) BsonSerializer.Deserialize(document, type);
            return result;
        }
    
        public void Serialize(
            BsonSerializationContext context, 
            BsonSerializationArgs args,
            T value)
        {
            var typeStr = value.GetType().FullName;
            BsonDocument document = value.ToBsonDocument();
            document.Add(new BsonElement("$type", BsonValue.Create(typeStr)));
            BsonSerializer.Serialize(context.Writer, typeof(BsonDocument), document);
        }
    
        public void Serialize(
            BsonSerializationContext context,
            BsonSerializationArgs args,
            object value)
            => Serialize(context, args, (T) value);
    
        object IBsonSerializer.Deserialize(
            BsonDeserializationContext context,
            BsonDeserializationArgs args)
            => Deserialize(context, args);
    }
    

    注册时,您可以将其作为T 的界面。具体类型将存储在 Document 中。

    但是,如果您重构类名甚至它们所在的命名空间,这将中断。因此,像这样存储所有内容不是一个好主意。我已经将它用于一次性配置文件。

    如果您正在为“真实数据”设计数据模型,这是个坏主意。您正在将数据与编写它的代码完全绑定。从您以后可能创建的处理相同数据的其他项目中读取数据会变得不必要地复杂。

    【讨论】:

    • 您实际上在此处对文档进行了两次反序列化。可能应该使用 context.Reader 导航到 $type 并查看 $type 的值,而不是反序列化整个文档以获取 $type 的值
    • 这可能不是最高效的代码,但我什至没有在意让它变得高效,它只运行一次,然后对象就永远保存在内存中。它包含现实世界的测量结果,我们必须在将其部署给客户之前手动测量和输入。之后我们需要它来计算几乎所有其他内容。在我的情况下,优化这是非常不受欢迎的工作。没有人关心您可以在这里节省的几毫秒启动时间。在你的情况下,它可能会有所不同。但肯定有人在创建所述配置文件时有点费力。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-09
    • 1970-01-01
    相关资源
    最近更新 更多