Comments 16
Да, всё верно. Кодогенерация — тоже способ решения подобных задач. Впрочем, у этого есть и минусы: захламление кодовой базы и необходимость её поддержки, так как модели могут (и будут) меняться.
Кстати, нет ли у вас под рукой хорошей статьи по использованию Roslyn для кодогенерации? Чтоб, так сказать, «на пальцах». Давно хотел освоить, но видел только какие-то сложные примеры.
Кстати, нет ли у вас под рукой хорошей статьи по использованию Roslyn для кодогенерации? Чтоб, так сказать, «на пальцах». Давно хотел освоить, но видел только какие-то сложные примеры.
Спасибо большое, интересная статья.
Однако, в комментариях правильно заметили: «суть автомаппера не банальное перекладывание из одного в другое, в общем-то проекции, возможность инъекции...».
В своей статье я дал лишь самый простой пример — копирование свойства в свойство. Проекции я не создавал, типы не изменял, данные в конструктор не пробрасывал и т.п. Как вы думаете, с помощью кодогенерации возможно будет задать какие-то дополнительные правила преобразования из одного типа в другое? Или организовать взаимосвязь мапперов, когда в одном объекте есть другой, на который тоже существует схема маппинга?
Мне кажется, что это сложная задача для кодогенерации. Впрочем, я в ней не специалист.
Да, я понял, что вы поддерживаете использование AutoMapper :)
Однако, в комментариях правильно заметили: «суть автомаппера не банальное перекладывание из одного в другое, в общем-то проекции, возможность инъекции...».
В своей статье я дал лишь самый простой пример — копирование свойства в свойство. Проекции я не создавал, типы не изменял, данные в конструктор не пробрасывал и т.п. Как вы думаете, с помощью кодогенерации возможно будет задать какие-то дополнительные правила преобразования из одного типа в другое? Или организовать взаимосвязь мапперов, когда в одном объекте есть другой, на который тоже существует схема маппинга?
Мне кажется, что это сложная задача для кодогенерации. Впрочем, я в ней не специалист.
Да, я понял, что вы поддерживаете использование AutoMapper :)
А в автомаппере, поле удалилось, ну и хрен с ним, тихонько игнорируем.
configuration.AssertConfigurationIsValid(); позволяет не игнорировать тихонько, а получить явный exception при старте приложения.
Ну что же. Добавьте в свой велосипед еще скорости FastExpressionCompiler .
И все-таки странно что Automapper слил, есть им еще куда стремиться, хотя думал по той тропинке столько зверей потопталось, что дальше уже некуда.
И все-таки странно что Automapper слил, есть им еще куда стремиться, хотя думал по той тропинке столько зверей потопталось, что дальше уже некуда.
Автомапер, как мне кажется, сложнее внутри, а так идея одна и та же. Следущий шаг — генерация байткода, но это потребует куда больших усилий.
А зачем? Этот велосипед, как я писал, нужен для демонстрации работы с ExpressionTrees. AutoMapper значительно мощнее. Если у меня когда-нибудь (по объективным замерам) возникнут проблемы с производительностью там, где происходит маппинг — возможно я вернусь к этому велосипеду.
FastExpressionCompiler посмотрю, спасибо. Есть у меня мысль написать про то, как я парсил JS и компилировал его с помощью ExpressionTrees — думаю там это может пригодиться.
FastExpressionCompiler посмотрю, спасибо. Есть у меня мысль написать про то, как я парсил JS и компилировал его с помощью ExpressionTrees — думаю там это может пригодиться.
teoadal почему вы решили строить выражение как функцию с отдельным созданием экземпляра и последовательным присвоений значений из сущности в ДТО, а не построение просто лямбда-выражения? Вы ведь обычно пишите запрос через ORM как .Select(x => new T { Field = x.Field }), а не .Select(x => { var y = new T(); y.Field = x.Field; }). Мне кажется такое вариант куда ближе к ОРМ-ным и проще им парсится
То же делали маппер на проекте, но через Expression.MemberInit + набор Expression.Bind
То же делали маппер на проекте, но через Expression.MemberInit + набор Expression.Bind
public Expression<Func<TEntity, TDto>> GetMapExpr<TEntity, TDto>()
{
var entityType = typeof(TEntity);
var entityParam = Expression.Parameter(entityType, "x");
var entityProps = entityType.GetProperties();
var dtoType = typeof(TDto);
var dtoProps = dtoType.GetProperties();
var memberExpressions = ... сличение dtoProps и entityProps и построение Expresion.Bind ...
var newDTO = Expression.MemberInit(Expression.New(dtoType), memberExpressions);
var selector = (Expression<Func<TEntity, TDto>>)Expression.Lambda(newDTO, entityParam);
return selector;
}
Я сделал так, чтобы было больше похоже на то, что делалось с помощью Reflection (PropertyInfo.GetValue/SetValue). Текст больше для тех, кто не очень разбирается в ExpressionTrees, поэтому хотелось, чтобы переход был плавный.
Также, у меня немного другая ситуация: аргумент функции типа object, а не TEntity. То есть мне в самом начале нужно сделать приведение типа и сохранить результат в переменную, а значит без тела (Expression.Body) как мне кажется не получится.
При этом вы правы, чтобы сократить количество действий можно было формировать набор Expression.Bind и в самом конце сделать Expression.MemberInit. Т.е. скомпилированный код будет вида:
А код построения функции:
Также, у меня немного другая ситуация: аргумент функции типа object, а не TEntity. То есть мне в самом начале нужно сделать приведение типа и сохранить результат в переменную, а значит без тела (Expression.Body) как мне кажется не получится.
При этом вы правы, чтобы сократить количество действий можно было формировать набор Expression.Bind и в самом конце сделать Expression.MemberInit. Т.е. скомпилированный код будет вида:
x =>
{
var source = (TIn) x;
return new TOut
{
Property = source.Property;
...
}
}
А код построения функции:
private Func<object, TOut> BuildConverter(Type sourceType)
{
var parameter = Expression.Parameter(typeof(object), "x");
var source = Expression.Variable(sourceType, "source");
var bindings = sourceType.GetProperties()
.Where(p => _outProperties.ContainsKey(p.Name))
.Select(p => Expression.Bind(_outProperties[p.Name], Expression.Property(source, p)));
var body = Expression.Block(new[] {source},
Expression.Assign(source, Expression.Convert(parameter, sourceType)),
Expression.MemberInit(Expression.New(_outConstructor), bindings));
return Expression.Lambda<Func<object, TOut>>(body, parameter).Compile();
}
Попробуйте FastExpressionCompiler, и не потому что компиляция быстрее (может для вас время старта не важно), а по этому что скомпилированный делегат может быть быстрее. Иногда, значительно.
Эх, хорошая штука эти ExpressionTrees. Я, правда, маппер не писал, но атрибутные валидацию данных и правовые фильтры, а также кастомные фильтры/сортировки для IQueriable прикручивал.
Да, мне тоже очень нравится, но только для простых задач. Например, парсить и создавать в функции из JavaScript — ад. Там возникает сразу целая куча проблем вроде того, что ExpressionTrees иногда сложно составлять «по ходу» кода. Иногда нужно заглянуть на пару шагов вперед, чтобы выражение получилось правильное.
Sign up to leave a comment.
Свой mapper или немного про ExpressionTrees