Небольшие заметки

Про Entity Framework и DDD

Written on September 3, 2019

Обычно так случалось, что если архитектура у нас по DDD, а проект более менее большой и серьёзный, то мы как правило разделяли доменную и дата модели на две отдельные, потому что Entity Framework в своих ранних версиях накладывал много ограничений и специфичных требований на модели, но в процессе взросления EF эти ограничения становились всё меньше и теперь уже Entity Framework Core позволяет в качестве и доменных и дата моделей использовать одну и ту же модель не идя на компромиссы. А как это делать можно прочитать в статьях Джули Лерман:

  1. DDD-Friendlier EF Core 2.0
  2. DDD-Friendlier EF Core 2.0, Part 2

Или посмотреть в её выступлении на NDC Conference: Mapping DDD Domain Models with EF Core 2.1

И ещё есть хорошая статья на хабре: Сущности в DDD-стиле с Entity Framework Core

Подход с объединением моделей проще, потому что при использовании отдельных моделей для домена и дата слоя необходимо решить проблему отслеживания изменений в доменных сущностях и правильного переноса этих изменений в дата модели, так чтобы Entity Framework смог сохранить всё корректно. Готовой статьи на эту тему дать не смогу, но, в принципе, эта проблема релевантна проблеме под названием “Работа с отсоединёнными сущностями” - в случае, когда доменные и дата модели разделены, доменные модели это по сути и есть отсоединённые сущности, так что идеи как решить проблему синхронизации доменных и дата моделей можно почерпнуть, изучая как решают проблему работы с отсоединёнными сущностями. А статья на эту тему, например, вот: Доступ к данным - Обработка состояния отсоединенных сущностей в EF всё той же Джули Лерман.

Ну и если говорить о DDD, то ещё можно затронуть тему Спецификаций и в статье ниже отличный пример того, как можно реализовать спецификации для использования в EF: EntityFramework: (анти)паттерн Repository

Комментировать

Прерывание выполнения потока в .NET Core

Written on August 28, 2019

В .NET Core выпилили метод Thread.Abort() и это значит, что теперь невозможно насильственное прерывание выполнения потока извне этого потока средствами управляемого кода.

Так что все эти знания о нюансах использования Thread.Abort() и чудесных способностях выбираться из try/catch блоков исключения ThreadAbortException, которые мы заучивали в Рихтере, теперь бесполезны - сегодня выкидывание ThreadAbortException невозможно.

Цитата из документации:

Even though this type exists in .NET Core, since Abort is not supported, the common language runtime won’t ever throw ThreadAbortException.

В этом Issue на GitHub подробное обсуждение плюсов и минусов такого решения.

Если кратко о причинах, то это было сделано, потому что очень дорого писать библиотеки, которые гарантировали бы защиту от corrupted state всего процесса, то есть гарантировали бы безопасность дальнейшей работы процесса, в условиях, когда в любой момент выполнение потока может быть прервано - а именно такие библиотеки нужно было написать разработчика .NET Core. Поэтому если вам нужно прерывать выполнение потока, то используйте CancellationToken, если это ваш код или код чужих разработчиков поддерживающий его, или запускайте код, который потребует прерывание выполнения потока в отдельном процессе и прекращайте его целиком.

Вот некоторые цитаты из этого обсуждения:

The key problem with thread abort is that it affects reliability of the whole stack. If you are using thread abort, all code (ie all libraries) running in the process have to be robust against being killed by thread abort at any point. It is extremely expensive to audit and write libraries with this constrain.

Try to review any code that is doing a more complex managed/unmanaged interop (e.g. sockets in CoreFX) and try to find places where inserting a thread abort exception would cause bad things to happen. I am sure that you are going to find many places where inserting thread abort would lead to hangs, crashes or data corruptions. And there will be a lot more that the tests would discover. People just do not naturally write code that is able to recover from being aborted at any point.

It is hard to tell where it is “safe” to insert the thread aborts. Libraries that need to be robust in presence of Thread.Abort need to be annotated for it, coded in a special way and stress tested.

We have tried to do this in .NET Framework: It was a full time job for several people to run a stress harness that inserted thread about at random points in .NET Framework, and file and fix bugs on the crashes, hangs and data corruptions that it hit. This was done only for a subset of .NET Framework that was usable in SQLCLR, and still it was never ending stream of issues.

Even with this effort, we often got a support escalation (from paid support) where people hit problems with Thread.Abort in production. Some of these issues require a very ugly hacks to workaround because of there was just no right fix to them. We had to resort to hacks like decoding assembly instructions and suppress or adjust thread abort behavior for particular instruction pattern that was known to hit the problem.

I am not even talking about larger .NET ecosystem - if you take a random NuGet package from nuget.org, it is almost guaranteed that it has reliability bugs in the presence of thread abort.

Да и Джон Скит, Эрик Липперт и Джо Даффи, задолго до .NET Core тоже не рекомендовали использовать Thread.Abort()

Комментировать

Про производительность Entity Framework

Written on August 15, 2019

Во-первых существует whitepaper по производительности EF, из которого можно узнать очень много грязных низкоуровневых подробностей работы EF, но он не обновлялся для EF Core, поэтому в чём-то не актуален. Хотя концептуально всё-равно полезен, если у вас возникнет необходимость супер-оптимизировать и скорость работы EF Core тоже:

Performance considerations for EF 4, 5, and 6

Но whitepaper никак не решает проблему того, что Entity Framework в принципе не приспособлен для массовых операций изменения данных - массовых Insert, Update и Delete. Тут проблема лежит в двух плоскостях - тормозит DbContext и тормозит сама база данных, так как EF производит все изменения отдельными sql запросами (хотя и в рамках одного физического запроса к базе данных).

Техники работы с контекстом, чтобы он меньше тормозил при Insert рассмотрены в этой статье Rick Strahl: Entity Framework and slow bulk INSERTs

И техника с пересозданием DbContext и “пакетным” сохранением описанная там, довольно эффективна, а в этом ответе на SO есть сравнение влияния размера пакетов на скорость сохранения, так чтобы вы могли выбрать оптимальный.

Для ускорения контекста при массовом обновлении и удалении, тоже есть “хаки”:

  1. Аттачить к контексту “болванки” (сущности, которые на загружались из БД и которых нет в контексте), а затем явно их помечать в контексте как обновлённые или удалённые (через DbContext.Entry), кое-что по этой теме можно найти здесь.
  2. Также как при Insert пакетировать сохранение.
  3. Также как при Insert отключать автоматический поиск изменений в контексте.

Но самый эффективный путь - это вообще отказаться от использования Entity Framework для массовых операций изменения данных и, например, напрямую использовать SQL. (Если решите пойти этим путём, то загляните ещё в эту статью, в ней автор рассказывают свою идею как сделать такие SQL-запросы более типизированными.)

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

Например, вот в этой статье, рассказано, как использовать специальные возможности MSSQL Server для ускорения Insert и Update на уровне базы данных: Entity Framework: повышаем производительность при сохранении данных в БД. Подобную возможность, кстати, предоставляет и PostgreSQL.

Ну и буквально на днях наткнулся на библиотеку Linq2Db у которой есть интеграция с Entity Framework и с помощью которой, судя по всему, без проблем можно будет делать массовый Insert, Update, Delete. Кстати, умеет использовать специальные высокопроизводительные операции специфичные для базы данных. Но предупреждаю, сам её ещё не использовал.

Комментировать

Несколько интересных статей про Entity Framework Core

Written on August 12, 2019

Не помню уже, как велики были возможности логирования в классическом Entity Framework, но у Entity Framework Core с этим точно всё в порядке, статья на эту тему: Настройка логирования в Entity Frmework Core

Оказывается Entity Framework Core может использовать INotifyPropertyChanged интерфейс для того, чтобы напрямую узнавать о изменениях в моделях и не использовать снапшот данных для их поиска. Думаю, что это можно попробовать использовать в высокопроизводительных сценариях.

