简体   繁体   English

protobuf-net 没有正确反序列化 DateTime.Kind

[英]protobuf-net does not deserialize DateTime.Kind correctly

using protobuf-net.dll Version 1.0.0.280使用protobuf-net.dll版本 1.0.0.280

When I deserialize a DateTime (wrapped in an object), the date/time is ok but the DateTime.Kind property is 'Unspecified'当我反序列化DateTime (包装在对象中)时,日期/时间没问题,但DateTime.Kind属性为“未指定”

Consider this test case to serialize/deserialize a DateTime.考虑这个测试用例来序列化/反序列化一个 DateTime。

[TestMethod]
public void TestDateTimeSerialization()
{
    var obj = new DateTimeWrapper {Date = DateTime.UtcNow};
    obj.Date = DateTime.SpecifyKind(obj.Date, DateTimeKind.Utc);
    var serialized = obj.SerializeProto();
    var deserialized = serialized.DeserializeProto<DateTimeWrapper>();
    Assert.AreEqual(DateTimeKind.Utc, deserialized.Date.Kind);
}

public static byte[] SerializeProto<T>(this T item) where T : class
{
    using (var ms = new MemoryStream())
    {
        Serializer.Serialize(ms, item);
        return ms.ToArray();
    }
}

public static T DeserializeProto<T>(this byte[] raw) where T : class, new()
{
    using (var ms = new MemoryStream(raw))
    {
        return Serializer.Deserialize<T>(ms);
    }
}

The Assert fails, the Kind == Unspecified断言失败,种类 == Unspecified

Addendum附录

As a result of protobuf-net not serializing this property (see below) one solution is to assume DateTimeKind is equal to Utc when displaying dates on the client side (only where you know it should be UTC of course):由于protobuf-net没有序列化此属性(见下文),一种解决方案是在客户端显示日期时假设DateTimeKind等于 Utc(当然只有在您知道它应该是 UTC 的地方):

public static DateTime ToDisplayTime(this DateTime utcDateTime, TimeZoneInfo timezone)
{
    if (utcDateTime.Kind != DateTimeKind.Utc)//may be Unspecified due to serialization
        utcDateTime = DateTime.SpecifyKind(utcDateTime, DateTimeKind.Utc);
    DateTime result = TimeZoneInfo.ConvertTime(utcDateTime, timezone);
    return result;
}

This saves you having to assign to each DateTime property on the receiving side.这使您不必分配给接收方的每个DateTime属性。

protobuf.net has to maintain compatibility with the protobuf binary format, which is designed for the Java date/time datatypes. protobuf.net 必须保持与 protobuf 二进制格式的兼容性,该格式专为 Java 日期/时间数据类型而设计。 No Kind field in Java -> No Kind support in the protobuf binary format -> Kind not transferred across the network. Java 中没有Kind字段 -> protobuf 二进制格式不支持Kind -> Kind未通过网络传输。 Or something along those lines.或类似的规定。

As it turns out, protobuf.net encodes the Ticks field (only), you'll find the code in BclHelpers.cs .事实证明,protobuf.net (仅)对Ticks字段进行编码,您将在BclHelpers.cs中找到代码。

But feel free to add another field in your protobuf message definition for this value.但是请随意在您的 protobuf 消息定义中为此值添加另一个字段。

As an extension to Ben's answer... strictly speaking, protobuf has no definition of time, so there is nothing to retain compatibility with.作为 Ben 的答案的扩展......严格来说,protobuf没有时间定义,因此没有什么可以保持兼容性。 I'm tempted to add support for this in v2, but sadly it would add 2 bytes per value.我很想在 v2 中添加对此的支持,但遗憾的是它会为每个值添加 2 个字节。 I have yet to think about whether this is acceptable... for example, I could perhaps default to "unspecified" so that only explicitly local or UTC dates have a value.我还没有考虑这是否可以接受......例如,我可能会默认为“未指定”,以便只有明确的本地或 UTC 日期才有值。

Another solution is to change kind property for DTO and always set it to UTC.另一种解决方案是更改 DTO 的种类属性并始终将其设置为 UTC。 This may not be acceptable for all the application, but works for me这可能不适用于所有应用程序,但对我有用

class DateTimeWrapper 
{
    private DateTime _date;

    public DateTime Date 
    {
        get { return _date; }
        set { _date = new DateTime(value.Ticks, DateTimeKind.Utc);}
    }
}

Update更新

After using protobuf for more than a year and integrating C#, Java, Python and Scala I came to conclusion that one should use a long representation for the DateTime. After using protobuf for more than a year and integrating C#, Java, Python and Scala I came to conclusion that one should use a long representation for the DateTime. For example using UNIX time.例如使用 UNIX 时间。 It's painful to translate C# DateTime protobuf object to other languages DateTime.把 C# DateTime protobuf object 翻译成其他语言的 DateTime 很痛苦。 However, something as simple as long is understood by all.不过,简单的事情大家都懂。

