【问题标题】:Protobuf-net pattern for optionally assignable nullable types (not collections)Protobuf-net 模式,用于可选可分配的可空类型(不是集合)
【发布时间】:2020-06-16 14:55:55
【问题描述】:

我已经搜索了一段时间,并找到了表明 protobuf-net 可以处理可为空类型的各种线程、关于诸如空集合之类的各种线程(此处不相关)以及以前的默认值行为在proto2中一直是“可选”字段,但我还没有找到以下问题的具体答案。这将是我第一次使用 protobuf-net 或协议缓冲区作为序列化格式。

想象一下,我正在多个不同的应用程序之间使用事件承载状态传输模式发送消息 - 因此消息的发布者和订阅者包含状态 - 以及该状态的某种共享模型。任何参与的应用程序都有一些“相同”实体的表示(因此是 ECST),但并非所有系统都理解所有属性。就应用程序的 SQL 数据库中的持久性而言,可能如下所示(为简洁起见,省略了比例):

table App1Products { productKey int, productName varchar null }

table App2Products { productKey int, productName varchar null, productWeightKg decimal null }

table App3Products { productKey int }

为了这个例子,假设普通共享模型是所有不同属性的联合:{ productKey, productName, productWeightKg }

现在假设有人在App1Products 中更新了productName。我们想发布改变的状态。当我们这样做时,我们无法填充整个共享模型,因为 App1 在其架构中不包含 productWeightKg。我们需要以某种方式“忽略”这个元素的任何值,让潜在消费者明白它没有被填充。

我们不能仅仅发送一个默认值 (0)(或让订阅者将缺失的元素反序列化为默认值)来传达“不更新”语义,因为这会导致 productWeightKg 值设置为 0当 App2 收到消息时,在App2Products 表中。我们不能发送 null 来传达“不更新”语义,因为 null 也是该列的合法值。

最终,我们需要 App2 的订阅者代码来构造一个更新语句,这样要么不引用 productWeightKg 列,要么只是将其设置为自身,我们需要某种方式告诉 App2 的订阅者代码来执行此操作.

一种解决方案似乎是在消息中为每个字段创建一个附加元素,指示该字段是否已设置。在消息内容方面,我们可能会这样使用:

[ProtoContract]
public class Product
{
    [ProtoMember(1)]
    public int ProductKey { get; set; }

    [ProtoMember(2)]
    public string productName { get; set; }

    [ProtoMember(3)]
    public decimal? productWeightKg { get; private set; }

    [ProtoMember(4)]
    public bool productWeightKgSet { get; private set; }

    public void SetProductWeight(decimal? weight)
    {
        productWeightKg = weight;
        productWeightKgSet = true;
    }

    public void ClearProductWeight()
    {
        productWeightKgSet = false;
    }
}

如果这是一种合理的使用模式,那么下一个“显而易见”的想法是为这种行为创建某种模板类,我们可以将其重用于我们所有的消息类...

public class Optional<T>
{
    public T Value { get; private set; }
    public bool HasValue { get; private set; }

    public void Set(T val) { Value = val; HasValue = true; }
    public void Clear() { HasValue = false; Value = default; }
}        

这是解决这个问题的一种合理的方法,还是我错过了一些其他“已知的好模式”,或者这个模式在 protobuf-net 中不能按预期工作?

目前我的有限理解是,这可能需要使用 ProtoInclude 属性使用其所有可能的子实现来装饰 Optional 类,对吗?

【问题讨论】:

  • 听起来您正在考虑将“条件序列化”与“合并”(而不是反序列化)相结合——这里的答案涵盖了很多类似的领域——有什么用吗? stackoverflow.com/questions/61931285/…
  • @MarcGravell 是的,在增量方面也有类似的想法。我表达了点对点消息,但这里的上下文是通过代理和公共数据模型发布/订阅,所有 App1...AppN 都可以使用它来共享状态,而无需明确了解彼此。一个简单的通用模型将是所有系统中所有语义上不同的属性的联合。任何单个发布者都可能无法填充整个共享模型,但任何订阅者都需要知道发布者没有填充哪些字段,以便它们不会被视为“真实”更改(例如,变为 null)。
  • 据我了解链接线程,我可以看到潜在有用的想法是让每条消息包含一个表示所有字段的单个位掩码,并以某种方式读取掩码,而不是解决方案其中每个“真实”字段都包含自己的伴随布尔值。这消除了将 Optional 作为消息中任何元素的基本类型的需要,这似乎有助于 protobuf-net 实现、删除继承和嵌套,尽管在添加新元素时确实需要更加小心字段。
  • 我已更新问题以更好地反映 pub/sub 通用模型状态转移语义。

标签: c# protobuf-net


【解决方案1】:

最终,protobuf-net 的目的不是提供强大的字段跟踪机制,而且由于它适用于 POCO 类型 - 它无处存储任何其他状态,除了您的对象模型提供。它确实支持条件序列化,并且你的模型可以做很多事情来跟踪内部变化,正如here所讨论的那样;这可能与 Merge 结合使用(而不是反序列化) - 但除此之外并不是开箱即用的东西(据我所知,大多数其他 POCO 序列化程序也没有提供它)。

你所描述的和FieldMask 概念之间存在一些交叉,但是:迄今为止,protobuf-net 还没有实现或支持FieldMask 的需要。

我总是乐于探索图书馆可能能够帮助人们的新事物,但如果缺少图书馆功能:这可能是在 GitHub 上更好地讨论的内容,以及目标的非常具体的细节情景和动机等。

【讨论】:

  • 是的,这确实是一个关于我应该如何编写使用 protobuf-net 序列化进行传输的代码的问题,而不是关于 protobuf-net 提供的功能的问题。一些提供明确的“不是由发布者设置的值”样式语义的模型可能比其他模型更适合 protobuf-net。我的示例解决方案需要对泛型进行序列化(这似乎很好,但可能存在陷阱),以及序列化类型的继承(这有一些管理开销),等等。所以这是一个关于包含 protobuf-net 的模式的问题。
  • @allmhuran 所以是的; protobuf-net 有将其组合在一起的部分,但它本身并不进行跟踪
猜你喜欢
  • 2011-06-13
  • 2010-10-21
  • 2020-01-25
  • 2020-01-24
  • 1970-01-01
  • 1970-01-01
  • 2011-10-30
相关资源
最近更新 更多