Protobuf-net适用于可选可赋值的可空类型(非集合)的模式。

3

我已经搜索了一段时间,并且发现了各种线程表明protobuf-net可以处理可空类型,各种关于空集合(此处不相关)和默认值行为的线程,对于proto2中以前可能是“optional”字段的内容,但我没有找到以下问题的具体答案。这将是我第一次使用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)(或让订阅者将缺失的元素反序列化为默认值)来传达“没有更新”的语义,因为当消息被App2接收时,这将导致App2Products表中的productWeightKg值设置为0。我们也不能发送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类及其所有可能的子实现进行装饰,是吗?

1
听起来你在谈论“条件序列化”与“合并”(而不是反序列化)- 这里的答案涵盖了很多类似的领域 - 有用吗?https://stackoverflow.com/questions/61931285/sending-explicit-zeroes-in-protobuf3/61931373#61931373 - Marc Gravell
@MarcGravell 是的,就增量而言,这里有类似的想法。我表达了一条点对点消息,但这里的上下文是通过代理进行发布/订阅和一个通用数据模型,所有 App1...AppN 都可以使用它来共享状态,而不需要显式地知道彼此。一个微不足道的通用模型将是所有系统中语义上不同属性的并集。任何单个发布者可能无法填充整个共享模型,但任何订阅者都需要知道发布者未填充哪些字段,以便它们不被视为“真实”更改(例如,变为 null)。 - allmhuran
据我理解,链接的线程中,我可以看到一个潜在有用的想法是让每个消息包含一个单一的位掩码,表示所有字段,并具有某种按字段读取掩码的方式,而不是每个“真实”字段都包含自己的伴随布尔值的解决方案。这消除了 Optional<T> 作为消息中任何元素的基本类型的需要,这似乎会在 protobuf-net 实现方面有所帮助,消除继承和嵌套,尽管添加新字段时需要更多的注意。 - allmhuran
我已更新问题,以更好地反映发布/订阅常见模型状态传输语义。 - allmhuran
1个回答

1
最终,protobuf-net 的目的不是为了提供一个健壮的字段跟踪机制,而且由于它适用于 POCO 类型 - 除了您的对象模型提供的状态外,它没有任何地方可以存储任何额外的状态。它支持条件序列化,并且有许多事情可以通过 您的模型 内部跟踪更改,如 这里 所讨论的; 这可能在与 Merge(而不是反序列化)结合使用时很有用 - 但超过这一点并不是开箱即用的功能(据我所知,大多数其他 POCO 序列化程序也不提供此功能)。
您所描述的内容与 FieldMask 概念有一些交叉,但迄今为止,protobuf-net 没有实现或支持 FieldMask 的需要。
我很乐意探索图书馆可以为人们做些什么的新事物,但如果缺少图书馆功能:最好在 GitHub 上讨论,同时提供目标场景和动机等非常具体的细节。

是的,这确实是一个关于我应该如何编写代码的问题,它利用了protobuf-net序列化进行传输,而不是有关protobuf-net提供功能的问题。 一些模型提供了明确的“未由发布者设置值”的语义,可能比其他模型更适合protobuf-net。我的示例解决方案需要序列化泛型(这似乎很好,但可能有陷阱),并继承序列化类型(这具有一定的管理开销),等等。因此,这是一个关于融入protobuf-net的模式的问题。 - allmhuran
@allmhuran 所以是的;protobuf-net有将这些组合在一起的工具,但它本身不进行跟踪。 - Marc Gravell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接