Here's an implementation for a workaround.这是解决方法的实现。 Let me know if you can find a better solution.如果您能找到更好的解决方案,请告诉我。 Thanks!谢谢!

[ProtoContract(SkipConstructor = true)]
public class ProtoDateTime
{
    [ProtoIgnore]
    private DateTime? _val;

    [ProtoIgnore]
    private DateTime Value
    {
        get
        {
            if (_val != null)
            {
                return _val.Value;
            }
            lock (this)
            {
                if (_val != null)
                {
                    return _val.Value;
                }
                _val = new DateTime(DateTimeWithoutKind.Ticks, Kind);
            }
            return _val.Value;
        }
        set
        {
            lock (this)
            {
                _val = value;
                Kind = value.Kind;
                DateTimeWithoutKind = value;
            }
        }
    }

    [ProtoMember(1)]
    private DateTimeKind Kind { get; set; }
    [ProtoMember(2)]
    private DateTime DateTimeWithoutKind { get; set; }


    public static DateTime getValue(ref ProtoDateTime wrapper)
    {
        if (wrapper == null)
        {
            wrapper = new ProtoDateTime();
        }
        return wrapper.Value;
    }

    public static DateTime? getValueNullable(ref ProtoDateTime wrapper)
    {
        if (wrapper == null)
        {
            return null;
        }
        return wrapper.Value;

    }

    public static void setValue(out ProtoDateTime wrapper, DateTime value)
    {
        wrapper = new ProtoDateTime { Value = value };
    }

    public static void setValue(out ProtoDateTime wrapper, DateTime? newVal)
    {
        wrapper = newVal.HasValue ? new ProtoDateTime { Value = newVal.Value } : null;
    }
}

Usage:用法:

[ProtoContract(SkipConstructor = true)]
public class MyClass
{
    [ProtoMember(3)]
    [XmlIgnore]
    private ProtoDateTime _timestampWrapper { get; set; }
    [ProtoIgnore]
    public DateTime Timestamp
    {
        get
        {
            return ProtoDateTime.getValue(ref _timestampWrapper);
        }
        set
        {
            return ProtoDateTime.setValue(out _timestampWrapper, value);
        }
    }

    [ProtoMember(4)]
    [XmlIgnore]
    private ProtoDateTime _nullableTimestampWrapper { get; set; }
    [ProtoIgnore]
    public DateTime? NullableTimestamp
    {
        get
        {
            return ProtoDateTime.getValueNullable(ref _nullableTimestampWrapper);
        }
        set
        {
            return ProtoDateTime.setValue(out _nullableTimestampWrapper, value);
        }
    }

}

It might make more sense for protobuf to automatically deserialize the DateTime with the UtcKind, that way if you are using Utc as your base, which I think is best practice anyway, you wont have any issues. protobuf 使用 UtcKind 自动反序列化 DateTime 可能更有意义,这样如果您使用 Utc 作为基础,我认为无论如何这是最佳实践,您不会有任何问题。

Assuming you only need a single DateTimeKind (ie UTC or Local ) there's a simple (though not pretty) solution.假设您只需要一个DateTimeKind (即UTCLocal ),则有一个简单(虽然不漂亮)的解决方案。

Since internally protobuf-net converts DateTime 's into Unix-Time representation it has a single DateTime value representing the Unix epoch (1970/01/01) to which it adds the relevant delta each time.由于 protobuf-net 在内部将DateTime转换为 Unix-Time 表示,因此它有一个DateTime值,表示 Unix 时期(1970/01/01),它每次都添加相关的增量。

If you replace that value using reflection with a UTC or Local DateTime value, all your DateTime s will have that specified DateTimeKind :如果使用UTCLocal DateTime值使用反射替换该值,则所有DateTime都将具有指定的DateTimeKind

typeof (BclHelpers).
    GetField("EpochOrigin", BindingFlags.NonPublic | BindingFlags.Static).
    SetValue(null, new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc));

You can read more about in on my blog你可以在 我的博客上阅读更多关于

Starting with protobuf-net 2.2 (see commit ), there is a possibility to opt-in to the serialization of DateTime.Kind.从 protobuf-net 2.2(参见commit )开始,可以选择加入 DateTime.Kind 的序列化。 You can set a global flag.您可以设置全局标志。 The corresponding issue on github (still open). github 上的相应问题(仍然开放)。

And here is a usage example in connection with NServiceBus.这是一个与 NServiceBus 相关的使用示例

Disclaimer: This won't help for the old protobuf-net version the OP is referring to, but this is an old question, and may be helpful for others.免责声明:这对 OP 所指的旧 protobuf-net 版本没有帮助,但这是一个老问题,可能对其他人有帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM