Я заметил, что когда возникает необходимость работать с базами данных SQL в приложениях .Net, разработчики часто выбирают какую-либо библиотеку ORM (в большинстве случаев .Net Entity Framework), даже не рассматривая другие альтернативы. На первый взгляд, это разумное решение, поскольку для популярных библиотек ORM есть множество руководств, отличный инструментарий и много опытных разработчиков. Однако это не всегда хороший выбор - я бы хотя бы рассмотрел другие варианты, если ваше приложение:

  1. В основном работает с отношениями фактов, а не с объектами
  2. Требуется использование динамических (не предопределенных) запросов

Фактические отношения

Если ваше приложение в основном реализует варианты использования, подобные этим:

1) «Дайте мне список сотрудников с самой высокой зарплатой с разбивкой по отделам»

2) «Сделайте скидку всем клиентам, зарегистрировавшимся в апреле»

3) «Уменьшите цену на футболки из прошлогодней коллекции на 20%»

тогда ваше приложение ориентировано на отношения фактов, а не на объекты, поскольку все эти сценарии включают обработку непредсказуемого количества данных. Например, в организации может быть 2 отдела и 30 сотрудников или сотни отделов и сотни тысяч сотрудников. Если используется какой-то ORM, предполагается, что все данные загружаются в память, затем анализируются и, в случае операций записи, выгружаются обратно в базу данных SQL. Очевидно, что это не может не сказываться на производительности.

Примечание. В современных библиотеках ORM есть много способов решения проблем с производительностью, таких как выражения LINQ, конвертируемые в SQL, отложенная загрузка и т. Д. Но мы должны помнить, что это всего лишь уловки, которые частично смягчают концептуальную проблему, и проблемы неизбежно возникают в какой-то момент.

С другой стороны, SQL специально разработан для работы с отношениями фактов, и описанные выше сценарии могут быть реализованы с помощью пары относительно простых выражений. Очевидно, здесь имеет смысл использовать чистый SQL без каких-либо ORM, но какие у нас есть варианты? Не так уж много. Наиболее распространенными из них будут:

1) Хранимые процедуры

2) Текстовые запросы

3) Некоторые построители запросов на основе LINQ (например, «LINQ to DB»)

4) Моя библиотека« SqExpress » :)

Хранимые процедуры

Хранимые процедуры - это первое, что приходит на ум, когда вам нужно использовать всю мощь нативного SQL, но этот подход имеет несколько серьезных недостатков:

1) Трудности в обслуживании - всегда необходимо убедиться, что код .Net синхронизирован с кодом хранимых процедур во всех экземплярах базы данных, с которыми работает ваше приложение. Это означает, что вы обречены возиться со сценариями миграции почти при каждом развертывании и неизбежно что-то пойдет не так в какой-то момент.

2) SQL - отличный язык запросов, но очень плохой язык программирования. Как только вы начнете писать хранимые процедуры, будет трудно устоять перед соблазном перенести в них как можно больше логики, потому что это - это такой простой способ решить проблемы с производительностью и быстро исправить проблемы в дизайне приложений. Но обратная сторона - то, что вы не сможете разбить свою логику на маленькие модули и тестировать их изолированно (как часто я видел «супер» процедуры для 5 тысяч строк с сотнями переменных внутри)

3) Копирование-вставка. Код в процедурах очень сложно повторно использовать (однако функции частично решают эту проблему), поэтому некоторая общая логика будет дублироваться во многих процедурах.

4) Сложно реализовать пакетную обработку. UpdateUserById - это типичное имя хранимой процедуры. Но что, если у меня 100 пользователей? Без проблем! Давайте вызовем процедуру 100 раз! Единственная проблема в том, что это займет в 100 раз больше времени (некоторые базы данных поддерживают табличные параметры, но такие параметры сложно использовать и поддерживать).

5) Хранимые процедуры не помогают, если вам нужны динамические запросы

6) Если вы активно используете хранимые процедуры, то миграция на другую базу данных SQL будет невероятно сложной.

Текстовые запросы

Запросы, возникающие в результате простой конкатенации строк, являются чистым злом. Такой код сложно писать, читать и поддерживать. Безопасность также является огромной проблемой - Sql-инъекции по-прежнему актуальны.

Однако, что удивительно, в этом подходе есть несколько положительных сторон:

1) Вы можете использовать этот подход в дополнение к любому другому.

2) Это по-прежнему самый популярный способ создания динамических запросов, доступный каждому.

Построители запросов на основе LINQ

Такие построители запросов (например, LINQ to DB) могут быть хорошей альтернативой, когда вам нужно достичь возможностей SQL. LEFT, RIGHT, FULL, CROSS JOINS больше не являются проблемой - вы можете выразить их прямо в коде C # (используя LINQ):

//Full Join
var query =
    from c in db.Category
    from p in db.Product.FullJoin(pr => pr.CategoryID == c.CategoryID)
    where !p.Discontinued
    select c;

Также поддерживаются CTE

//CTE
var employeeSubordinatesReport  =
   from e in db.Employee
   select new
   {
      e.EmployeeID,
      e.LastName,
      e.FirstName,
      NumberOfSubordinates = db.Employee
          .Where(e2 => e2.ReportsTo == e.ReportsTo)
          .Count(),
      e.ReportsTo
   };

var employeeSubordinatesReportCte = employeeSubordinatesReport
                                       .AsCte("EmployeeSubordinatesReport");
var result =
   from employee in employeeSubordinatesReportCte
   from manager in employeeSubordinatesReportCte
                      .LeftJoin(manager => employee.ReportsTo == manager.EmployeeID)
   select new
   {
      employee.LastName,
      employee.FirstName,
      employee.NumberOfSubordinates,
      ManagerLastName = manager.LastName,
      ManagerFirstName = manager.FirstName,
      ManagerNumberOfSubordinates = manager.NumberOfSubordinates
   };

Однако когда дело доходит до динамических запросов, LINQ - не лучшее решение. Теоретически можно изменить выражение, но это непростое действие.

SqExpresss

В течение многих лет мне не нравилось работать с SQL из .Net, но потом я написал свою собственную библиотеку (SqExpress), которая позволяет мне писать код на C #, максимально приближенный к SQL. Он не использует LINQ - построение запросов реализовано с помощью вспомогательных функций и перегрузки операторов. В результате нет проблем с динамическими запросами. Вот пример:

Все еще ORM?

Остается еще один вопрос: «Когда ORM - хорошее решение?» Что ж ... давайте просто перевернем те условия, при которых ORM не рекомендуется использовать:

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

Если эти условия соблюдены, то, скорее всего, ORM - правильный выбор.

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

Ссылки: