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

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

Пара замечаний:

  1. Вместо dbContext.Attach() + …IsModified = true нужно использовать dbContext.Entry(entity).State = EntityState.Modified — это даже в дефолтных примерах написано.

  2. Для быстрых изменений можно использовать Linq2Db EfCore — там появляются расширения для массовых обновлений.

  1. Уточнение: в тестовом сценарии нам необходимо отредактировать только Name, поэтому мы намеренно используем `IsModified = true` только для этого свойства. При использовании `IsModified = true` EF сгенерирует код идентичный коду в Dapper варианте репозитория, где Update применяется только для свойства Name. Если попытаться использовать в данном сценарии `dbContext.Entry(entity).State = EntityState.Modified`, EF попытается в Update обновить все свойства в БД из обьекта, большинство из которых будут null.

  2. Много слышал про Linq2Db, наверное стоит попробовать) Спасибо !

EF Improved / Get detailed product by Id - "Ух б**" сказали мужики...
EF Improved / Get detailed products page - "Ух б**" сказали мужики...
EF Improved / Edit product - "Ага б** !" - Сказали мужики и пошли использовать Dapper...

Зачем, можно ведь использовать `dbContext.Database.ExecuteSqlRaw` который работает с такой же производительностью :)

Однако стоит учитывать что ваш DbContext по сути становится синглтоном

А как это интегрируется в многопоточную среду ASP.NET Core, учитывая, что сам DbContext по сути однопоточен?

Тут имеется ввиду что при использовании пула экземпляр DbContext из пула будет жить все время пока приложение работает. На каждый запрос от пользователя создается scope, и для каждого scope из пула достается один экземпляр DbContext из пула. DbContext в обоих (с пулом и без) случаях используется как scoped зависимость и в рамках одного запроса им может пользоваться только один поток. Если у вас есть многопоточные сценарии в рамках одного запроса, тогда вам в обоих случаях нужно резолвить еще один DbContext.

НЛО прилетело и опубликовало эту надпись здесь

Для всех сценариев из статьи, SQL код, который генерирует EFCoreImprovedProductsRepository, идентичен коду из DapperProductsRepository.

НЛО прилетело и опубликовало эту надпись здесь

Нет информации о машине на которой производились тесты. Во сколько cpu работал сервис? Во сколько cpu работал нагрузочный тул? Какое было потребление памяти? Был ли тротлинг (показатели температуры процессора)? Без этого непонятно была ли борьба за ресурсы у компьютера

Нет информации о времени ответа (включая квантили 95 и 99), о среднеквадратичном отклонении, о количестве ошибок. Не было сказано о прогреве сервиса перед стартом. Ещё было бы неплохо прогнать тесты 10 раз и выбрать худшие/лучшие результаты

С большей частью описанной вами информации вы можете ознакомится в отсчетах NBomber и BenchmarkDotNet, которые я сохранил в репозитории.
В NBomber тесте прогрев присутствовал и каждый сценарий работал на протяжении 3-х минут. В BenchmarkDotNet прогрев есть по умолчанию, а каждый сценарий прогонялся 20 раз.

UPD: ошибся на счет 3-х минут, каждый сценарий работал 30 секунд :)

1) DbContext pooling "Однако стоит учитывать что ваш DbContext по сути становится синглтоном"

2) DbContext в Entity Framework Core, не поддерживает сценарии работы с несколькими потоками.

Чего? как это работает? Почему тут противоречивые абзацы? Если Пулинг возвращает синглетон и мы не отключали "потокобезопасность" , asp.net или webapi это же многопоточные приложения , как это всё работает?

Отвечал в комментарии выше, но на всякий случай продублирую:

Тут имеется ввиду что при использовании пула экземпляр DbContext из пула будет жить все время пока приложение работает. На каждый запрос от пользователя создается scope, и для каждого scope из пула достается один экземпляр DbContext. DbContext в обоих (с пулом и без) случаях используется как scoped зависимость и в рамках одного запроса им может пользоваться только один поток. Если у вас есть многопоточные сценарии в рамках одного запроса, тогда вам в обоих случаях нужно резолвить еще один DbContext.

Формулировка с синглтоном присутствует и в документации к EF, я привел эту формулировку почти дословно:

Context pooling works by reusing the same context instance across requests; this means that it's effectively registered as a Singleton, and the same instance is reused across multiple requests (or DI scopes).

Если честно, никогда не приходилось использововать запросы в бизнес-коде PUT от клиента и сразу в БД. Как можно не удостовериться в валидности запроса, наличии прав, доступном состоянии сущности и т.п. без ее предварительного получения. Поэтому кейс с Edit-ом хороший, но не очень применимый в жизни. По крайней мере, в моей :)

Как известно, запросы на запись значительно медленнее запросов на чтение на уровне баз данных, что отлично заметно по разнице Dapper для Get/Edit в районе 2-х раз. И если у гет запросов можно включить AsNoTracking и получить практически паритет EF - Dapper. То в случае создания новой сущности ее все равно придется отслеживать для получения PK, например. Поэтому было бы очень интересно сравнить POST у EF и Dapper. При условии, что запись медленная в БД, то может трекинг не будет совсем влиять относительно самого запроса :)

P.S.: мне казалось, что я где-то видел или сталкивался, что для каждого Include в EF надо указывать свой AsNoTracking, но могу и ошибаться. Если это так, то совсем неудобно использовать в коде и думать об этом.

P.P.S.: я бы не стал всем рекомендовать налево и направо использование AsNoTracking еще потому, что не всегда разумно вытягивать одним запросом с кучей join-ов информацию. Особенно, когда там есть пересечения в Foreign Keys. У БД есть проблема какого-то там взрыва, когда из-за join-ов приходится много данных одинаковых дублировать, потом эти же дубли передавать по сети и т.п. В EF Core для этого вводили фичу сплита запроса на несколько разных как раз из-за этого. Поэтому ребята на Dapper могут упереться в это и будут руками объединять эти запросы, а так за нас сделает все Change Tracking в EF из коробки :)

На счет POST это мое упущение, почему то забыл добавить этот сценарий :) Исправил в статье и материалах. К сожалению проблема скорее всего именно в EF change tracking как для POST так и для PUT, потому что raw sql код EF работает так же быстро как и код на Dapper. Будем ждать следующего релиза, там как раз обещают оптимизировать эти сценарии.

P.S.: AsNoTracking указывается для всего запроса и согласно описанию:

The change tracker will not track any of the entities that are returned from a LINQ query.

P.P.S.: Очень хороший поинт ! Я думал о том чтобы включить пункт про сплит в статью, но к сожалению мне не хватило усидчивости :) К тому же, на мой взгляд, тут нужно подходить больше со стороны БД чем EF. Было бы здорово если бы кто то смог осветить тему эффективности разделения запросов в отдельной статье.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации