1

I don't want to mess up our BusinessObjects with any serialization logic. That's why I wanted to implemented a Surrogate for every single BusinessObject which encapsulates the Data which is necessary to serialize and deserialize the BusinessObject. Furthermore it contains all business logic which is to be executed after serialization.

However there seems to be an issue with reference tracking in Dictionaries. Consider the following example:

   //DataContracts:


        public class SerializeClass
        {
            public Dictionary<string, SerializeDictionaryItem> MyDictionary { get; set; }
            public List<SerializeDictionaryItem> MyList { get; set; }
        }

        [ProtoContract]
        public class SerializeDictionaryItem
        {
            [ProtoMember(1)]
            public string MyField { get; set; }
        }

            [ProtoContract(SkipConstructor = true)]
            public class SerializeClassSurrogate
            {
                [ProtoMember(1000, AsReference = true)]
                public Dictionary<string, SerializeDictionaryItem> MyDictionary { get; set; }

                [ProtoMember(1001, AsReference = true)]
                public List<SerializeDictionaryItem> MyList { get; set; }

                public static implicit operator SerializeClass(SerializeClassSurrogate surrogate)
                {
                    if (surrogate == null)
                        return null;

                    var serializeClass = new SerializeClass();
                    serializeClass.MyDictionary = surrogate.MyDictionary;
                    serializeClass.MyList = surrogate.MyList;

                    return serializeClass;
                }

                public static implicit operator SerializeClassSurrogate(SerializeClass serializeClass)
                {
                    if (serializeClass == null)
                        return null;

                    var surrogate = new SerializeClassSurrogate();
                    surrogate.MyDictionary = serializeClass.MyDictionary;
                    surrogate.MyList = serializeClass.MyList;

                    return surrogate;
                }
            }


    //Serialization Logic:
RuntimeTypeModel.Default[typeof(SerializeClass)].SetSurrogate(typeof(Surrogates.SerializeClassSurrogate));

                var myDictionaryItem = new SerializeDictionaryItem();
                myDictionaryItem.MyField = "ABC";

                SerializeClass m = new SerializeClass() {MyDictionary = new Dictionary<string, SerializeDictionaryItem>()};
                m.MyDictionary.Add("def", myDictionaryItem);
                m.MyDictionary.Add("abc", myDictionaryItem);

                m.MyList = new List<SerializeDictionaryItem>();
                m.MyList.Add(myDictionaryItem);
                m.MyList.Add(myDictionaryItem);

                using (var writer = new StreamWriter(OutputDir + "proto.bin"))
                {
                    Serializer.Serialize(writer.BaseStream, m);
                }

                using(var reader = new StreamReader(OutputDir + "proto.bin"))
                {
                    var deserialized = Serializer.Deserialize<SerializeClass>(reader.BaseStream);
                    var areEqualInDictionary = deserialized.MyDictionary["def"] == deserialized.MyDictionary["abc"];
                    var areEqualInList = deserialized.MyList[0] == deserialized.MyList[1];
                }

The two "areEqualIn..." booleans should be true after the deserialization. However I get the following exception during deserialization:

"A reference-tracked object changed reference during deserialization"

Is this a bug or am I doing something wrong?

/EDIT: This doesn't seem to have anything to do with the usage of Surrogates. If I try the serialization without them and simply change the SerializeClass to a ProtoContract and its Members accordingly to ProtoMembers exactly the same problem occurs.

Btw. I am using protobuf-net v2 429

/EDIT2: This exception can also be provoked using this code, which doesn't need Dictionaries at all:

    public class SerializeClass
    {
        public string MyField { get; set; }

        public SerializeClass GroupTrailerRef { get; set; }
    }

        [ProtoContract(SkipConstructor = true)]
        public class SerializeClassSurrogate
        {
            [ProtoMember(1001)]
            public string MyField { get; set; }

            [ProtoMember(1002, AsReference = true)]
            public SerializeClass GroupTrailerRef { get; set; }

            public static implicit operator SerializeClass(SerializeClassSurrogate surrogate)
            {
                if (surrogate == null)
                    return null;

                var serializeClass = new SerializeClass();
                serializeClass.MyField = surrogate.MyField;
                serializeClass.GroupTrailerRef = surrogate.GroupTrailerRef;

                return serializeClass;
            }

            public static implicit operator SerializeClassSurrogate(SerializeClass serializeClass)
            {
                if (serializeClass == null)
                    return null;

                var surrogate = new SerializeClassSurrogate();
                surrogate.MyField = serializeClass.MyField;
                surrogate.GroupTrailerRef = serializeClass.GroupTrailerRef;

                return surrogate;
            }
        }

RuntimeTypeModel.Default[typeof(SerializeClass)].SetSurrogate(typeof(Surrogates.SerializeClassSurrogate));

//Serialization Code:
SerializeClass groupTrailer = new SerializeClass();
            groupTrailer.MyField = "Group";

            SerializeClass m = new SerializeClass() {};
            m.GroupTrailerRef = groupTrailer;
            m.MyField = "First";

            SerializeClass k = new SerializeClass();
            k.GroupTrailerRef = groupTrailer;
            k.MyField = "Second";


            var lst = new List<SerializeClass>();
            lst.Add(m);
            lst.Add(k);

            using (var writer = new StreamWriter(OutputDir + "proto.bin"))
            {
                Serializer.Serialize(writer.BaseStream, lst);
            }

            using(var reader = new StreamReader(OutputDir + "proto.bin"))
            {
                var deserialized = Serializer.Deserialize<List<SerializeClass>>(reader.BaseStream);
                var areEqual = deserialized[0].GroupTrailerRef == deserialized[1].GroupTrailerRef;
            }

What do you suggest? I like the surrogate method to separate the serialization logic from my business objects. Do you think I should just stick to the "normal" way? However in that case I am again stuck because of Issue 203...

Thank you, TH

TwinHabit
  • 753
  • 8
  • 22
  • I will have to look later... as long as it only went through the item once in the deserialization (which it must have, in a dictionary), I can't think of a reason that it *can't* work - more likely, this is an edge case where it is doing something a bit... wonky. – Marc Gravell Jul 11 '11 at 14:19
  • Thank you very much for your quick responses. I think I'll try v1 in the meantime. I am trying to convince our project leader right now to switch from BinaryFormatter to your library. However, soon I have to be able to present the first working prototype ;) – TwinHabit Jul 11 '11 at 14:41
  • v1 doesn't have reference tracking *at all* ;p – Marc Gravell Jul 11 '11 at 15:21
  • I just got the same exception even without using Dictionaries: I'll add it to my first post as these few characters are not enough :) – TwinHabit Jul 11 '11 at 15:25
  • right; I have the example, will look later – Marc Gravell Jul 11 '11 at 15:36
  • Just as an update, I am working through this – Marc Gravell Jul 12 '11 at 15:23
  • Thank you very much. Yesterday I succeeded in getting the data serialized with protobuf to match 99,8% of the data serialized with BinaryFormatter. Apparently the only thing left that I have to figure out is that our contracts rely on a difference between empty lists and null lists (Depending on whether the data was pulled from the database). I think I will try to solve this with Surrogates for my Lists. Do you see any problems with that? I hope by doing that the problems described above wont arise, as Reference Tracking is absolutely vital. As soon as that works, we'll switch :) – TwinHabit Jul 13 '11 at 06:35
  • Unfortunately it does not work with surrogates. Posted my question here: http://stackoverflow.com/questions/6675422/differentiation-between-empty-lists-and-null-lists-cannot-be-worked-around-using – TwinHabit Jul 13 '11 at 07:22

0 Answers0