6

Я пытался добавить новое значение enum для определенного класса protobuf-serialized в новой версии приложения, а во время тестирования заметил, что предыдущая версия генерирует исключение, учитывая этот новый формат файла :Protobuf-net enum обратная совместимость

 
An unhandled exception of type 'ProtoBuf.ProtoException' occurred in protobuf-net.dll 
Additional information: No {enum-type-name} enum is mapped to the wire-value 3 

это довольно очевидно, что это говорит мне, что нет никакого значения перечисления для int значения 3, но я всегда была идея, что Protocol Buffers defaulted to the zero-valued ("default") enum value (если таковая существует), в случае, если фактическое значение перечисления не удалось сопоставить.

Чтобы уточнить, это может быть воспроизведен с помощью следующего примера (я намеренно делаю шаг десериализации в другой класс, чтобы имитировать старые приложения пытается загрузить новый формат):

// --- version 1 --- 

public enum EnumV1 
{ 
    Default = 0, 
    One = 1, 
    Two = 2 
} 

[ProtoContract] 
public class ClassV1 
{ 
    [ProtoMember(1)] 
    public EnumV1 Value { get; set; } 
} 



// --- version 2 --- 

public enum EnumV2 
{ 
    Default = 0, 
    One = 1, 
    Two = 2, 
    Three = 3 // <- newly added 
} 

[ProtoContract] 
public class ClassV2 
{ 
    [ProtoMember(1)] 
    public EnumV2 Value { get; set; } 
} 

И следующий код потерпит неудачу:

// serialize v2 using the new app 
var v2 = new ClassV2() { Value = EnumV2.Three }; 
var v2data = Serialize(v2); 

// try to deserialize this inside the old app to v1 
var v1roundtrip = Deserialize<ClassV1>(v2data); 

Поскольку v1 находится в открытом, есть некоторые метаданные можно использовать при сериализации в v2, чтобы избежать этой проблемы? Я могу, конечно, избавиться от этой проблемы, переписав v2, чтобы использовать отдельное свойство и оставить значения перечисления неизмененными, но я хотел бы сделать переходы обратно совместимыми, если это возможно.

+0

Что должно произойти, чтобы '' v1roundtrip.Value' когда EnumV2.Three' был отправлен? – Caramiriel

+1

