Pull to refresh

Comments 19

UFO just landed and posted this here
А у нас была задача заставить авро сериализатор работать с динамическими типами. Проще всего было генерить тип на лету.
        public static Type CreateType(this IDictionary<string, PropertySchema> properties, string typeName)
        {
            var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(Guid.NewGuid().ToString()), AssemblyBuilderAccess.Run);
            ModuleBuilder module = assemblyBuilder.DefineDynamicModule("tmp");
            
            TypeBuilder typeBuilder = module.DefineType(typeName, TypeAttributes.Public | TypeAttributes.Class);

            AddAttribute<DataContractAttribute>(typeBuilder);
            // Loop over the attributes that will be used as the properties names in out new type
            foreach (var prop in properties)
            {
                string propertyName = prop.Key;

                var propType = prop.Value.Type;

                if (propType == typeof(TimeSpan)) propType = typeof(DateTime);//timespan is not supported by avro core lib

                // Generate a private field
                FieldBuilder field = typeBuilder.DefineField("_" + propertyName, propType, FieldAttributes.Private);
                // Generate a public property
                PropertyBuilder property =
                    typeBuilder.DefineProperty(propertyName,
                                     PropertyAttributes.None,
                                     propType,
                                     new Type[] { propType });


                if (prop.Value.IsNullable) {
                    AddAttribute<NullableSchemaAttribute>(property);
                }
                AddAttribute<DataMemberAttribute>(property);

                MethodAttributes GetSetAttr =
                    MethodAttributes.Public |
                    MethodAttributes.HideBySig;

                MethodBuilder currGetPropMthdBldr =
                    typeBuilder.DefineMethod("get_value",
                                               GetSetAttr,
                                               propType,
                                               Type.EmptyTypes);

                ILGenerator currGetIL = currGetPropMthdBldr.GetILGenerator();
                currGetIL.Emit(OpCodes.Ldarg_0);
                currGetIL.Emit(OpCodes.Ldfld, field);
                currGetIL.Emit(OpCodes.Ret);

                MethodBuilder currSetPropMthdBldr =
                    typeBuilder.DefineMethod("set_value",
                                               GetSetAttr,
                                               null,
                                               new Type[] { propType });

                ILGenerator currSetIL = currSetPropMthdBldr.GetILGenerator();
                currSetIL.Emit(OpCodes.Ldarg_0);
                currSetIL.Emit(OpCodes.Ldarg_1);
                currSetIL.Emit(OpCodes.Stfld, field);
                currSetIL.Emit(OpCodes.Ret);

                property.SetGetMethod(currGetPropMthdBldr);
                property.SetSetMethod(currSetPropMthdBldr);
            }

            return typeBuilder.CreateType();
        }

Зачем этот огород, если есть ExpandoObject и dynamic?

Нужно когда требуется выжать перформанс, т.к. оно работает ГОРАЗДО быстрее ExpandoObject и dynamic.
Но не уверен, что у автора коммента стояла такая задача)) Поэтому соглашусь про ExpandoObject)
Задача: копировать данные из azure tables и сохранять их в компактном бинарном формате. Когда мы берем данныиз из таблиц, то они попадают к нам в полуструктурированном виде как IEnumerable<IDictionary<string,object>>. Тоесть у нас типа вообще нет. Но мы знаем что строки все пишутся туда примерно в одном и томже виде, тоесть имеют один и тот же набор полей. Поэтому мы проходим первые N строк и формируем описательный обьект IDictionary<string,Type>, теперь мы знаем схему данных, но типа у нас нет. Мы формируем тип данных динамически, как было описано выше, назовем его как NewType.

Зачем это надо?
Дальше мы могли бы в случае например Json сериализатора просто скормить ему IDictionary<string,object> или же NewType и результат был бы идентичным, так как там не требуется схема данных. В случае же бинарных форматов нам необходимо описать схему в виде требуемым конкретным сериализатором. Хотя avro сериализатор и умеет работать с динамическими типами, но при создании мы должны ему задать схему.

const string Schema = @"{
                            ""type"":""record"",
                            ""name"":""Microsoft.Hadoop.Avro.Specifications.SensorData"",
                            ""fields"":
                                [
                                    {
                                        ""name"":""Location"",
                                        ""type"":
                                            {
                                                ""type"":""record"",
                                                ""name"":""Microsoft.Hadoop.Avro.Specifications.Location"",
                                                ""fields"":
                                                    [
                                                        { ""name"":""Floor"", ""type"":""int"" },
                                                        { ""name"":""Room"", ""type"":""int"" }
                                                    ]
                                            }
                                    },
                                    { ""name"":""Value"", ""type"":""bytes"" }
                                ]
                        }";

        //Create a generic serializer based on the schema
        var serializer = AvroSerializer.CreateGeneric(Schema);
        var rootSchema = serializer.WriterSchema as RecordSchema;

        //Create a memory stream buffer
        using (var stream = new MemoryStream())
        {
            //Create a generic record to represent the data
            dynamic location = new AvroRecord(rootSchema.GetField("Location").TypeSchema);
            location.Floor = 1;
            location.Room = 243;

            dynamic expected = new AvroRecord(serializer.WriterSchema);
            expected.Location = location;
            expected.Value = new byte[] { 1, 2, 3, 4, 5 };

            Console.WriteLine("Serializing Sample Data Set...");

            //Serialize the data
            serializer.Serialize(stream, expected);


Итак нам надо сформировать описание схемы в авро формате. И тут проблема, что это хоть и возможно сделать как функцию String GetAvroSchema(IDictionary<string,Type> schema), но довольно сложно. Плюс когда мы начинаем добавлять другие сериализаторы нам потребуется писать еще одну функцию для каждого, например для protobuff String GetProto3Schema(IDictionary<string,Type> schema) и дальше будет куча велосипедов. В данном случае гораздо удобнее сгенерировать один тип и дальше создавать сериализаторы нормальным путем.

public static class AvroExtensions {
        public static string GetAvroSchemaFromType(Type t) {
            var m = typeof(AvroExtensions).GetMethod(nameof(GetAvroSchema));
            var ctor = m.MakeGenericMethod(new[] { t });
            return (string)ctor.Invoke(null, null);
        }

        public static string GetAvroSchema<T>()
        {
            var s = AvroSerializer.Create<T>();
            return s.WriterSchema.ToString();
        }
    }


Далее стоит вопрос скорости работы всего этого дела. Динамический подход работает очень меееедлеееенннннноооо. Проще сделать уже сериализатор полностью на основе типа и писать туда обьекты этого типа, сериализаторы могут использовать тогда различные оптимизационные трюки. Вопрос только в том, как нам создать эффективную функцию NewType Cast(IDictionary<string,object> obj)? Но и тут нам помогает тип. Так как многие источники также умеют работать и с типами, мы можем задать типизированный доступ к azure table.
public IEnumerable<T> GetEntities<T>(string table) where T : ITableEntity, new()
    {
        var table = _tableClient.GetTableReference(table);
        var query = table.CreateQuery<T>();
        //...
        return rows;
    } 


И копирование у нас будет тривиальным:
public static void Copy<T>()
        {
            var s = Source.Create<T>();
            var d = Dest.Create<T>();
            foreach (var item in s)
            {
                Dest.write(item);
            }
        }

        public static void Copy(Type t)
        {
            var m = typeof(AvroExtensions).GetMethod(nameof(Copy));
            var ctor = m.MakeGenericMethod(new[] { t });
            return (string)ctor.Invoke(null, null);
        }
Как я писал, dynamic — это не сферический тип в вакууме, а вполне кокретный, просто на этапе компиляции мы о нем не знаем, но в рантйме он вполне конкретный и чтобы его создать требуется информация о типе.
Также не совсем понимаю (может спросонья), как поможет ExpandoObject, если у нас уже есть массив байт в который сериализован конкретный тип (момент сериализации мы не можем менять) и надо его десериализовать. Я так понимаю, можно избежать включения классов по ссылке, заменив динамически, но вот с десиарилизацией

Когда у меня возникла проблема с десериализацией объектов, нашел где-то:


public sealed class dsreplace : System.Runtime.Serialization.SerializationBinder
{
    public override Type BindToType(string assemblyName, string typeName)
    {
        var outType =
                   Type.GetType(String.Format("{0}, {1}", typeName, assemblyName), false)
               ?? Type.GetType(String.Format("{0}, {1}", typeName, assemblyName), false, false)
               ?? Type.GetType(String.Format("{0}", typeName), false, false));
        return outType;
    }
}

Но все типы уже предварительно загружены.

Это не сработает для Dictionary
Осталось понять, почему нельзя было сменить формат передачи данных на портал. Десериализовать объекты там где это сделать проще всего, и сериализовать их уже в xml.
Потому что было в базе уже огромное количество данных сериализованных таким неудобным способом. Давайте я еще раз уточню, чтобы не было путаницы: мы говорим не о сериализации для передачи на портал, а о том что часть результов нашего приложения хранится в базе в сериализованом виде.
В базе портала или приложения? Если первое, то что они там вообще делали когда их прочитать было невозможно? Если второе, то что мешает сменить формат в процессе передачи на портал? Если база — общая, то зачем вообще xml?
В базе приложения. и они автоматом уже были переданы в базу портала. Опять же если с самого начала об этом подумали — все было бы по другому

Вы не пытались подписаться на AssemblyResolve и переопределить сборку?


    AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
    // ...

    private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
        if (args.Name == "foo")
            return typeof(Bar).Assembly;

        return null;
    }

Еще есть событие TypeResolve, на случай если имена типов разные.


Достоинство этого метода — в том, что имя типа парсится системными средствами.

Хм, вот этот способ упустил, действительно. Спасибо

А оно ведь сломается если layout объектов разный? Т.е. все поля формально есть но в другом порядке или поле добавилось.

ну да, компилиться всегда должно в комлексе
Хотя, если десерелизация через рефлексию происходит (надо проверить) и используются имена полей — тогда все ок
Вообще-то «deserialization constructor» далеко не вчера «изобрели» :-) Он с нами, емнип, со времен появления ISerializable. Так что, все там проще на много в этом плане.
Only those users with full accounts are able to leave comments. Log in, please.