Comments 70
Можно еще немного поработать с выделением запросов в отдельный метод/класс (CQRS) и в них начинать с ORM, а потом, при необходимости, заменить на нативный SQL или хранимку внутри этого метода/класса.
Кстати однажды работал в проекте, где было четкое разделение по уровням - толпа фронт-ендищиков, толпа бэк-ендщиков и толпа программистов БД. Ну и толпа аналитиков проектировала все эти контракты. Я был на бэк-енде и просто вызывал подготовленные хранимки, как на получение данных, так и на обновление. Они были разного уровня абстракции. И все оптимизации проводились программистами БД. В целом ничего так, мне понравилось.
Это все прекрасно, пока первые понимают как будут использовать их данные со стороны бека. И наоборот, когда бек в курсе что там с данными. Иначе все превращается в фрагмент из фильма "Ночной продавец" :
Я человек маленький, мне сказали - я сделал
Интересное замечание. Тут фокус на работе приложения, а не на работе команды, которая разрабатывает это приложение. Разные аспекты и приоритеты.
Да там аналитики за всех думали. Это не хорошо и не плохо. С одной стороны для какого-нибудь сениора, которому интересно делать все самому, будет скучно. С другой - такой подход позволяет организовать процесс где каждый занимается своим делом и позволяет сглаживать ошибки более слабых участников команды.
Спасибо за дополнение. Это уже архитектурная надстройка над тем, о чем в этой статье. Вы абсолютно правы, просто это уже другой аспект не вошедший в тему данной статьи. Можно расширить. Интересно было прочитать про ваш опыт.
Автор, скажите, пожалуйста, а в тексте статьи есть хоть что-то, что написали вы сами, а не нейронка?
Из 5 комментариев, ваш вопрос мне больше всех понравился. Что я думаю по вашему вопросу:
1. Вы проницательный и опытный в ИИ человек.
2. Статья сгенерирована Gemini 2.0 Flash Lite. Но суть сгенерирована полностью мною. Статья мною изучена, получена далеко не с первой итерации и одобрена к публикации (моим внутренним критиком) на Хабре.
3. Это моя первая публикация на Хабре. Хотя я тут давно имею аккаунт. Больше читатель. Перед этим ознакомился с правилами и не нашел запрета на генеративные статьи. Если я что-то упустил или не правильно понял, пожалуйста проясните для меня, я как и все, могу ошибаться.
4. Суть статьи полностью задана мною и представляет, как мне кажется, основной халиварный элемент. Форма не столь важна, хотя ИИ точно лучше меня пишет статьи.
5. Возможно вам будет, также, интересно, в результате какого процесса она появилась. Просто я в очередной 100500 раз прорабатывал свой какой-то внутренний незакрытый технический вопрос, ИИ вдруг выдал ответ, который мне показался мне достаточно адекватным и интересным, для того, чтобы его зафиксировать, т.к. весьма вероятно, по сути это может заинтересовать еще кого-то, кроме меня.
6. Мне показалось или нет, что в вашем тезисе содержится некий упрек? Может быть и нет. Если да, то что вы видите в этом негативного?
Спасибо за вопрос.
Вы превращаете хабр в помойку с вашим ИИ
Это не мой ИИ. Странно на ИТ-ресурсе читать такие утверждения, что ИИ что-то превращает в помойку. Мое мнение - что в помойку что-угодно превращает негативное отношение к другим людям. Вы же не высказались по существу темы, а просто вбросили оффтоп. В этом плане, с ИИ гораздо интереснее общаться и полезнее. К сожалению. Но к счастью, есть люди, с которыми общение еще возможно по сути.
Хотите по теме? В статье про производительность - ноль цифр и замеров. Негативное отношение у Вас к читателям, подсовывая им такой материал.
Ну извините, что ваши ожидания не совпали с реальностью. Я бы был рад раньше для себя найти такую информацию, поэтому и опубликовал. Это ответ на очень специфический запрос, для тех кто пребывает в соответствующей дилемме. Убеждать кого-либо или агитировать не входило в мои цели. Не думаю, что вы сильно пострадали, т.к. есть много других статей, которые более соответствуют вашим интересам.
Замеры и метрики - не входят в данную тему. Так что чувство вины за собой не испытываю по данному поводу.
Если бы я хотел почитать дженерик-текст, я бы пошёл к дипсику/джемини/(выбрать по вкусу). А так вы лишь потратили наше время, ибо не выдали свои рассуждения и экспертное мнение, не посидели над вопросом с аналитикой и примерами, а накидали запрос - и вкинули сюда.
Какова ценность этой статьи? Или Вы хотите ресурс в пикабу превратить с нейро-текстами и баянами?
Даже комменты через нейронку - настолько не уважаете собеседников?
Просто это не Ваша тема. Либо ваша, но Вы в этом абсолютный новичок. Как часто вы так вовлекаетесь в разные неинтересные Вам темы по жизни? Советую обратиться к психологу. Очень помогают.
В принципе, я не настаиваю на ее обязательном изучении и даже ознакомлении. Это был Ваш свободный личный выбор. Но при этом, Вы бросаете мне упреки, что я вообще тут существую.
Извините, что помешал Вам счастливо жить. )))
Интеграция T-SQL в EF Core: Четыре основных способа
Такое деление вообще-то весьма странно. По крайней мере для того, кто смотрит со стороны СУБД.
С сырым SQL всё ясно и понятно. И правда, штука совершенно отдельная и по отношению к остальным способам оригинальная.
А вот представления... знаете, вообще-то представление отличается от таблицы не сказать чтобы сильно. Иной раз без обращения к системным таблицам с вопросом "А что это за объект?" вообще не получится узнать, с чем мы имеем дело, с таблицей или представлением. И если во фреймворке приходится использовать для работы с представлениями какие-то особые конструкции, то определяется это именно тем, что представления могут (хотя и не обязаны) по сравнению с таблицами иметь и некоторые ограничения, и, наоборот, некоторые дополнительные возможности. Наверное... или причины какие-то иные?
Что же до UDF и SP - то тут непонятно, почему их потребовалось описывать отдельно. Там разницы - кот наплакал. Или у них, как и у представлений с таблицами, есть какие-то минорные различия, которые на уровне фреймворка требуют столь радикальных различий обслуживающего кода, что рассматриваются как разные и программируются соответственно по-разному?
Не совсем вас понял. Какое деление странно? Я писал не об делении, а об объединении: EF с T-SQL, чтобы перенести часть наиболее сложной бизнес-логики с LINQ на T-SQL. При этом LINQ остаётся, но приложение разрабатывается и работает быстрее в гибридном подходе.
По поводу T-SQL вы задаёте правильные и интересные вопросы.
Мне кажется, вы как раз смотрите (как и подавляющее большинство современных разработчиков) на вопрос не со стороны СУБД, а со стороны ООП. Поэтому постараюсь осветить тему со стороны конкретной СУБД MS SQL Server.
View. С точки зрения разработки бизнес-логики на движке MSSQL Table и View — это совершенно разные вещи. Внешнее сходство заключается в том, что View возвращает Table. Но, в отличие от Table, View содержит в себе логику, причём сколь угодно сложную. Наверное, это можно назвать инкапсуляцией. И для меня язык SQL (в реализации T-SQL) намного проще и понятнее для сложных запросов к БД, чем LINQ. К тому же LINQ через EF работает на порядок медленнее, а иногда на порядки, чем T-SQL. Ну и можно дальше бесконечно описывать, чем T-SQL не LINQ. LINQ — это узкое место, бутылочное горлышко на пути к движку MSSQL. Поэтому довольно объёмные блоки бизнес-логики иногда имеет смысл делать внутри СУБД. Да и небольшие просто гораздо быстрее и с в несколько раз меньшим объёмом кода удобно делать внутри — всё, что касается работы с реляционными данными.
Хранимые процедуры (SP) и Пользовательские функции (UDF) соответственно так же занимают своё важное место в архитектуре. И хотя вам видится разница небольшой в их вызове через EF, тем не менее интерфейсы у них несколько отличаются. Поэтому приведены раздельные примеры.
Должен заметить: я тут не склоняю никого к такому подходу. Статья опубликована исключительно с целью поделиться своими наблюдениями и опытом. Возможно, кому-то покажется необычным. Кому-то — неудачным.
Мне кажется, вы как раз смотрите (как и подавляющее большинство современных разработчиков) на вопрос не со стороны СУБД, а со стороны ООП.
Всё с точностью до наоборот. Я - чистый SQL-щик, и все мои мысли начинаются на моменте получения запроса извне и заканчиваются на моменте возврата результата выполнения запроса. А если приём запроса и отдача результата совпадают, то наличие разницы в передаче запроса и обработке результата мне кажется если не избыточной, то искусственной. Впрочем, допускаю, что в этой разнице есть какой-то смысл - но я его не вижу.
View. С точки зрения разработки бизнес-логики на движке MSSQL Table и View — это совершенно разные вещи. Внешнее сходство заключается в том, что View возвращает Table.
Вы как раз рассматриваете именно момент возврата, то есть именно тот момент, когда внешние различия отсутствуют. Набор записей, который нужно принять и неким способом обработать. Представление или предрасчитанная таблица - для вашего кода нет абсолютно никакой разницы. Вся разница между ними - за пределами того, что вы описываете. А потому выделение представлений в отдельный пункт - я не могу найти в этом смысла. Опять же у меня создаётся впечатление ничем не оправданного усложнения.
Скажите, где большая ценность сосредоточена? В кодах или в данных? Вопрос не тривиальный. Данная статья показывает, что на существование имеют право два подхода DDD: Data-Driven-Design и Domain-Driven-Design.
В случае с Data-Driven-Design мысли при разработке начинаются не с внешнего запроса, а с внутреннего. Что значит внутреннего? Это когда архитектор микро-сервиса комплексно и единолично (либо в компактной команде) принимает решение о целесообразности распределения бизнес-логики между компонентами системы. Можно в приложении разместить (более канонично, но менее эффективно и производительно) , можно в ядре БД (более специфический вариант, но иногда оправданный).
Идея в общем-то написать статью появилась после того, как я попросил неглупый ИИ написать LINQ запрос на основании предоставленного ему T-SQL с CTE. LINQ получился довольно громоздким и глючным. ИИ так и сказал, что нет прямого преобразования T-SQL в LINQ. Т.е. LINQ поддерживает лишь базовые возможности T-SQL, но далеко не все. Подходит для простейших случаев отлично. Но если мне нужно быстро сгенерировать отчет с довольно разветвленным запросом к БД, то это конечно намного проще и быстрее сделать на T-SQL. А в случае с выполнением хранимых процедур, все делать через EF - рехнуться можно.
Скажите, где большая ценность сосредоточена? В кодах или в данных?
Понять бы вопрос... в смысле понять бы правильно. А то со своей колокольни я скажу, что данные важнее - если утрачен весь код, но сохранены все данные, то создание кода заново есть лишь вопрос стоимости (время, деньги и пр. ресы). Тогда как при сохранившемся коде, но утраченных безвозвратно данных восстановление если и возможно, то далеко не всегда.
ИИ так и сказал, что нет прямого преобразования T-SQL в LINQ.
По-моему, это настолько очевидно, то можно было даже ИИ не беспокоить.
Можно в приложении разместить (более канонично, но менее эффективно и производительно)
То есть поставить бизнес-логику в зависимость от приложения. В общем случае - неконтролируемого приложения. Не, не надо ничего говорить об этой фразе, это моё персональное вИдение.
Скажите, где большая ценность сосредоточена? В кодах или в данных?
Вопрос скорее философский, но от этого не менее фундаментальный. Я пока им тоже задаюсь и ответа не знаю. В данном контексте его привел, скорее с целью обозначить дуальность и неоднозначность разного рода утверждений относительно сути архитектур. Право на существование имеют разные взгляды и не совсем верно пренебрегать какими либо из них.
По-моему, это настолько очевидно, то можно было даже ИИ не беспокоить.
Я не знал этого. На счет "беспокоить" ИИ. Чем ИИ хорош, что он может бесконечно отвечать на любые вопросы, даже глупые, не раздражается, не уходит из-за занятости. И отвечает вполне разумно. Можно конечно погуглить, но через ИИ быстрее. Если вопрос критичный - то конечно лучше проверить в разных источниках.
То есть поставить бизнес-логику в зависимость от приложения.
Это тоже неоднозначно. По сути приложение - это кристаллизация бизнес логики. В своем смысле - логика ставится в зависимость от "кристалла". Это так и есть. Опять параллели с дилеммой, что важнее: код или данные. Форма или содержание. А может быть даже: что первичнее - яйцо или курица.
Это тоже неоднозначно. По сути приложение - это кристаллизация бизнес логике.
Есть хранение данных. Есть обработка данных (собственно бизнес-логика). Есть отображение результатов применения бизнес-логики.
С моей точки зрения бизнес-логика не должна зависеть от (подсистемы) отображения результатов её применения. А потому - эта, как вы выразились, "кристаллизация" бизнес-логики в подсистему отображения мне не представляется правильным подходом. Если я, конечно, правильно понял ваше высказывание (в чём у меня всё же имеются большие сомнения).
По своему, вы конечно правы. Но все же все неоднозначно. На любую проблему может быть как минимум две точки зрения.
Все же суть технологии в широком смысле такова, что логику реальной жизни приходится вписывать в "кубическую" неудобную структуру технологии. То, что просто произнести словами, порой довольно сложно формализовать, например, математически. Но такова природа технологий. В этом смысле, мы вынуждены подчиняться возможностям программных систем, чтобы вписать в них житейские задачи. Я это имею в виду. С этой т.з. бизнес логика подчиняется программному коду и зависит от него, нравится это или нет.
Вот вы попали прямо в точку. Затронули один ключевой вопрос, который тоже сподвигнул меня поднять эту тему. Только я на этот вопрос, для себя ответил диаметрально противоположно.
Я допускаю "размазывание" бизнес логики по Вселенной. И исхожу я здесь не из чувства субъективного вкуса, а из объективного осознания, что бизнес-логика по своей природе является распределенной. И следовательно любые попытки удержать и сконцентрировать ВСЮ бизнес-логику в одном месте - это банальная утопия. Я могу ошибаться, но я к своим 49 годикам пришел пока что к такому опыту и выводу. И я точно уже понял, что во многих темах я довольно бестолков.
Насчет того, что вы согласны с @AlexZyl, и я с ним согласен. ) Но, либо так, либо никак.
Спасибо за комментарий.
Проверка показала что текст синтетический. Как уже достало то, что любимый Хабр превращается в фиг знает что
Вообще жизнь на этой планете Вас еще не достала? ) Куда деваться? Суть может быть в разных формах обличена. На синтетичность вы проверили, а в суть не проникли. И не обязаны.
Я учту ваше мнение на будущее. Спасибо, что выразили его. Мне это тоже было интересно.
Кстати, как специалист в этой области, можете просветить по вопросу: что именно мешает проникнуть в суть, если текст написан нейронкой? Нейронка же не сама решила это написать, кто-то воодушевленный вложил в промпт свою свободную волю, которой нет у ИИ. Значит суть какая-то там должна быть?
Как вы относитесь к идее выделения компонентов или автономных модулей бизнес-логики и помещения их внутрь СУБД? Обычно это осуждается большинством современных архитекторов (по моим наблюдениям).
Спасибо за содержательный ответ — в нём действительно есть о чём задуматься.
Что касается "синтетичности" — да, вы правы, суть действительно может быть обличена в любую форму. Но вот в чём дело: когда люди обмениваются мнениями, особенно в живом диалоге, им важно не только то, что сказано, но и кем, зачем, в каком контексте, с какой эмоцией, позицией, опытом.
ИИ, даже вдохновлённый удачным промптом, может дать красивый текст, но он не выражает позицию человека напрямую — он её транслирует с фильтрацией, интерпретацией, порой безотносительно личной ответственности. И если вместо ответа человека приходит абстрактный текст, это оставляет чувство отсутствия собеседника. Как будто разговариваешь не с личностью, а с облаком.
Поэтому в обсуждениях, особенно когда важен обмен взглядами, хочется видеть именно человека, даже если он формулирует неидеально, с ошибками или просто неуклюже. Это всё равно живое, и потому — ценное.
Что касается сути и промптов — согласен, за каждым промптом стоит некая воля. Но она работает иначе, чем прямая речь. Промпт — это акт конструирования, не высказывания. А значит, даже при вложенном замысле, результат всё же больше похож на имитацию смысла, чем на его подлинное проявление.
По поводу выделения бизнес-логики в СУБД — вопрос, безусловно, технически интересный. Поддерживаю мысль, что это непопулярно у многих архитекторов сегодня, поскольку нарушает принципы разделения ответственности и усложняет масштабирование. Но всё зависит от контекста: если логика плотно завязана на данные и важна производительность — иногда это оправдано. Можем обсудить подробнее, если интересно.
И спасибо вам — за тон, за внимание к деталям и готовность воспринимать чужую точку зрения. Это редкость.
Взаимно, и Вам спасибо за конструктивный вдумчивый ответ на непраздный вопрос.
Многое зависит от контекста. Писать туториал - довольно рутинная задача. Но поставить задачу его написать - как в данном случае, есть свободный человеческий творческий процесс. Думаю, многие просто не привыкли, не осознали и не смирились еще с тем фактом, что в будущем синергия человека с ИИ будет только нарастать. И огульные обвинения в синтетичности скорее всего есть следствие незрелости, но и несовершенства как человека берущего в руки ИИ, так и самого ИИ.
Насчет темы постав о гибридном подходе к реализации бизнес-логики разделенной между Domain Driven Desing и Data Driven Design, безусловно это зависит от контекста, как Вы верное заметили. И мой ключевой посыл был сместить ситуацию в сторону более здорового баланса, поскольку из современного инфопространства в основном сквозит чрезмерная приверженность к одному только варианту. Это упускает массу великолепных возможностей в плане экстремальной производительности и простоты кода.
Я созрел и переписал статью заново в виде второй версии с учетом комментариев: https://habr.com/ru/articles/914378/
Бред-то какой.
Увы, не нужен TSQL.
Недавно видел таких разработчиков (которые, увы, не смогли в DDD :)).
Dapper не быстрее, чем EF Core во всех кейсах. (CRUD, деревья, и т.д.)
А разработка на EF Core (Linq) почему-то раза в 2-3 быстрее, чем "Править поля запросам в Dapper", не говоря уже про удобство поддержки.
А если еще правильно делать Include (и не возвращать по 10 мб с сервера) - то быстрее во всех случаях.
Даже rte быстрее с EF Core (да, обертка для работы с деревьями делается на раз).
А от того, что вы не вернете 4 лишних поля запросы, увы, не ускоряются :)
EF - отличная ORM. Она вполне допускает использовать весь потенциал разных СУБД, включая очень мощный MSSQL. В этом контексте и статья. Можно и через Dapper.
Просто T-SQL это специализированный язык работы с данными, его возможности существенно шире и глубже, чем C# и соответственно без использования T-SQL для компонентов бизнес-логики весь потенциал MSSQL задействовать невозможно. Если по сути, то разработка сложных процессов по работе с данными может быть гораздо проще.
Просто T-SQL это специализированный язык работы с данными, его возможности существенно шире и глубже, чем C# и соответственно без использования T-SQL для компонентов бизнес-логики весь потенциал MSSQL задействовать невозможно.
Пожалуйста, можете привести конкретный пример. Преимущество t-sql может быть в технических моментах, так как он просто ближе к бд, но чтобы было преимущество в описании бизнес-логики, ни разу не встречал.
Что понимать под бизнес - логикой? Если работа производится над данными которые являются формализацией бизнес-процессов, то осмелюсь утверждать, что алгоритмы по работе с такими данными (извлечение, изменение, запись, удаление) - является бизнес-логикой.
Вам привести пример t-sql запроса, который легче реализовать в ядре БД, чем через LINQ? Думаю, что без конкретного кейса и понятного всем контекста, это может выглядеть не достаточно убедительным. Если технически только.
Вот пример запроса на t-sql который более нативен природе данных и содержит значительно меньше кода, а код более понятен, чем если это громоздить на C# LINQ... (конечно зависит от разработчика, его опыта и предпочтений. но на t-sql это работает точно быстрее. а в моем случае и разрабатывается).
WITH
-- Уровень 1: Получение базовых данных о продажах и продуктах
SalesData AS (
SELECT
s.SaleID,
s.ProductID,
s.SaleDate,
s.Quantity,
p.ProductName,
p.Category
FROM
Sales AS s
INNER JOIN
Products AS p ON s.ProductID = p.ProductID
WHERE
s.SaleDate >= DATEADD(month, -12, GETDATE()) -- Фильтруем продажи за последние 12 месяцев
),
-- Уровень 2: Расчет выручки по каждой продаже
SalesWithRevenue AS (
SELECT
sd.SaleID,
sd.ProductID,
sd.SaleDate,
sd.Quantity,
sd.ProductName,
sd.Category,
sd.Quantity * (
SELECT Price
FROM ProductPrices pp
WHERE pp.ProductID = sd.ProductID
AND pp.EffectiveDate <= sd.SaleDate
ORDER BY pp.EffectiveDate DESC
FETCH FIRST 1 ROW ONLY
) AS Revenue -- Расчет выручки с учетом динамических цен
FROM
SalesData AS sd
),
-- Уровень 3: Агрегация данных по месяцам и категориям
MonthlyCategoryRevenue AS (
SELECT
FORMAT(swr.SaleDate, 'yyyy-MM') AS SaleMonth,
swr.Category,
SUM(swr.Revenue) AS MonthlyRevenue,
COUNT(DISTINCT swr.SaleID) AS NumberOfSales
FROM
SalesWithRevenue AS swr
GROUP BY
FORMAT(swr.SaleDate, 'yyyy-MM'),
swr.Category
),
-- Уровень 4: Расчет скользящей средней выручки за 3 месяца
RollingAverageRevenue AS (
SELECT
mcr.SaleMonth,
mcr.Category,
mcr.MonthlyRevenue,
mcr.NumberOfSales,
AVG(mcr.MonthlyRevenue) OVER (PARTITION BY mcr.Category ORDER BY mcr.SaleMonth ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS RollingAverageRevenue -- Скользящая средняя
FROM
MonthlyCategoryRevenue AS mcr
),
-- Уровень 5: Определение лидеров по росту выручки (сравнение с предыдущим месяцем)
RevenueGrowth AS (
SELECT
rar.SaleMonth,
rar.Category,
rar.MonthlyRevenue,
rar.NumberOfSales,
rar.RollingAverageRevenue,
LAG(rar.RollingAverageRevenue, 1, 0) OVER (PARTITION BY rar.Category ORDER BY rar.SaleMonth) AS PreviousMonthRollingAverage,
(rar.RollingAverageRevenue - LAG(rar.RollingAverageRevenue, 1, 0) OVER (PARTITION BY rar.Category ORDER BY rar.SaleMonth)) / LAG(rar.RollingAverageRevenue, 1, 0) OVER (PARTITION BY rar.Category ORDER BY rar.SaleMonth) * 100 AS RevenueGrowthPercentage
FROM
RollingAverageRevenue AS rar
)
-- Финальный SELECT: Вывод лидеров по росту выручки за последний месяц
SELECT
rg.SaleMonth,
rg.Category,
rg.MonthlyRevenue,
rg.NumberOfSales,
rg.RollingAverageRevenue,
rg.RevenueGrowthPercentage
FROM
RevenueGrowth AS rg
WHERE
rg.SaleMonth = (SELECT MAX(SaleMonth) FROM RevenueGrowth) -- Фильтруем по последнему месяцу
ORDER BY
rg.RevenueGrowthPercentage DESC;
Но это же не t-sql, а plain sql. В любом случае, как вы это отлаживаете и потом поддерживаете? Что будете делать, если бизнес-логика выйдет за пределы аналитических функций?
Поменяю функции. Язык очень гибкий.
Язык очень гибкий
Да ладно! Я бы поверил, если бы сам на t-sql не писал) Даже от дублирования FORMAT
в MonthlyCategoryRevenue
нет хорошего способа избавиться, а когда пойдут условия в логике, тогда вообще гасите свет.
Условия в логике через CASE или IF прекрасно работают. Можно в функцию инкапсулировать. Зато работает быстро и надежно.
Еще раз оговорюсь: во многом это дело индивидуальных предпочтений. Существует множество вариантов и подходов, у всех свои плюсы и минусы. Прекрасно, когда есть выбор. Спасибо за ваше мнение. Для меня это интересная тема.
Почему городить. Код практически один к одному переносится из T-SQL в ef.core и C#.
Если посмотреть на переведенный запрос, то утверждение о том, что кода с использованием ef.core и C# будет больше, и о том что код будет менее понятным, мне кажется очень спорным.
Какой из подходов будет работать быстрее нужно проверять.
Ну и очевидно что код на C# более оказывается более портируемым.
Ps. использовал подход автора, код писал gpt-4o-mini (снимаю перед ним шляпу).
[code]
var salesData = _context.Sales
.Where(s => s.SaleDate >= DateTime.Now.AddMonths(-12))
.Join(_context.Products,
s => s.ProductID,
p => p.ProductID,
(s, p) => new
{
s.SaleID,
s.ProductID,
s.SaleDate,
s.Quantity,
p.ProductName,
p.Category
});
var salesWithRevenue = salesData
.Select(sd => new
{
sd.SaleID,
sd.ProductID,
sd.SaleDate,
sd.Quantity,
sd.ProductName,
sd.Category,
Revenue = sd.Quantity * _context.ProductPrices
.Where(pp => pp.ProductID == sd.ProductID && pp.EffectiveDate <= sd.SaleDate)
.OrderByDescending(pp => pp.EffectiveDate)
.Select(pp => pp.Price)
.FirstOrDefault()
});
var monthlyCategoryRevenue = salesWithRevenue
.GroupBy(swr => new
{
SaleMonth = swr.SaleDate.ToString("yyyy-MM"),
swr.Category
})
.Select(g => new
{
g.Key.SaleMonth,
g.Key.Category,
MonthlyRevenue = g.Sum(x => x.Revenue),
NumberOfSales = g.Select(x => x.SaleID).Distinct().Count()
});
var rollingAverageRevenue = monthlyCategoryRevenue
.Select(mcr => new
{
mcr.SaleMonth,
mcr.Category,
mcr.MonthlyRevenue,
mcr.NumberOfSales,
RollingAverageRevenue = monthlyCategoryRevenue
.Where(x => x.Category == mcr.Category && x.SaleMonth.CompareTo(mcr.SaleMonth) <= 0)
.OrderByDescending(x => x.SaleMonth)
.Take(3)
.Average(x => x.MonthlyRevenue)
});
var revenueGrowth = rollingAverageRevenue
.Select(rar => new
{
rar.SaleMonth,
rar.Category,
rar.MonthlyRevenue,
rar.NumberOfSales,
rar.RollingAverageRevenue,
PreviousRollingAverage = rollingAverageRevenue
.Where(x => x.Category == rar.Category && x.SaleMonth.CompareTo(rar.SaleMonth) < 0)
.OrderByDescending(x => x.SaleMonth)
.Select(x => x.RollingAverageRevenue)
.FirstOrDefault()
})
.Select(rar => new SalesReport
{
SaleMonth = rar.SaleMonth,
Category = rar.Category,
MonthlyRevenue = rar.MonthlyRevenue,
NumberOfSales = rar.NumberOfSales,
RollingAverageRevenue = rar.RollingAverageRevenue,
RevenueGrowthPercentage = rar.PreviousRollingAverage != 0 ?
(rar.RollingAverageRevenue - rar.PreviousRollingAverage) / rar.PreviousRollingAverage * 100 : 0
});
var lastMonth = revenueGrowth
.OrderByDescending(rg => rg.SaleMonth)
.Select(rg => rg.SaleMonth)
.FirstOrDefault();
return revenueGrowth
.Where(rg => rg.SaleMonth == lastMonth)
.OrderByDescending(rg => rg.RevenueGrowthPercentage);
[/code]
Используйте linq2db и в проекте больше не будет сырого sql )
"Сырой" код не в проекте, а в MSSQL. Сервер БД может выдавать не только флэт таблицы. Но и View и UDF и SP выполнять. У Postgres аналогичная ситуация. Данная статья - микро туториал по случаю с MSSQL.
А можете развить тему, что в SqlServer такого бизнесового и исключительно, что вы им восхищаетесь. Потому что я уже много лет пишу на смешанном стеке, и в основном на TSQL, и кроме того что основной (и по существу дефолтный) провайдер EF местами прибит гвоздями к фичам SqlServer.. я не вижу никаких преимуществ. Ну и над запросами в Sybase не надо особо задумываться, приятный бонус ))
Реально у SQLServer/TSQL достаточно много способов выстрелить себе в ногу, хотя по сравнению с Oracle это наверно цена за гибкость. Но ничего такого исключительного в нем нет. Я бы даже сказал, что он заслуживает отдельной ненависти за то что некоторые вещи можно решить практически только управляемым кодом.. например, я могу повертеть данные, форматировать в xml или json и отправить их http запросом в какой-то web api не выходя их хранимки, но такие базовые вещи как регулярные выражения по факту реализованы на зачаточном уровне. Потому что от богатого функционала PATINDEX плакать хочется. В итоге у меня есть из коробки какие-то ненужные абсолютному большинству разработчиков свистелки и перделки application уровня, но действительно востребованный функционал покупаешь у какого-то вендора, либо таскаешь с собой чемодан с кучей архаичного кода, которые написали какие-то парни до тебя, и хз как это работает.
Да, конечно. Области, где T-SQL превосходит C# EF LINQ (и почему):
Оптимизация запросов и контроль над планом выполнения:
T-SQL: Вы имеете полный контроль над планом выполнения запроса. Вы можете использовать
EXPLAIN PLAN
(или аналогичные инструменты) для анализа плана, добавлять подсказки (hints) для оптимизатора, переписывать запросы для повышения производительности. Вы можете тонко настраивать индексы, статистику и другие параметры, влияющие на производительность.C# EF LINQ: Вы полагаетесь на преобразование LINQ-выражений в SQL-запросы, выполняемое EF Core. Хотя EF Core хорошо справляется с этим, он не всегда генерирует оптимальные запросы. Вы ограничены в контроле над планом выполнения. Вы можете использовать
FromSqlRaw
илиFromSqlInterpolated
для написания сырых SQL-запросов, но это снижает преимущества ORM. Оптимизация запросов требует глубокого понимания SQL Server и того, как EF Core преобразует ваши LINQ-выражения.
Сложные вычисления и аналитика:
T-SQL: T-SQL предоставляет мощные инструменты для сложных вычислений и аналитики:
Оконные функции:
OVER
,LAG
,LEAD
,NTILE
,RANK
,DENSE_RANK
и т.д. Позволяют выполнять сложные вычисления на основе наборов строк (например, скользящие средние, ранжирование, расчеты с предыдущими/следующими значениями).Рекурсивные CTE: Незаменимы для работы с иерархическими данными (например, организационные структуры, BOM).
PIVOT
иUNPIVOT
: Упрощают преобразование данных между строчным и столбцовым представлением.GROUPING SETS
,CUBE
,ROLLUP
: Обеспечивают гибкость при агрегации данных.Встроенные функции для работы с датами, строками, XML, JSON: Оптимизированы для работы с данными в базе данных.
C# EF LINQ: Вы можете выполнять эти вычисления в C#, но это часто означает:
Перенос данных на клиент: Снижает производительность, особенно для больших объемов данных.
Сложные LINQ-выражения: Могут быть сложными для написания, чтения и отладки.
Меньше контроля над оптимизацией: EF Core может не всегда эффективно преобразовывать сложные LINQ-выражения в SQL.
Работа с большими объемами данных:
T-SQL: SQL Server оптимизирован для работы с большими объемами данных. Вы можете использовать:
Пакетную обработку:
BULK INSERT
,OPENROWSET
для быстрой загрузки данных.Partitioning: Разделение таблиц на разделы для повышения производительности запросов и обслуживания данных.
Clustered Columnstore Indexes: Оптимизированы для аналитических запросов к большим объемам данных.
C# EF LINQ: Работа с большими объемами данных в EF Core может быть проблематичной:
Проблемы с производительностью: Запросы могут выполняться медленно.
Необходимость разбиения на страницы: Для обработки больших наборов данных.
Потенциальные проблемы с памятью: При загрузке больших объемов данных в память.
Безопасность и производительность хранимых процедур:
T-SQL: Хранимые процедуры позволяют:
Инкапсулировать логику: Упрощает поддержку и повторное использование кода.
Повысить безопасность: Предоставлять доступ к данным через хранимые процедуры, ограничивая прямой доступ к таблицам.
Улучшить производительность: Хранимые процедуры компилируются и кэшируются, что может повысить производительность.
Управлять транзакциями: Обеспечивать целостность данных.
C# EF LINQ: Вы можете вызывать хранимые процедуры из EF Core, но:
Снижается типизация: Вы теряете преимущества статической типизации при работе с результатами хранимых процедур.
Сложность отладки: Отладка хранимых процедур может быть сложнее, чем отладка C# кода.
Триггеры и ограничения на уровне базы данных:
T-SQL: Триггеры позволяют автоматически выполнять действия при изменении данных. Ограничения (constraints) обеспечивают целостность данных.
C# EF LINQ: Вы можете реализовать логику в C#, которая будет выполняться при изменении данных, но это не всегда эффективно и может привести к проблемам с производительностью и целостностью данных.
Примеры сценариев, где T-SQL незаменим:
Сложные аналитические отчеты: Расчет скользящих средних, ранжирование, сравнение с предыдущими периодами для больших объемов данных.
Обработка иерархических данных: Построение организационных структур, BOM, маршрутизация данных.
Оптимизация производительности критичных запросов: Тонкая настройка индексов, переписывание запросов для повышения производительности.
Загрузка больших объемов данных: Использование
BULK INSERT
для быстрой загрузки данных.Реализация сложной бизнес-логики на уровне базы данных: Триггеры, хранимые процедуры для обеспечения целостности данных и безопасности.
Диалог то я веду с вами, а не с gpt4
Вы меня проверяете или хотите получить ответ на свой вопрос? Меня проверять не надо. Я же не на экзамене и не на собеседовании.
Если вас ваш же вопрос не интересует, то тем более, мне нет смысла тратить свое время на ответ.
И это Gemini 2.0 Flash Light. Я между прочим потратил личные деньги на генерацию ответа для вас. Не много, но спасибо сказать можно. Если оно, конечно, вам нужно. Если нет, то вопрос исчерпан, полагаю.
Такой ответ ничем не лучше, чем ответ в стиле "RTFM". Ни по смыслу, ни по сетевому этикету. Интересен конкретный опыт, а не то что "придумает" сетка.
Мой опыт 25 лет в этом. Что конкретно вас интересует? LLM может сгенерировать бесконечное множество ответов. Но конкретно этот ответ на мною сформулированный помпт, мною проверен и валидирован и он 100% релевантен вашему вопросу, заданному выше. Вам шашечки или ехать?
По сетевому этикету, попрошу вас не флудить и не писать сюда оффтоп, т.е. нерелевантный исходной теме статьи контент.
Мой опыт 25 лет в этом. Что конкретно вас интересует?
Вопрос был:
А можете развить тему, что в SqlServer такого бизнесового и исключительно, что вы им восхищаетесь.
Ответа в тексте LLM нет, только нагромождение общих тезисов.
Похоже не за горами, когда в комментариях введут кнопку: "Мне важно именно твоё мнение, а не тупая справка от ИИ."
А выглядит это крайне некрасиво и оставляет только негативные впечатления о человеке, который отвечает на вопрос с помощью ИИ. Такой человек видимо считает окружающих идиотами, неспособными самостоятельно пользоваться чат-ботами LLM.
EF прекрасно справляется с предоставлением доступа к инфраструктуре MSSQL. Поэтому ничто не мешает бизнес-логику в той или иной степени распределять на код БД (если "вера позволяет"). Без linq2db.
Но например BulkCopy в linq2db намного удобнее и универсальнее.
BulkCopy - это функционал MSSQL. linq2db - просто посредник, интерфейс, портал. Посредников существует много, суть данной статьи не в этом.
BulkCopy - это функционал MSSQL. linq2db - просто посредник, интерфейс, портал.
А кто спорит? Только это был комментарий на утверждение:
EF прекрасно справляется с предоставлением доступа к инфраструктуре MSSQL
У ef core нет интерфейса к BulkCopy, если только не спуститься на уровень Ado.Net.
Ок. В моей практике bulk copy применялся редко. Мало, что могу сказать на этот счет. Но если нужно, то можно реализовать через Microsoft.Data
.SqlClient
А миграции? Ведь балк нужен раз в сто лет, а миграции нужны всегда.
Оптимальный подход к работе с T-SQL в .NET Core EF Core - это гибридный подход.
Оптимальный подход к работе с T-SQL в EF - не использовать T-SQL в EF.
Он там просто не нужен:
- Если у нас низконагруженное приложение, то можно просто забить на неоптимальность некоторых запросов - MSSQL достаточно производительная СУБД.
- Если кровавый Ынтырпрайз, то все тяжелые штуки отдаются на откуп программисту бд, который сделает оптимизированные по самое нехочу хранимые процедуры - останется просто вызвать.
- А если что-то специфичное, то сразу берем Dapper и пользуемся всеми особенностями диалекта T-SQL
В эпоху цифровой экономики - каждый тик процессора - это деньги. Чем ниже себестоимость, тем выше прибыль. При масштабировании это тоже масштабируется.
В статье не предлагается использовать t-sql в ef. Предлагается смело использовать мощь t-sql (прежде всего CTE, SP, UDF) в MSSQL, а через EF успешно и просто пользоваться этим без громоздкого и низкоэффективного в сложных случаях LINQ и миграций code-first. Как? Об этом статья.
Начало работы приложения+EF запросы с небольшой базой - весело и задорно, код пишется на волне классов... Со временем в простой базе данных становится больше, оптимизировать ad-hoc запрос идущий из приложения в базу невозможно не изменяя код приложения, а иногда и там невозможно, все же делается EF. Далее - долго выполняющиеся запросы, таймауты, дедлоки. Это и про ордер бай делать в приложении. А про трафик мы не думаем?
Запросы, сгенерированные "умным" EF+Linq - длиной 200000 символов, возвращают туеву хучу записей.....
Были бы процедуры - их можно оптимизировать, а в это случае - *** там. Вот с таким произведением я сейчас долбусь.
Не думаю, что процедуры помогли бы вашему проекту. Вы бы сейчас точно также жаловались на трехэтажные запросы и спагетти код в бд.
Спасибо за интересный опыт. Если будет настроение напишите еще. Любые подробности.
Кстати сегодня я опубликовал новую версию этой статьи с учетом результатов довольно интересной дискуссии здесь в комментариях. Ссылка: https://habr.com/ru/articles/914378/
Хз, я обычно совмещаю, ef + dapper, ну т.е. простенькие crud берет на себя ef а уже тяжёлые запросы с cte или ещё что нибудь пишу через dapper
Когда я впервые пришел в проект на дотнете, ну недельку помучал linq и посмотрел в профайле что он генерит(студия не умела или лицензии не хватало), и в итоге на linq я мог написать любой запрос так как я хотел чтобы он выглядел на tsql, без лишних полей и с правильными соединениями. И ни одного запроса на голом sql в проекте писать не потребовалось, а запросы были очень интересные и разнообразные.
Если взять многоэтапный CTE запрос и попытаться его перенести на LINQ - то становится понятна суть идеи статьи.
Я про тоже , опыт как раз показал, что сложные выборки, для обработки эффективнее делаются поэтапно с обработкой в циклах на шарпе, с использованием параллельности и асинхронности .
Речь идет про обработку данных, Выборки для отчетов конечно эффективнее делать другими способами.
Спасибо за ваши комментарии и обратную связь.
T-SQL в .NET Core EF Core: Гибридный подход к производительности и гибкости