Как правильно наследовать от кортежей, чтобы генерация сериализатора из Орлеана была правильной?

У меня есть проект с netstandard2.0 в качестве TargetFramework и следующие пакеты Nuget:

microsoft.orleans.core -> Version="2.2.0"
microsoft.orleans.orleanscodegenerator.build -> Version="2.2.0"

В этом проекте есть DTO, который реализует кортеж следующим образом:

public sealed class SomeDetailsDto : Tuple<Guid, Guid>
    {
        public SomeDetailsDto(Guid firstGuidId, Guid secondGuidId)
            : base(firstGuidId, secondGuidId)
        {
        }

        public Guid firstGuidId => Item1;

        public Guid secondGuidId => Item2;
    }

Этот DTO будет использоваться в зернистом методе. Сгенерированный orleans код сериализатора выглядит следующим образом:

[global::System.CodeDom.Compiler.GeneratedCodeAttribute(@"Orleans-CodeGenerator", @"2.0.0.0"), global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute, global::Orleans.CodeGeneration.SerializerAttribute(typeof(global::SomeDetailsDto))]
    internal sealed class OrleansCodeGenSomeDetailsDtoSerializer
    {
        private readonly global::System.Func<global::System.Tuple<global::System.Guid, global::System.Guid>, global::System.Guid> getField0;
        private readonly global::System.Action<global::System.Tuple<global::System.Guid, global::System.Guid>, global::System.Guid> setField0;
        private readonly global::System.Func<global::System.Tuple<global::System.Guid, global::System.Guid>, global::System.Guid> getField1;
        private readonly global::System.Action<global::System.Tuple<global::System.Guid, global::System.Guid>, global::System.Guid> setField1;
        public OrleansCodeGenSomeDetailsDtoSerializer(global::Orleans.Serialization.IFieldUtils fieldUtils)
        {
            global::System.Reflection.FieldInfo field0 = typeof(global::System.Tuple<global::System.Guid, global::System.Guid>).GetField(@"m_Item1", (System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public));
            getField0 = (global::System.Func<global::System.Tuple<global::System.Guid, global::System.Guid>, global::System.Guid>)fieldUtils.GetGetter(field0);
            setField0 = (global::System.Action<global::System.Tuple<global::System.Guid, global::System.Guid>, global::System.Guid>)fieldUtils.GetReferenceSetter(field0);
            global::System.Reflection.FieldInfo field1 = typeof(global::System.Tuple<global::System.Guid, global::System.Guid>).GetField(@"m_Item2", (System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public));
            getField1 = (global::System.Func<global::System.Tuple<global::System.Guid, global::System.Guid>, global::System.Guid>)fieldUtils.GetGetter(field1);
            setField1 = (global::System.Action<global::System.Tuple<global::System.Guid, global::System.Guid>, global::System.Guid>)fieldUtils.GetReferenceSetter(field1);
        }

        [global::Orleans.CodeGeneration.CopierMethodAttribute]
        public global::System.Object DeepCopier(global::System.Object original, global::Orleans.Serialization.ICopyContext context)
        {
            global::SomeDetailsDto input = ((global::SomeDetailsDto)original);
            global::SomeDetailsDto result = (global::SomeDetailsDto)global::System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof(global::SomeDetailsDto));
            context.RecordCopy(original, result);
            setField0(result, getField0(input));
            setField1(result, getField1(input));
            return result;
        }

        [global::Orleans.CodeGeneration.SerializerMethodAttribute]
        public void Serializer(global::System.Object untypedInput, global::Orleans.Serialization.ISerializationContext context, global::System.Type expected)
        {
            global::SomeDetailsDto input = (global::SomeDetailsDto)untypedInput;
            context.SerializeInner(getField0(input), typeof(global::System.Guid));
            context.SerializeInner(getField1(input), typeof(global::System.Guid));
        }

        [global::Orleans.CodeGeneration.DeserializerMethodAttribute]
        public global::System.Object Deserializer(global::System.Type expected, global::Orleans.Serialization.IDeserializationContext context)
        {
            global::SomeDetailsDto result = (global::SomeDetailsDto)global::System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof(global::SomeDetailsDto));
            context.RecordObject(result);
            setField0(result, (global::System.Guid)context.DeserializeInner(typeof(global::System.Guid)));
            setField1(result, (global::System.Guid)context.DeserializeInner(typeof(global::System.Guid)));
            return (global::SomeDetailsDto)result;
        }
    }

Все это совершенно нормально.

Но недавно я обновил TargetFramework до netstandard2.1, microsoft.orleans.core до «3.0.2» и вместо microsoft.orleans.orleanscodegenerator.build (2.2.0) установил Microsoft. Орлеан.CodeGenerator.MSBuild (3.0.2).

С приведенной выше настройкой я получил следующее предупреждение:
предупреждение ORL1001: Тип SomeDetailsDto имеет базовый тип, принадлежащий эталонной сборке. Создание сериализатора для этого типа может не включать важные поля базового типа.

Команды Restore, Build и Publish работают. Однако сгенерированный сериализатор неисправен, из-за чего тесты не выполняются.
Ниже приведен сгенерированный orleans код для того же DTO.

[global::System.CodeDom.Compiler.GeneratedCodeAttribute("OrleansCodeGen", "2.0.0.0"), global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute, global::Orleans.CodeGeneration.SerializerAttribute(typeof(global::SomeDetailsDto))]
    internal sealed class OrleansCodeGenSomeDetailsDtoSerializer
    {
        public OrleansCodeGenSomeDetailsDtoSerializer(global::Orleans.Serialization.IFieldUtils fieldUtils)
        {
        }

        [global::Orleans.CodeGeneration.CopierMethodAttribute]
        public object DeepCopier(object original, global::Orleans.Serialization.ICopyContext context)
        {
            global::SomeDetailsDto input = ((global::SomeDetailsDto)original);
            global::SomeDetailsDto result = (global::SomeDetailsDto)global::System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof(global::SomeDetailsDto));
            context.RecordCopy(original, result);
            return result;
        }

        [global::Orleans.CodeGeneration.SerializerMethodAttribute]
        public void Serializer(object untypedInput, global::Orleans.Serialization.ISerializationContext context, global::System.Type expected)
        {
            global::SomeDetailsDto input = (global::SomeDetailsDto)untypedInput;
        }

        [global::Orleans.CodeGeneration.DeserializerMethodAttribute]
        public object Deserializer(global::System.Type expected, global::Orleans.Serialization.IDeserializationContext context)
        {
            global::SomeDetailsDto result = (global::SomeDetailsDto)global::System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof(global::SomeDetailsDto));
            context.RecordObject(result);
            return (global::SomeDetailsDto)result;
        }
    }

Последний сериализатор проигнорировал Item1 и Item2 кортежа, как указано в предупреждении. Я много искал по этому вопросу и ничего не нашел. Я мог бы использовать собственный сериализатор, как указано в этой документации orleans. . Но у меня есть еще один DTO, который также получает ранее упомянутое предупреждение.

Что может быть лучшим способом решения этой проблемы?


person Manikanta    schedule 13.12.2019    source источник
comment
Вместо этого рассмотрите возможность использования ValueTuple, если Orleans поддерживает его ((int firstGuidId, int secondGuidId)), поэтому вам не нужно объявлять новый тип в первую очередь; Tuple мало что добавляет. Если это невозможно, просто подумайте о том, чтобы не наследовать от Tuple и реализовать равенство/хеширование самостоятельно (где это необходимо), например, при поддержке System.HashCode.   -  person Jeroen Mostert    schedule 13.12.2019


Ответы (1)


Я бы рекомендовал не наследовать от Tuple. Вместо этого поместите эти поля в свой класс, чтобы он выглядел так:

[Serializable]
public sealed class SomeDetailsDto
{
  public SomeDetailsDto(Guid firstGuidId, Guid secondGuidId)
  {
    FirstGuidId = firstGuidId;
    SecondGuidId = secondGuidId;
  }

  public Guid FirstGuidId { get; }

  public Guid SecondGuidId { get; }
}

Обратите внимание, что я также добавил в ваш класс атрибут [Serializable]. Я также рекомендую вам всегда добавлять [Serializable] к типам, которые вы собираетесь сериализовать. Генератор кода сделает все возможное, чтобы сделать вывод, что вы все равно хотите, чтобы сериализатор был сгенерирован для этого типа (поскольку он появляется в сигнатуре метода интерфейса зерен и с помощью других эвристик), но этот процесс нельзя сделать абсолютно точным, поэтому лучше быть явным .

Если вы хотите иметь какой-то общий тип, указывающий, что ваш класс имеет 2 поля (при условии, что это полезно для вас), вы можете определить интерфейс или свои собственные кортежеподобные типы и реализовать это в своем DTO.

Как указано в сообщении с предупреждением о сборке, Tuple определен в сборке особого типа, называемой сборкой только для ссылки. Это означает, что генератор кода Orleans не может видеть ни одно из полей в классе Tuple (эти сборки, содержащие только ссылки, пропускают частные члены и весь код IL, поэтому они вообще отсутствуют). Не зная, какие поля присутствуют в базовом классе, генератор кода не может сгенерировать для него правильный сериализатор. Это ограничение вызвано сборками, состоящими только из ссылок, и в репозитории Orleans обсуждалось, как исправить это ограничение.

Здесь есть запрос функции для поддержки наследования от Tuple и типов коллекций: https://github.com/dotnet/orleans/issues/6158

person Reuben Bond    schedule 13.12.2019
comment
Спасибо за ответ. Что меня беспокоило, так это то, что этой проблемы не было для netstandard 2.0. Не знаю, как целевая структура будет иметь значение. - person Manikanta; 16.12.2019
comment
Во время сборки выбираются разные сборки, когда используется netstandard2.0 и когда используется конкретная целевая платформа. - person Reuben Bond; 16.12.2019