Protobuf-net ссылка на граф объекта с суррогатом - PullRequest
0 голосов
/ 22 февраля 2019

Насколько я знаю, protobuf-net, начиная с v2, поддерживает ссылки, но их нельзя использовать вместе с суррогатом (исключение "В этом случае выбрасывается ссылка, отслеживаемая ссылками, объект изменил ссылку во время десериализации" ))

Мне интересно, есть ли какой-нибудь обходной путь, который я не рассматривал, чтобы заставить его работать.

Здесь следуем коду моего теста, который воспроизводит вышеупомянутое исключение.

Классы

public class Person
{
    public Person(string name, GenderType gender)
    {
        Name = name;
        Gender = gender;
    }
    public string Name { get; set; }
    public GenderType Gender { get; set; }
}

[Flags]
public enum GenderType : byte
{
    Male = 1,
    Female = 2,
    Both = Male | Female
}

public class Family
{
    public Family(List<Person> people, Person familyHead = null)
    {
        People = people;

        FamilyHead = familyHead;
    }

    public List<Person> People { get; set; }

    public Person FamilyHead { get; set; }
}

public class PersonSurrogate
{
    public string Name { get; set; }
    public byte Gender { get; set; }

    public PersonSurrogate(string name, byte gender)
    {
        Name = name;
        Gender = gender;
    }       

    #region Static Methods

    public static implicit operator Person(PersonSurrogate surrogate)
    {
        if (surrogate == null) return null;

        return new Person(surrogate.Name, (GenderType)surrogate.Gender);

    }

    public static implicit operator PersonSurrogate(Person source)
    {
        return source == null ? null : new PersonSurrogate(source.Name, (byte)source.Gender);
    }

    #endregion       
}

public class FamilySurrogate
{
    public FamilySurrogate(List<Person> people, Person familyHead)
    {
        People = people;
        FamilyHead = familyHead;
    }

    public List<Person> People { get; set; }

    public Person FamilyHead { get; set; }

    #region Static Methods

    public static implicit operator Family(FamilySurrogate surrogate)
    {
        if (surrogate == null) return null;

        return new Family(surrogate.People, surrogate.FamilyHead);

    }

    public static implicit operator FamilySurrogate(Family source)
    {
        return source == null ? null : new FamilySurrogate(source.People, source.FamilyHead);
    }

    #endregion
}

Сериализатор

/// <summary>
/// Class with model for protobuf serialization
/// </summary>
public class FamilySerializer
{    
    public GenderType GenderToInclude;

    public FamilySerializer(Family family, GenderType genderToInclude = GenderType.Both)
    {
        GenderToInclude = genderToInclude;
        Family = family;

        Init();
    }

    private void Init()
    {
        Model = RuntimeTypeModel.Create();
        FillModel();
        Model.CompileInPlace();         
    }

    public FamilySerializer()
    {
        Init();
    }

    public Family Family { get; set; }
    public RuntimeTypeModel Model { get; protected set; }

    protected virtual void FillModel()
    {
        Model = RuntimeTypeModel.Create();

        Model.Add(typeof(Family), false)
            .SetSurrogate(typeof(FamilySurrogate));

        MetaType mt = Model[typeof(FamilySurrogate)];
        mt.Add(1, "People");
        mt.AddField(2, "FamilyHead").AsReference = true;  // Exception "A reference-tracked object changed reference during deserialization" - because using surrogate.
        mt.UseConstructor = false;

        Model.Add(typeof(Person), false)
            .SetSurrogate(typeof(PersonSurrogate));

        mt = Model[typeof(PersonSurrogate)]
            .Add(1, "Name")
            .Add(2, "Gender");
        mt.UseConstructor = false; // Avoids to use the parameterless constructor.
    }

    public void Save(string fileName)
    {            
        using (Stream s = File.Open(fileName, FileMode.Create, FileAccess.Write))
        {
            Model.Serialize(s, Family, new ProtoBuf.SerializationContext(){Context = this});
        }
    }

    public void Open(string fileName)
    {
        using (Stream s = File.Open(fileName, FileMode.Open, FileAccess.Read))
        {
            Family = (Family)Model.Deserialize(s, null, typeof(Family), new ProtoBuf.SerializationContext(){Context = this});
        }
    }
}

Контрольный пример

private Family FamilyTestCase(string fileName, bool save)
{           
    if (save)
    {
        var people = new List<Person>()
        {
            new Person("Angus", GenderType.Male),
            new Person("John", GenderType.Male),
            new Person("Katrina", GenderType.Female),           
        };
        var fam = new Family(people, people[0]);

        var famSer = new FamilySerializer(fam);

        famSer.Save(fileName);

        return fam;
    }
    else
    {
        var famSer = new FamilySerializer();

        famSer.Open(fileName);

        if (Object.ReferenceEquals(fam.People[0], fam.FamilyHead))
        {
            // I'd like this condition would be satisfied
        }

        return famSer.Family;
    }
}

1 Ответ

0 голосов
/ 22 февраля 2019

Я думаю, что пока это просто неподдерживаемый сценарий, и я не знаю, как заставить его волшебным образом работать;это может быть то, к чему я могу вернуться в какой-то момент, но есть много вещей с более высоким приоритетом, которые будут иметь приоритет.

Мой обычный совет здесь - и это относится к любому сериализатору, а не только к protobuf-net: всякий раз, когда вы сталкиваетесь с ограничением сериализатора или даже с чем-то, что неудобно конфигурировать в сериализаторе: прекратите бороться с сериализатором.Такая проблема почти всегда возникает, когда люди пытаются сериализовать свою модель обычного домена , и что-то в модели домена не идеально подходит для выбранного ими сериализатора.Вместо того, чтобы пытаться использовать тайную магию: разделите вашу модель - пусть ваша модель domain будет соответствовать тому, что вы хотите, чтобы ваше приложение увидело, и создайте Отдельная модель, которая отлично подходит для вашего сериализатора.Тогда вам не нужны такие понятия, как «суррогаты».Если вы используете несколько форматов сериализации или имеете несколько разных «версий» макета в одном и том же формате сериализации: имеет несколько моделей сериализации .

Это действительно не стоит головной болипытаюсь заставить на модель служить нескольких мастеров.

...