Comments 19
А у нас была задача заставить авро сериализатор работать с динамическими типами. Проще всего было генерить тип на лету.
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)
Но не уверен, что у автора коммента стояла такая задача)) Поэтому соглашусь про ExpandoObject)
Задача: копировать данные из azure tables и сохранять их в компактном бинарном формате. Когда мы берем данныиз из таблиц, то они попадают к нам в полуструктурированном виде как IEnumerable<IDictionary<string,object>>. Тоесть у нас типа вообще нет. Но мы знаем что строки все пишутся туда примерно в одном и томже виде, тоесть имеют один и тот же набор полей. Поэтому мы проходим первые N строк и формируем описательный обьект IDictionary<string,Type>, теперь мы знаем схему данных, но типа у нас нет. Мы формируем тип данных динамически, как было описано выше, назовем его как NewType.
Зачем это надо?
Дальше мы могли бы в случае например Json сериализатора просто скормить ему IDictionary<string,object> или же NewType и результат был бы идентичным, так как там не требуется схема данных. В случае же бинарных форматов нам необходимо описать схему в виде требуемым конкретным сериализатором. Хотя avro сериализатор и умеет работать с динамическими типами, но при создании мы должны ему задать схему.
Итак нам надо сформировать описание схемы в авро формате. И тут проблема, что это хоть и возможно сделать как функцию String GetAvroSchema(IDictionary<string,Type> schema), но довольно сложно. Плюс когда мы начинаем добавлять другие сериализаторы нам потребуется писать еще одну функцию для каждого, например для protobuff String GetProto3Schema(IDictionary<string,Type> schema) и дальше будет куча велосипедов. В данном случае гораздо удобнее сгенерировать один тип и дальше создавать сериализаторы нормальным путем.
Далее стоит вопрос скорости работы всего этого дела. Динамический подход работает очень меееедлеееенннннноооо. Проще сделать уже сериализатор полностью на основе типа и писать туда обьекты этого типа, сериализаторы могут использовать тогда различные оптимизационные трюки. Вопрос только в том, как нам создать эффективную функцию NewType Cast(IDictionary<string,object> obj)? Но и тут нам помогает тип. Так как многие источники также умеют работать и с типами, мы можем задать типизированный доступ к azure table.
И копирование у нас будет тривиальным:
Зачем это надо?
Дальше мы могли бы в случае например 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, если у нас уже есть массив байт в который сериализован конкретный тип (момент сериализации мы не можем менять) и надо его десериализовать. Я так понимаю, можно избежать включения классов по ссылке, заменив динамически, но вот с десиарилизацией
Также не совсем понимаю (может спросонья), как поможет 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;
}
}
Но все типы уже предварительно загружены.
Осталось понять, почему нельзя было сменить формат передачи данных на портал. Десериализовать объекты там где это сделать проще всего, и сериализовать их уже в 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 объектов разный? Т.е. все поля формально есть но в другом порядке или поле добавилось.
ну да, компилиться всегда должно в комлексе
Sign up to leave a comment.
.Net Бинарная сериализация без ссылки на сборку с исходным типом или как договориться с BinaryFormatter