views:

240

answers:

1

I'm having issues deserializing certain Guid properties of ORM-generated entities using protobuf-net.

Here's a simplified example of the code (reproduces most elements of the scenario, but doesn't reproduce the behavior; I can't expose our internal entities, so I'm looking for clues to account for the exception). Say I have a class, Account with an AccountID read-only guid, and an AccountName read-write string. I serialize & immediately deserialize a clone.

Deserializing throws an Incorrect wire-type deserializing Guid exception while deserializing.

Here's example usage...

        Account acct = new Account() { AccountName = "Bob's Checking" };
        Debug.WriteLine(acct.AccountID.ToString());
        using (MemoryStream ms = new MemoryStream())
        {
            ProtoBuf.Serializer.Serialize<Account>(ms, acct);
            Debug.WriteLine(Encoding.UTF8.GetString(ms.GetBuffer()));
            ms.Position = 0;
            Account clone = ProtoBuf.Serializer.Deserialize<Account>(ms);
            Debug.WriteLine(clone.AccountID.ToString());
        }

And here's an example ORM'd class (simplified, but demonstrates the relevant semantics I can think of). Uses a shell game to deserialize read-only properties by exposing the backing field ("can't write" essentially becomes "shouldn't write," but we can scan code for instances of assigning to these fields, so the hack works for our purposes).

Again, this does not reproduce the exception behavior; I'm looking for clues as to what could:

[DataContract()]
[Serializable()]
public partial class Account
{
    public Account()
    {
        _accountID = Guid.NewGuid();
    }
    [XmlAttribute("AccountID")]
    [DataMember(Name = "AccountID", Order = 1)]
    public Guid _accountID;

    /// <summary>
    /// A read-only property; XML, JSON and DataContract serializers all seem
    /// to correctly recognize the public backing field when deserializing: 
    /// </summary>
    [IgnoreDataMember]
    [XmlIgnore]
    public Guid AccountID
    {
        get { return this._accountID; }
    }

    [IgnoreDataMember]
    protected string _accountName;

    [DataMember(Name = "AccountName", Order = 2)]
    [XmlAttribute]
    public string AccountName
    {
        get { return this._accountName; }
        set { this._accountName = value; }
    }
}

XML, JSON and DataContract serializers all seem to serialize / deserialize these object graphs just fine, so the attribute arrangement basically works. I've tried protobuf-net with lists vs. single instances, different prefix styles, etc., but still always get the 'incorrect wire-type ... Guid' exception when deserializing.

So the specific questions is, is there any known explanation / workaround for this? I'm at a loss trying to trace what circumstances (in the real code but not the example) could be causing it.

We hope not to have to create a protobuf dependency directly in the entity layer; if that's the case, we'll probably create proxy DTO entities with all public properties having protobuf attributes. (This is a subjective issue I have with all declarative serialization models; it's a ubiquitous pattern & I understand why it arose, but IMO, if we can put a man on the moon, then "normal" should be to have objects and serialization contracts decoupled. ;-) )

Thanks!

+1  A: 

Agreed, you shouldn't need an explicit dependency - DataMember is fine. And protobuf-net uses the same logic re ignore etc. How / where are you storing the data? In my experience the most common cause of this is that people are over-writing a buffer (or file) with different data, and not truncating it (leaving garbage at the end of the stream), as discussed here. Is this related to your scenario?

Marc Gravell
+1, definitely a good track to search on. tried setting ms.Capacity = Convert.ToInt32(ms.Length) (why is .Capacity an int still hitting the same "Incorrect wire-type deserializing Guid" exception.
Paul Smith
@Paul - I didn't write `MemoryStream` ;-p Re the wire type issue - is there a reproducible example?
Marc Gravell
@Paul - It isn't `Capacity` that is important; it is the `Length`. If you are over-writing a `MemoryStream` you *must* truncate it (before or after doesn't matter). The problem (with over-writing) is that if you write a first object and it is (say) 254 bytes, then you set the position to 0 and over-write a second object (say) 120 bytes, then the stream is **still* 254 bytes long. The last 134 bytes are now garbage. See the `SetLength()` point in the referenced question.
Marc Gravell
@Marc Gravell - Thanks! Good tip; yes, I did make sure I was reading from a MemoryStream that had been written to only once. After your hint, I thought truncating its .Capacity to its .Length might make a difference, but unfortunately no. (Actually, due to the issues you mention, I practically never re-use a memory stream for writing, and I definitely don't in my reproducible scenario.)
Paul Smith
@Paul - I'm going to really struggle without a reproducible scenario, I'm afraid. Also - it might be worth trying the v2 code (from the trunk) - not *fully* stable yet (and not officially released), but it could be that if it is a bug, it is already fixed in v2.
Marc Gravell
Paul Smith
@Paul - warning! The trunk is v2. That is very different, and is not fully production ready yet (v1 is a branch). Indeed, `Guid.Empty` has separate handling, but it should still work fine. It is entirely possible that if it *is* a bug, that it is already fixed in v2 - but I can't for the life of me recognise (without an example) what is happening in this case.
Marc Gravell
@Marc - Interesting results; same scenarios, v2 succeeds (no `incorrect wire-type deserailizing Guid` exception) where r282 fails. Unfortunately, GetProto<T> isn't implemented yet in v2 (which I understand isn't stable yet anyway). Does the fact that v2 works cleanly suggest anything to you about where the issue could be on the v1 branch?
Paul Smith
@Paul - It tells me it is probably a bug... *very* hard to say where - maybe a stacktrace (or the protobuf-net parts of it) might help? Note that by default (at the moment, for debug purposes) v2 runs unoptimised (reflection only). If you call `Serializer.PrepareSerializer<T>` it should speed things up somewhat. Note also that v2 is not *quite* feature complete yet (most of it is there though - oddly I seem to have left "enums" and a few other things until last), and it does *not* currently pass the full test suite.
Marc Gravell
@Marc - I've had oddly mixed results swapping references between v1 r282 and the latest svn trunk. Next time I get the v1 to throw, I'll try to get you a stack trace (possibly thru other channels so stackoverflow has the benefit of a decisive answer at the end). Meanwhile, if anything on v1 r283+= comes out, I'll give that a whirl. Thanks for looking into this.
Paul Smith
Paul Smith