Pull to refresh

Comments 33

В результате мы получим содержимое заказа на текущий момент, а не на момент загрузки самого заказа из базы.
Расшифруйте эту фразу подробнее, пожалуйста.
Имеется в виду что загрузка сущности заказ из базы и обращение к ее свойству OrderLineItem разнесено по времени и находится в разных транзакциях, что в некоторых случаях может быть нежелательно.
Так оберните в одну транзакцию, да еще с каким нибудь «повышенным» уровнем изоляции, например Repeatable read.
Еще, например, почему бы не сделать что бы в
using(var context = new Context())

new Context() не стартовал бы себе транзакцию, либо не стартовать транзакцию еще как нибудь?
При этом подобные ошибки не так то просто отловить на этапе разработки/ревью и подобный код может выстрелить уже в боевой среде.
У меня в привычке всегда использовать SQL Profiler и тогда не приходится ломать голову над правильным составлением linq запросов, сразу видно если что то нужно поправить.
И это правильно, но проверка остается на совести разработчика который писал код, на ревью может проскочить.

И вы это очень быстро увидите, если используете miniProfiler

оставить lazy loading включенным но строго следить за тем что бы все навигационные свойства не были виртуальными
Как тогда будет работать lazy loading?
Там дальше в скобках написано, что в тех редких случаях когда он действительно нужен, делать свойство виртуальным.
Вопрос, сколько запросов будет отправлено в базу? Правильно, 3.
Непонятно: а как правильно написать запрос, чтобы он не ухудшал производительность? Может на таких маленьких примерах Lazy Loading и не нужен, а что если нужно загрузить 100-400 тыс. записей в какой-нибудь combobox или auto-complete, и чтобы ничего не тормозило, что тогда делать? Есть реальный пример?
Два варианта.
1) .Take(n).Skip(m); — то есть пейджинг.
2) .Select(x=> {x.Name, un = x.User.Name}).ToList().Select(x=>x.Name+" "+un);
синтаксис не помню, с головы привел, смысл в том что сначала тянем из базы массово нужную проекцию полей, а потом делаем конкатенацию строк.
если нужно загрузить 100-400 тыс. записей
тогда Lazy Loading точно не нужен!
Но это не значит что нужно убрать virtual у свойств, просто используйте Select() и явную загрузку данных (ToList(), ToArray()...).
А уже после явной загрузки данных, делайте в памяти что хотите.

Чтобы ничего не тормозило — надо загружать нужные данные заранее:


var trades = context.Trades
    .Where(x => tradeIds.Contains(x.Id))
    .Include(x => x.Buyer)
    .Include(x => x.Seller);
щас лень проверять, но думаю если по trades вызвать
foreach(var t in trades)
без ToList(), то будут множественные запросы к таблице Trades, которые будут тормозить систему.

И в чем же вы видите принципиальную разницу между foreach(var t in trades) и вызовом ToList()?


Для справки: реализацию ToList() можно посмотреть на referencesource. И используемый конструктор можно найти там же.

Профилировщик показывает один запрос с where in…

Есть одна очень неприятная особенность EF, при наличии нескольких инклюдов он очень долго генерирует запрос (именно текст запроса). Теоретически должен их кэшировать, но практически кэш очень хрупкий. Пришлось отказаться от инклюдов и жить с lazy, это оказалось в разы быстрее.

Это что же за инклюды у вас? И сколько это получилось — "несколько"?

Обычные 4-6 инклюдов, какой-то системы в этом я не нашел, но поведение зависит от числа инклюдов. Причем генерация может занимать до минуты времени. Вот типичный пострадавший: https://stackoverflow.com/questions/686554/why-is-entity-framework-taking-30-seconds-to-load-records-when-the-generated-que

Не так просто использовать compiled query и это не решит проблему с тормозами, которые первый раз будут все равно.

Первый раз, да. И это не серебряная пуля, да. Но в некоторых сценариях использования может помочь. Например, если у вас серверное приложение, редко выгружаемое из памяти.
Хотя, конечно, в идеале, хотелось бы, чтоб EF быстрее справлялся с .Include. Те числа, которые вы приводите, это уже за гранью )
Представим что в промежутке между загрузкой заказа из базы и обращением к свойству OrderLines содержимое заказа было изменено. В результате мы получим содержимое заказа на текущий момент, а не на момент загрузки самого заказа из базы.
Все таки неплохо было бы эти два предложения переписать/уточнить.

Представим что в промежутке между загрузкой заказа из базы и обращением к свойству OrderLines содержимое заказа было изменено в базе, в другой транзакции.

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

Если нет транзакции, а был изменен OrderLines, то получим несогласованные данные между Order и OrderLines.

В общем эти два предложения надо раскрыть подробнее.
Если нет транзакции, а был изменен OrderLines, то получим несогласованные данные между Order и OrderLines.
да и транзакция не гарантия, надо еще о совместимости транзакций и блокировок подумать.
Я не увидел предлагаемого вами решения.

Не использовать lazy loading, абстагироваться от персистенса и может даже перейти на dapper.

абстагироваться от персистенса
Для решения какой проблемы относящейся к этой статье?

Для устранения протечки абстракции

При использовании lazy loading наши POCO объекты перестают быть POCO

Мне кажется, что использование entity только в «рамках» созданного контекста — это первое, чему учит любой туториал по EF. И за пределы слоя доступа к бд (обычно это отдельный проект?) эти объекты никогда не выходят, а мапятся на POCO.

Вопрос, сколько запросов будет отправлено в базу? Правильно, 3.

Опять же, оператор «Include» в любом туториале в разделе о lazy loading'е всегда упоминается. Подобные ошибки — это незнание инструмента, а не недостаток EF.

EF Code First рекламировался как способный понимать в качестве Entity любые POCO, а не только специально подготовленные классы. Так что вылезают за пределы слоя доступа к БД они часто.

Сталкивался с такой же проблемой, большое количество запросов в базу за одиночными записями.
Индикатором, того, что EF выполняет запросы по Lazy Loading как правило является @EntityKeyValue1.
Во первых можно сделать «eager loading»: как сказал уважаемый mayorovp добавить INCLUDE
Во вторых можно управлять на уровне контекста: Context.Configuration.LazyLoadingEnabled = false;

Вот тут объясняется про Lazy Loading и как с этим можно бороться.

Очень хотелось бы иметь возможность контролировать lazy/eager выборочно, но увы, можно только на уровне контекста задать поведение для всех.

Статья верна для EF версии 6 включительно. В Entity Framework Core lazy loading отсутствует. Видимо Microsoft согласны с автором в его выводах)
Sign up to leave a comment.

Articles