Реклама
Комментарии 24
Спасибо за полезную информацию! Правда из своего опыта работы с БД из .NET, используя ORM пришел к выводу, что проще и эффективнее либо писать чистый SQL с маппингом результатов (Dapper, EF .SqlQuery), либо организовывать хранилище данных таким образом, чтобы для «боевых» выборок никогда не требовалось бы что-то сложнее простого select + where и несложных group by.
Расскажите, как обновляете БД? Пишете всю раскладку со сравнениями изменённых данных для всего графа?
Немного не понял вопрос. Если вы о об изменении данных в таблицах, то зачастую все эти операции можно доверить ORM. Простые insert, update by id транслируются в адекватный SQL. Сложных запросов, даже типа 'update where', где условие включает в себя нечто большее чем match по индексируемому полю я так же стараюсь избегать. Вообще сейчас, когда есть возможность использовать объемные и надежные СХД все больше update становятся insert (event sourcing), а о delete лучше вообще забыть.
Если про схему, то механизм миграций в EF — отличная вещь. Если чего-то не хватает в штатном генераторе — всегда можно дописать SQL в миграции. Если хочется отказаться от EF, то нужен отдельный скрипт под VCS, позволяющий создать/обновить базу. Хорошо, если в таком случае ведением таких скриптов будет заниматься отдельный сотрудник, который в случае чего сможет и написать скрипт под сложную миграцию базы с данными.
Да, я про изменение данных.

Ясно, по первому комментарию решил, что вы отказались от полноценной ORM и перешли на чистый SQL, а вы используете сразу и то, и то.

спасибо! всегда хотелось выработать какую-то стратегию совмещения EF с оптимизацией

спасибо за статью, очень интересно было прочитать.
хотелось бы поинтересоваться у сообщества, кто какой подход предпочитатет в плане того, какое количество логики писать на c#/linq и какое на стороне базы через хранимые процедуры/функции?

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

Хотелось бы узнать у кого какой опыт?

Примерно такой же стратегии придерживаюсь по возможности

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

Делать многосоставной кластерный индекс, в котором может быть много полей — это спорный вариант для производительности, частые операции модификации могут сделать их обслуживание дорогим, а выборки начнут простаивать в блокировках ( все же таблицы достаточно большие). Фильтрованный индекс — это хорошее решение, но делать его для достаточно сложных предикатов отбора — это опять же закладывать камень под производительность. Все таки пересечения условий могут быть очень размазаны по всему возможному диапазону значений, да и содержать сложные сочетания условий and, or, is not null.

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

Достаточно часто встречал случаи, когда фильтруемый индекс не то, что не работал, а даже в подсказках не мог быть использован.
По ому с ним надо очень аккуратно-уж лучше секционированный индекс или по старинке разбить таблицы на оперативные и неоперативные данные.

Сколько лет уже существует linq 2 SQL / EF — столько я убеждаюсь, что правильно сделал, что не поддался хайпу, и оставил классический принцип построения систем:
1. RDBMS, спроектированная профессионалом
2. объекты RDBMS, написанные автором п.1
3. Максимально возможная бизнес-логика внутри п.1,2
4. Взаимодействие с БД только через п.2
5. Обертки над п.4 на процедурном ЯП и бизнес-логика, которую сложно/невозможно сделать в п.1,2
6...99999 Любые обертки и надстройки сверху п.5

Если бизнес-требования к системе более-менее устойчивы, то гораздо проще и надежнее сделать п.1,2 на T-SQL, чем пытаться извращениями добиться хотя бы порядково похожей эффективности на Linq2SQL/Linq2Entities, особенно в системах со сложной моделью данных и правилами их модификации.
Пункт 3 позволяет передавать бизнес-знания всего ОДНОМУ специалисту, и этот специалист как раз является инженером высокой квалификации.
Далее, п.4 гарантирует, что ни один джун не напишет кривой запрос, который выгрузит на клиент пол-базы, забрав в качестве payload всего один скаляр, и вообще, НЕ БУДЕТ никаких взаимодействий с БД, ни на чтение, ни на запись помимо определенных в п.2 точек входа. Мы просто запрещаем пермиссиями обращаться к данным иначе, чем через объявленные объекты (процедуры, TVF, вьюхи)
Следствием детерминирования доступа к БД является полезная возможность комбинировать средства синхронизации доступа к данным со средствами потоковой синхронизации — (например sp_getapplock). Для длинных бизнес-действий часто надежнее выстроить их в один поток этими средствами, чем использование serializable-транзакций

В п.6 и выше можно использовать специалистов любого уровня — они не смогут при всем желании ни испортить бизнес-целостность данных, ни существенно повлиять на производительность.

Где же здесь место для EF/Linq2Entities? Оно очень удобно для генерации оберточных классов вокруг сигнатур SP и резалтсетов возврата. Не более того.

Отладка такой системы очень проста — п.1,2 проверяются тест-скриптами, которые вызывают объекты БД, имитируя карту действий фронтенда. Для п.6 и выше можно использовать тестовые БД с предопределенными данными, а можно и данные, полученные прогоном тестовых скриптов из предыдущего пункта
Performance optimization делается тоже очень прозрачно — все вызовы детерминированы, а T-SQL код в них написан человеком и легко читаем/понимаем. Оптимизировать объекты БД можно прямо на лету, при условии, что сигнатуры и резалтсеты методов БД не меняются.
Имена вызываемых методов видны сразу в SQL Profiler, и обычно сразу видно что, для чего, и в каком порядке вызывается — сравните с адовой лапшой, которая льется SQL-текстом из Linq2SQL в профайлер без малейшей возможности без скрупулезного разбора понять, что именно в нем делается.

Да, тривиальный бакенд можно очень быстро накидать на code-first, но подумайте, останется ли он простым? Если концепт пойдет, не придется ли переделывать все заново? И потом, простые концепты обычно показывают бизнес-пиплу — а им нужно, чтобы было красиво. Так вот, для простых концептов дельта затрат на то, чтобы сделать T-SQL средствами модель, и обернуть ее методы EF по сравнению с code first, ничтожна относительно затрат на разработку дизайна, верстку и построение фронтенда
НЛО прилетело и опубликовало эту надпись здесь
С динамикой конечно сложнее, и прежде всего не потому, что нужно помнить и бояться sql-инъекций. Существует класс задач с динамическим построением резалтсетов по метаданным, которые вообще плохо решаемы статическими обертками, но, например, хорошо решаемы с использованием старых добрых нетипизированных датасетов. Код бакенда разрабатывается одним (чаще всего его хватает, если разработка линейна, даже для больших систем) профессионалом, и это достаточная гарантия правильной работы с пользовательским вводом.
НЛО прилетело и опубликовало эту надпись здесь
Подумайте тогда для сравнения, во что превратилась бы система, если по модели в несколько сотен таблиц каждый разработчик ездил запросами вдоль и поперек, в массовых модификациях данных вместо множественных операций использовались бы единичные вставки и обновления в циклах в десятки тысяч итераций, а lazy load глубиной в 4 этажа вешался бы намертво из-за невозможности указания префиксов?

Linq2Entities — это отрубить себе пальцы рук, мотивируя тем, что «теперь никто не прорисовывает детали, а грубо можно и культей», затем понять, что для тонких/нестандарнтых вещей пальцы все-таки нужны — но их уже нет, и поэтому придумать 10 воркэраундов, как с гораздо меньшим успехом теперь рисовать ногами.
НЛО прилетело и опубликовало эту надпись здесь
Есть фундаментальное противоречие, неразрешимое в общем виде никакими средствами: RDBMS — это системы, работающие в терминах множеств. Чем ловчее вы для них определяете множества — тем эффективнее они работают на вас. Процедурные ЯП — это системы, работающие в терминах нитей исполнения. Чем ловчее вы декомпозируете алгоритм на параллельные нити — тем эффективнее они работают на вас.
Руками и в частном случае можно добиться какой-то адекватной проекции второго метода на первый с точки зрения эффективности. В общем случае — нет. Поэтому для решения задач работы с данными нужно использовать именно средства работы с данными, а не обертки.

Это если еще не говорить про эффективность обработки данных «на месте». Если про нее говорить, да еще в контексте атомарности — все проблемы умножаются на порядок. Вынуть полбазы под транзакцией, модифицировать и записать назад итератором по одному — как вам такое?

И наконец, доказательство:

1. разработка программного функционала стоит денег
2. программный функционал реализует свойства системы, за счет которых она эффективно (конкурентноспособно) продается
3. только малая часть функционала RDBMS от Microsoft поддерживается в обертке их же производства — большая часть может быть использована только не-ORM средствами
4. от версии к версии количество такого не-ORM функционала, по крайней мере, не уменьшается (на самом деле — растет)
=> существует более эффективный способ работы с RDBMS, использующий весь имеющийся функционал, и поскольку объем такого функционала растет — значит, компания вкладывается в его поддержку => понимает, что без него не достичь таких показателей работы, чтобы оставаться конкурентноспособным на SMB-рынке. А поскольку, этот способ — T-SQL, это значит, что коммерчески для построения бакендов для автоматизации SMB T-SQL остается более подходящим.
Думал, обойдусь парой предложений…

А динамичные запросы вы через строки и sp_executesql делаете? В конце концов где-то что-то пропустите и вас нагнут через sql injection.
Нет, конечно, так нет необходимости делать. Динамические запросы нужно строить так, чтоб никакого пользовательского ввода в код запроса просто не попадало. Это всегда можно, кроме редчайших случаев типа построения констант для некоторых вариантов полнотекстового поиска на MS SQL — вот в этих и только этих случаях да, приходится писать обертки, которые не позволят выбраться зловредному пользовательскому вводу за пределы строковых констант. Даже для запросов с 100500 вариантами фильтров, соединений и выражений в том же MS SQL, если не хотите использовать условия запуска подветок, обычно очень неплохо подходит запрос с перекомпиляцией, если вы хорошо понимаете, как написать сколь угодно большой запрос так, чтобы оптимизатор для начала гарантированно отрубил все неиспользуемые ветви, а затем эффективно построил план для оставшейся части — и да, для этого знать про option(recompile) совершенно недостаточно.

Чистый SQL не очень подходит для бизнес логики, кроме совсем простой
Это попросту не так. И да, я видел и некоторое время поучаствовал в разработке такой огромной системы. Там, совершенно как в примере gleb_l, точно такое отношение к безопасности — базовые права гарантирует сам сервер, остальное — процедурной частью, а логика вообще вся на сервере, ни одного запроса мимо интерфейсных объектов, да мимо и невозможно — см. пункт про права на объекты БД. Или же ваше утверждение требует переформулировки или дополнений, например — по факту очень трудно найти специалистов БД, которые способны все это спроектировать, расписать и при необходимости развивать и поддерживать. Например, в сравнением с рынком специалистов по C# — это очень заметно, факт.

код бакенда разрабатывается одним профессионалом
У меня как раз система с горами такого. Спасибо, за двадцать лет там столько «профессионалов» поучавствовало, что волосы встают дыбом, там где их и быть-то не должно.
Видите, у вас ровно обратный хрестоматийный пример — вместо разработки грамотным профессионалам — «столько «профессионалов» поучавствовало» (цитата), с предсказуемыми последствиями. Подход же, озвученный gleb_l, при условии выполнения грамотным специалистом (я понимаю, что специалистом по SQL считает себя чуть менее, чем каждый каждый первый, кто умеет написать простой запрос, но сейчас я именно про грамотных профильных специалистов) очень устойчив при использовании разношерстной командой, уже в одном этом его огромный плюс.

Чистый SQL не очень подходит для бизнес логики, кроме совсем простой. Можно конечно через SQL CLR делать, но тоже так себе удовольствие. На Оракле конечно повеселее, а для SQL server-а это занятие для не слабых духом
Это говорит лишь о том, что с Ораклом вы сколько успели проработать, а с MSSQL — нет. Тут все дело в том, что подходы к ним очень, очень разные. При переходе от одного к другому — неважно, в каком порядке — сначала необходимо полностью перестроить мозги, сломать привычное мышление, совершенно изменить подход. Начинать писать на t/sql и обнаружить, что в нем отсутствуют привычные так необходимые объекты и пайпланы или, наоборот, перейти на Оракл и обескураженно искать, где же тут локальные времянки, без которых жизнь как без воздуха, и непосредственная отдача резалтсета из процедурной части клиенту — поначалу просто апатия, проклятия и ненависть, без преувеличения. Холивары MS SQL vs Oracle потому так и унылы и однообразны, что читаешь аргументы что одной, что другой строны — и видишь только, что аргументаторы привыкли мыслить только одной парадигмой, а противоположная обеим сторонам кажется какой-то инопланетной дикостью, поэтому эффективно мыслить за обе стороны мало кто пытается. В действительности же и у той, и другой стороны есть свои сильные и слабые стороны, опять же хрестоматийный случай.
Ну и да, никто обходиться без CLR не заставляет, даже совершенно не для написания на нем бизнес-логики, а исключительно для базовых вещей, которые почему-то до сих пор не завезли в MS SQL, например, почему-то отсутствующего инструментария для использования регулярок, из-за чего у каждого разработчика есть своя библиотечка на этом CLR для оберток над функционалом для работы с ними. Видите, я использовал аргумент из первой десятки в пользу Оракла, где регулярки давно из коробки :)
В названии статьи отсутствует LINQ by EF. Так как оно не отражает полную картину. EF всегда был калекой в трансляции дерева выражений в SQL.

Практически все эти детские болезни мы полечили в linq2db. Клеить строки не надо, Dapper не надо, делать хранимки на каждый чих не надо.

  • Хинты можно использовать
  • CTE из каробки, вместе с рекурсивными CTE
  • Union, Concat собирает все что надо, хоть завались ими
  • INSERT FROM, INSERT INTO, UPDATE FROM...
  • Менять названия таблиц налету
  • Использовать временные таблицы
  • Оконные функции из каробки
  • BulkCopy на все поддерживаемые базы данных
  • CRUD по CQRS паттерну, ничего лишнего вплоть до единственного поля
  • ...


“Но они продолжали жевать кактус и дискутировать какой generic repository лучше и когда стартовать UoW.”
Годная вещь. Я правильно понимаю, что можно подцепить его к EF Core с сохранением миграций, и пользоваться всей мощью linq2db?
Только полноправные пользователи могут оставлять комментарии. , пожалуйста.