@Caramiriel: Согласно моему пониманию (объяснено в [этой теме] (http://stackoverflow.com/q/10392952/69809)), он должен был быть установлен в 'EnumV1.Default' вместо того, чтобы бросать исключение. Этого я ожидал бы, если бы я хотел убедиться, что формат обратно совместим. Например, [этот пользователь] (http://stackoverflow.com/a/13924171/69809), похоже, имел такую ​​же проблему и исправил его, добавив значение перечисления по умолчанию (нуль), без дополнительных атрибутов protobuf. – Groo

+2

ping @marcgravell – jgauffin

ответ

1

Поскольку v1 находится под открытым небом, есть ли некоторые метаданные, которые я могу использовать при сериализации в версии 2, чтобы избежать этой проблемы? Я могу, конечно, избавиться от этой проблемы, переписав v2, чтобы использовать отдельное свойство и оставить значения перечисления неизмененными, но я хотел бы сделать переходы обратно совместимыми, если это возможно.

То, что вы испытываете это Protobuf-сеть ошибка описана здесь protobuf-net - issue #422: Invalid behaviour while deserializing unknown enum value.

Кажется, что это еще не исправлено в соответствии с здесь protobuf-net faulty enum exception (issue 422) need a good workaround (и, конечно, ваше сообщение).

К сожалению, вам необходимо исправить исходный код protobuf-net или использовать обходные пути.

UPDATE: Я проверил код в репозитории GitHub и подтвердил, что проблема все еще не исправлена. Вот проблемный код внутри EnumSerializer.cs (на ISSUE #422 комментария мой):

public object Read(object value, ProtoReader source) 
{ 
    Helpers.DebugAssert(value == null); // since replaces 
    int wireValue = source.ReadInt32(); 
    if(map == null) { 
     return WireToEnum(wireValue); 
    } 
    for(int i = 0 ; i < map.Length ; i++) { 
     if(map[i].WireValue == wireValue) { 
      return map[i].TypedValue; 
     } 
    } 
    // ISSUE #422 
    source.ThrowEnumException(ExpectedType, wireValue); 
    return null; // to make compiler happy 
} 
0

В вашем классеV1 отсутствует передовая совместимость.

Я бы применил протокол Proto таким образом, чтобы он сериализовал/десериализовал строковое представление значения перечисления. Таким образом вы можете самостоятельно справиться с резервным значением по умолчанию. Свойство Value не будет сериализовано/десериализовано.

public enum EnumV1 
{ 
    Default = 0, 
    One = 1, 
    Two = 2 
} 

public enum EnumV2 
{ 
    Default = 0, 
    One = 1, 
    Two = 2, 
    Three = 3 // <- newly added 
} 

[ProtoContract] 
public class ClassV1 
{ 
    [ProtoMember(1)] 
    public string ValueAsString 
    { 
     get { return Value.ToString(); } 
     set 
     { 
      try 
      { 
       Value = (EnumV1) Enum.Parse(typeof (EnumV1), value); 
      } 
      catch (Exception) 
      { 
       Value = EnumV1.Default; 
      } 
     } 
    } 

    public EnumV1 Value { get; set; } 
} 

[ProtoContract] 
public class ClassV2 
{ 
    [ProtoMember(1)] 
    public string ValueAsString 
    { 
     get { return Value.ToString(); } 
     set 
     { 
      try 
      { 
       Value = (EnumV2)Enum.Parse(typeof(EnumV2), value); 
      } 
      catch (Exception) 
      { 
       Value = EnumV2.Default; 
      } 
     } 
    } 

    public EnumV2 Value { get; set; } 
} 

Тем не менее это не решает проблему наличия в производстве непереходно-совместимого класса.

+0

Существует множество способов обеспечения обратной/прямой совместимости. Более простой способ (для перечисления), который будет работать, состоял бы в том, чтобы/сериализовать значение 'int'. Но это не то, что я сделал, потому что я полагался на протобуф, чтобы справиться с этим. Я разработал контракт v1, полагая, что * Протокольные буферы по умолчанию имеют значение нулевого значения перечисления, если фактическое значение перечисления не может быть сопоставлено с *, и я хотел бы видеть 1) почему это не работает и 2) по крайней мере, как перехватить и обработать этот случай при десериализации. Ваш ответ не объясняет, почему * 'ClassV1' не имеет совместимости с fw, и что теперь делать. – Groo

+0

Отсутствие прямой совместимости происходит из ClassV1, не поддерживающего новые значения enum, которые можно рассматривать как типы значений, состоящие из констант времени компиляции. Это как реализация инструкции switch/case без ветви по умолчанию и позволяет обрабатывать неподдерживаемые случаи. –

+0

Я не думаю, что установка значения по умолчанию является обязанностью protobuf. Я бы позволил классу ClassV1 позаботиться об этом сам, инициализируя свойство во время instanciation, вместо того, чтобы оставить его униализованным (логические поля также инициализируются ложным неявным образом). proto-buf бросит все, что угодно, потому что это показывает, что есть проблема совместимости. –

0

Вы можете добавить атрибут DefaultValue к свойству proto member.

[ProtoContract] 
public class ClassV1 
{ 
    [ProtoMember(1), DefaultValue(EnumV1.Default)] 
    public EnumV1 Value { get; set; } 
} 

Чтобы уточнить, как должно быть инициализировано свойство для случая по умолчанию.

+0

Вы попробовали? Я не думаю, что это имеет какое-то отношение к исключению. – Groo

3

Добавление [ProtoContract(EnumPassthru=true)] к вашим перечислениям позволит protobuf-net десериализовать неизвестные значения.

К сожалению, нет способа ретроактивно исправить ваш v1. Вам придется использовать другое свойство.

+0

Но разве это не означает, что десериализованное значение равно '3' (даже если у него не было подходящего перечисления) вместо значения по умолчанию? Я все еще ожидал, что значение по умолчанию будет использоваться в случае, если фактическое перечисление не может быть разрешено? – Groo

+0

Да. Если вам требуется значение по умолчанию для старой версии, а значение новой версии - новое значение enum (в этом случае - 3), то при каждом добавлении вам нужно переключиться на новое свойство новое значение для вашего перечисления. – yaakov

 Смежные вопросы

  • Нет связанных вопросов^_^