Статья с разбором продвинутых техник использования проекций в Entity Framework Core.

  1. Как хранить проекции для их переиспользования.
  2. Как хранить и использовать вложенные проекции для проекций.
  3. И наконец самое интересное - способ создания вложенных проекций не для коллекций, а для одиночных сущностей (для вложенной проекции одиночной сущности нельзя вызвать Select, а значит и нельзя сделать проекцию.

EF Core 1.1: Read Only Entities & Extending Metadata with Annotations - статья показывающая пример работы с аннотациями метаданных сущностей в EF Core.

Ну и наконец две статьи в которых рассказывается, как можно изменить генерацию SQL в Entity Framework Core.

  1. Extending SQL Generation in Entity Framework Core
  2. Реализуем свой оператор в Entity Framework Core
Комментировать

Статья про джойны

Written on July 30, 2019

Статья с хорошим объяснением механизма работы джойнов. Если вы забыли, то джойн даёт в результате декартово произведение записей двух таблиц. А пока читал вспомнил, что когда работал с NHibernate при джойне двух таблиц со связью один ко многие, часто сталкивался с проблемой появления дублирующихся записей в результате выборки. Интересно, можно ли такую аномалию получить при использовании Entity Framework.

Комментировать

Выгрузка сборок в dotnet core 3

Written on June 18, 2019

Благая весть идёт: предстоящий .NET Core 3 принесёт нам возможность выгружать сборки из памяти. Сделать это можно будет с помощью класса AssemblyLoadContext. А вот тут инструкция по использованию.

Так как .NET Core забрал у нас Домены Приложений, а это была единственная возможность выгрузить сборку в управляемом коде, то новость просто отличная.

Комментировать

Прошлое и будущее командной строки Windows

Written on June 20, 2018

Серия статей про командную строку Windows. Здесь всё что вы хотели знать о ней, но боялись спросить. Ну как минимум стоит прочитать, если хотите узнать чем отличается TTY, PTY, терминал, консоль и командная строка.

Комментировать

Защитное программирование

Written on April 4, 2018

Старый перевод старой статьи Марка Симана, автора книги “Внедрение зависимостей в .NET”. В ней он рассуждает о концепции защитного программирования, также описанной в книге “Совершенный код” (впервые?).

Статья в основном сосредоточена на проблеме NullRefferenceException, хотя защитное программирование в целом касается намного большего круга вопросов.

Суть такова: Ваш код не должен возвращать и передавать в другие методы null. Если вы находитесь в защищенной местности (область кода в которой вы можете быть уверены, что null вам не будет возвращен из метода или свойства объекта), то вы можете быть уверены, что null вы сами никуда не передадите, соответственно никакие дополнительные проверки для этого не нужны и всё сводится к:

  1. Проверяйте данные приходящие из незащищённой местности (пользовательский ввод, системы не являющиеся вашей зоной контроля или не следующие концепции защитного программирования).
  2. Не возвращайте в защищённой местности из ваших методов и объектов null.
Комментировать

Хорошая статья про DRY

Written on March 22, 2018

Сама статья

Оказалось, что принцип DRY был впервые описан в книге “Программист Прагматик”, которую я обязательно прочитаю в будущем.

Принцип DRY о дублировании знания (обычно к знаниям относят бизнес-логику или алгоритмы), а не кода, поэтому:

  1. Дублирование знания является нарушением принципа DRY.
  2. Дублирование кода не обязательно является нарушением принципа DRY.

Неправильное применение принципа DRY приведёт к:

  1. Бесполезным абстракциям
  2. Преждевременной оптимизации

Которые в свою очередь приведут к увеличению сложности и ненужному объединению кода.

В комментах дали ссылку на “Правило трёх”

Которое говорит, что дублирование однозначно вредно, если код дублируется в трёх местах, и может быть как вредным так и нет, если дублируется в двух.

Комментировать