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

У меня есть проект с 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.Orleans.CodeGenerator.MSBuild (3.0.2).

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

Команды восстановления, сборки и публикации работают. Однако сгенерированный сериализатор неисправен, из-за чего не проходят тесты.
Ниже приведен сгенерированный код 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 кортежа, как указано в предупреждении. Я много искал по этой проблеме и ничего не нашел. Я мог бы использовать специальный сериализатор, как указано в этой документации по Орлеану. Но у меня есть другой DTO, который также получает ранее упомянутое предупреждение.

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

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определяется в специальном типе сборки, называемой сборкой только для ссылки. Это означает, что генератор кода Орлеана не может видеть ни одно из полей вTupleclass (эти сборки, предназначенные только для справки, не содержат закрытых членов и всего кода IL, поэтому они вообще отсутствуют). Не зная, какие поля присутствуют в базовом классе, генератор кода не может сгенерировать для него правильный сериализатор. Это ограничение вызвано сборками только для ссылок, и в репозитории Orleans было некоторое обсуждение того, как исправить это ограничение.

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

Другие вопросы по тегам