Как стать автором
Обновить

Комментарии 16

НЛО прилетело и опубликовало эту надпись здесь
Да, всё верно. Кодогенерация — тоже способ решения подобных задач. Впрочем, у этого есть и минусы: захламление кодовой базы и необходимость её поддержки, так как модели могут (и будут) меняться.

Кстати, нет ли у вас под рукой хорошей статьи по использованию Roslyn для кодогенерации? Чтоб, так сказать, «на пальцах». Давно хотел освоить, но видел только какие-то сложные примеры.
НЛО прилетело и опубликовало эту надпись здесь
Спасибо большое, интересная статья.

Однако, в комментариях правильно заметили: «суть автомаппера не банальное перекладывание из одного в другое, в общем-то проекции, возможность инъекции...».

В своей статье я дал лишь самый простой пример — копирование свойства в свойство. Проекции я не создавал, типы не изменял, данные в конструктор не пробрасывал и т.п. Как вы думаете, с помощью кодогенерации возможно будет задать какие-то дополнительные правила преобразования из одного типа в другое? Или организовать взаимосвязь мапперов, когда в одном объекте есть другой, на который тоже существует схема маппинга?

Мне кажется, что это сложная задача для кодогенерации. Впрочем, я в ней не специалист.

Да, я понял, что вы поддерживаете использование AutoMapper :)

НЛО прилетело и опубликовало эту надпись здесь
А в автомаппере, поле удалилось, ну и хрен с ним, тихонько игнорируем.

configuration.AssertConfigurationIsValid(); позволяет не игнорировать тихонько, а получить явный exception при старте приложения.
НЛО прилетело и опубликовало эту надпись здесь
Ну что же. Добавьте в свой велосипед еще скорости FastExpressionCompiler .
И все-таки странно что Automapper слил, есть им еще куда стремиться, хотя думал по той тропинке столько зверей потопталось, что дальше уже некуда.

Автомапер, как мне кажется, сложнее внутри, а так идея одна и та же. Следущий шаг — генерация байткода, но это потребует куда больших усилий.

А зачем? Этот велосипед, как я писал, нужен для демонстрации работы с ExpressionTrees. AutoMapper значительно мощнее. Если у меня когда-нибудь (по объективным замерам) возникнут проблемы с производительностью там, где происходит маппинг — возможно я вернусь к этому велосипеду.

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
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. Т.е. скомпилированный код будет вида:
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 иногда сложно составлять «по ходу» кода. Иногда нужно заглянуть на пару шагов вперед, чтобы выражение получилось правильное.
Да, в сложных случаях получается как с регулярками — оно работает, но чтобы понять как — надо сделать реверс-инжиринг :). Но в отличие от регулярок тут хоть код можно хорошо оформить, комментарии оставить.
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации