Тонкости Lambda Expressions в C#

То, о чем написано в статье, я отлавливал около 10 часов, это были 10 часов непрерывного дебага, которые cвелись к пошаговому сравнению рабочей и нерабочей версий кода, даже не так, к сравнению каждой строчки из окошка дебага рабочей и не рабочей версий кода

Для программиста, не знакомого с деревьями выражений хорошая вводная статья на msdn, в ней подробно раскрываются некоторые шаги в построении дерева, которые я опустил в статье.

В каком случае этот тест будет зеленым (включена надстройка EF, при которой он падает, если не может полностью транслировать запрос в SQL).

oeewrbsquql_hpccdqubza8eneo.png


  • Во-первых, должен быть определен маппинг поля PupilName, если его не определить Query Provider не узнает, на что нужно спроецировать свойство PupilName, чтобы дописать в SQL ORDER BY.
  • Во-вторых, провайдер должен разобрать этот маппинг, вот, такой, например, не пойдет, EF начнет ругаться (по умолчанию не ругается, поднимает сущности в память — LINQ to Object, но это можно включить):

    tjzmv5o6fvxeyswvjzosu8ncbyg.png


А все дело в том, что Query Provider не понимает по какому свойству (свойствам) сущности мы хотим сортировать. Но если маппинг написан правильно, то провайдер справится, отправит SQL запрос, получит ответ и десериализует результаты.

Иногда возникает необходимость написания Expression’ов самому, т.е что-то похожее:

fw8t7ohyj7fpruatyldj7ihp4fq.png

Я покажу, как написать этот Expression, это не так сложно, по ссылке выше приводятся несколько похожих примеров:

f2chodjb09-ot3axnhjh8o4nnjk.png

Мы по шагам собираем лямбду, сначала параметр, свойство, а потом склеиваем все.
В каком случае это не будет работать и тест будет зеленым (провайдер не сможет разобрать expression)?

Не знаете? Не огорчайтесь, я тоже не знал, и в итоге потратил около 10 часов, чтобы понять.
Вот подсказка, так все будет работать:

jwfrumf54jr2svyqxhyuqckwpyo.png


PupilDto наследуется от PupilDtoBase

o4enejjq3czuyrzwywvthzruh7u.png

Все дело в том, что PropertyInfo нужно брать из базового класса, если само свойство PupilName принадлежит базовому классу, так C# строит lambda expression, и EF разбирает их, полагаясь на стандарты языка


Пруфы

Что показывает debugger, если написать обычную лямбду в OrderBy:

i9-kdmaept9u3y-3eg2lomc2qqq.png

А в нашем случае так:

y_hwe1n1t41uzej7d79nvjkc-da.png


AutoMapper и IncludeBase

Если вы старайтесь не дублировать код, то наверняка сталкивались с наследованием DTO и IncludeBase:

turdj9vf5lwcfssnudp2ngaynh8.png

Бывает еще более изощренная ситуация:

qxd1kspunr_hcopovd6ocuyk7de.png

И этот тест будет зеленым:

p8x32fwfxaqcz1zxmrfndsqd4kq.png

А дело в том же самом, снова PropertyInfo подтягивается из интерфейса:

b-o2ng71ovmuy3clpfzszdim-t0.png


Пруфы

ReflectedType свойства Age это интерфейс, IPupilDto, С# строит лямбду, в которой свойство Age — свойство класса PupilDto, а не интерфейса, а вот, как строит лямбду автомаппер:

4g-z36alnp_8wk0xiengm2oo4cw.png

Как же решить эту проблему? Если IncludeBase Automapper’a с интерфейсом нас не устраивает (если вы используете маппинг в памяти — это вас не коснется), то придется отказаться от этого API, я решил эту проблему выделив маппинг в extension метод, вот так:

sedmvpn6a8binykrlj6uu-wqppi.png

Тогда автомаппер сам найдет подходящее свойство типа по имени, построит «правильную» лямбду
Спасибо за внимание!

© Habrahabr.ru