Приходилось ли писать низкоуровневые SQL-запросы напрямую к БД?

Ответ

Да, в ряде сценариев работа с "сырым" SQL необходима, даже при использовании ORM вроде Entity Framework Core.

Типичные случаи и подходы:

  1. Сложные аналитические запросы: Операции, которые неэффективно или невозможно выразить через LINQ (оконные функции, рекурсивные CTE, сложные агрегации).

    var query = @"
        WITH RankedOrders AS (
            SELECT 
                CustomerId,
                OrderDate,
                TotalAmount,
                ROW_NUMBER() OVER (PARTITION BY CustomerId ORDER BY OrderDate DESC) as rn
            FROM Orders
        )
        SELECT * FROM RankedOrders WHERE rn = 1";
    var lastOrders = await _context.Orders.FromSqlRaw(query).ToListAsync();
  2. Массовые операции (Bulk Operations): Для обновления/удаления большого объема данных ExecuteSqlRaw или ExecuteSqlInterpolated эффективнее, чем загрузка сущностей в память.

    await _context.Database.ExecuteSqlInterpolatedAsync(
        $"UPDATE Products SET Price = Price * {1.1m} WHERE CategoryId = {categoryId}");
  3. Работа с ADO.NET напрямую: Для максимального контроля над подключением и чтением данных.

    using (var connection = new SqlConnection(_config.GetConnectionString("Default")))
    {
        await connection.OpenAsync();
        var command = new SqlCommand("SELECT Id, Name FROM Users WHERE IsActive = @isActive", connection);
        command.Parameters.AddWithValue("@isActive", true); // КРИТИЧНО: всегда использовать параметры!
    
        using (var reader = await command.ExecuteReaderAsync())
        {
            while (await reader.ReadAsync())
            {
                var id = reader.GetInt32(0);
                var name = reader.GetString(1);
                // ... обработка
            }
        }
    }

Важно: При любом использовании прямого SQL обязательны параметризация запросов для защиты от SQL-инъекций и анализ плана выполнения для обеспечения эффективности.

Ответ 18+ 🔞

Ну вот, опять про этот ваш SQL, прям как будто без него совсем никак! Слушай, а ты в курсе, что иногда ORM — это как ехать на работу на танке? Надёжно, блядь, непробиваемо, но бензина жрёт дохуя, и парковаться — ёперный театр!

Вот смотри, бывают же ситуации, когда этот Entity Framework просто разводит руками и говорит: «Сам, сука, разбирайся!».

Типичные случаи, когда надо лезть в это сырое мясо:

  1. Запросы, от которых у LINQ мозг вытекает. Ну серьёзно, там какие-то оконные функции, рекурсивные CTE, сложнейшие агрегации. Пытаться это через LINQ выразить — это как пытаться объяснить теорию относительности мартышке. Проще написать SQL и не ебать себе мозг.

    var query = @"
        WITH RankedOrders AS (
            SELECT 
                CustomerId,
                OrderDate,
                TotalAmount,
                ROW_NUMBER() OVER (PARTITION BY CustomerId ORDER BY OrderDate DESC) as rn
            FROM Orders
        )
        SELECT * FROM RankedOrders WHERE rn = 1";
    var lastOrders = await _context.Orders.FromSqlRaw(query).ToListAsync();

    Вот видишь? Красота же! Чисто, понятно, и база данных не обосрётся от кривого плана запроса.

  2. Когда надо обновить или удалить овердохуища записей. Представь: UPDATE на миллион строк. Загружать их все в память через EF, менять свойство и вызывать SaveChangesAsync — это, прости, уровень долбоёба. Сервер памяти сожрёт и сдохнет. А так — хуяк одним запросом, и все дела.

    await _context.Database.ExecuteSqlInterpolatedAsync(
        $"UPDATE Products SET Price = Price * {1.1m} WHERE CategoryId = {categoryId}");

    Смотри, как аккуратно: ExecuteSqlInterpolatedAsync. Параметры сами подставятся, SQL-инъекции не будет. Умно и быстро.

  3. Абсолютный контроль, когда терпения ноль ебать. Хочешь рулить подключением, читать данные потоком, как бог? Пожалуйста, на тебе старый добрый ADO.NET. Только не обосрись.

    using (var connection = new SqlConnection(_config.GetConnectionString("Default")))
    {
        await connection.OpenAsync();
        var command = new SqlCommand("SELECT Id, Name FROM Users WHERE IsActive = @isActive", connection);
        command.Parameters.AddWithValue("@isActive", true); // СЛУШАЙ СЮДА, НЕ ХУЙ ЗАБЫВАТЬ! ВСЕГДА ПАРАМЕТРЫ!
    
        using (var reader = await command.ExecuteReaderAsync())
        {
            while (await reader.ReadAsync())
            {
                var id = reader.GetInt32(0);
                var name = reader.GetString(1);
                // ... ну и делай тут что хочешь
            }
        }
    }

    Видишь эту строку с параметрами? Это святое. Кто строки конкатенирует — тот пидарас шерстяной и его надо гнать из профессии поганой метлой.

И главное, запомни раз и нахуй: если уж полез в ручной SQL, то:

  1. ПАРАМЕТРИЗАЦИЯ. Я не шучу. Это не обсуждается.
  2. СМОТРИ ПЛАН ЗАПРОСА. А то напишешь какую-нибудь хуйню с SELECT * и NOLOCK на всю таблицу, а потом удивляешься, почему всё встало колом. Будет тебе Хиросима, а не апдейт.

В общем, инструмент на все случаи жизни. Главное — мозги включать, а не просто копипастить с StackOverflow.