TGViewer
.NET Разработчик .NET Разработчик @netdeveloperdiary · 6.75K subscribers
Post #2942 2.16K
День 2443. #ЗаметкиНаПолях
Используем Хранимые Процедуры в EF Core. Окончание

Начало
Продолжение

3. Хранимая процедура с валидацией
Допустим, вам нужно изменить количество доступных билетов, но вы хотите предотвратить состояния гонки и проверить операцию:
CREATE OR REPLACE PROCEDURE 
tt.adjust_available_quantity(
ticket_type_id uuid,
delta numeric
)
LANGUAGE plpgsql AS $$
DECLARE
v_qty numeric;
v_avail numeric;
v_new_avail numeric;
BEGIN
SELECT quantity, available_quantity
INTO v_qty, v_avail
FROM tt.ticket_types
WHERE id = ticket_type_id
FOR UPDATE;

IF NOT FOUND THEN
RAISE EXCEPTION 'Тип билета % не найден', ticket_type_id;
END IF;

v_new_avail := v_avail + delta;

IF v_new_avail < 0 THEN
RAISE EXCEPTION 'Нельзя уменьшить < 0';
END IF;

IF v_new_avail > v_qty THEN
RAISE EXCEPTION 'Нельзя превышать общее количество';
END IF;

UPDATE tt.ticket_types
SET available = v_new_avail
WHERE id = ticket_type_id;
END;
$$;

Процедура делает несколько важных вещей:
- «Запирает» строку для обновления (FOR UPDATE), чтобы другая транзакция не могла её обновить, пока не завершится хранимая процедура;
- Проверяет бизнес-правила перед внесением изменений;
- Выводит понятные сообщения об ошибках при возникновении проблем;
- Сохраняет всё атомарно в одном запросе к БД.
Всё это можно было бы реализовать на C# с ручным управлением транзакциями и явной блокировкой, но это сложнее и подвержено ошибкам. Позвольте БД делать то, в чём она хороша. Вот как это можно вызвать из EF Core:
try
{
await dbContext.Database.ExecuteSqlAsync(
$"""
CALL tt.adjust_available_quantity({ticketTypeId},{quantity})
""");
}
catch (Exception e)
{
// обработка ошибки
}

Процедура не возвращает значение, но, если она выбрасывает исключение (с помощью RAISE EXCEPTION), PostgreSQL передаст его в ваш код C#. Вы можете перехватить его и вернуть корректный ответ об ошибке.

Представления (view)
Представления БД подобны функциям без параметров. Это сохранённые запросы, к которым можно обращаться по имени. Можно выполнять запросы к ним, используя SqlQuery<T>, как к функциям:
var results = await dbContext.Database
.SqlQuery<ActiveCustomerDto>(
$"SELECT * FROM tt.active_customers")
.ToListAsync();

Или можно сопоставлять их с типами сущностей в DbContext для полной поддержки LINQ. Представления отлично подходят для часто используемых запросов, не требующих параметров. Их также можно добавить в DbContext. Функции же обеспечивают гибкость параметризации.

Итого
EF Core не заставляет вас выбирать между LINQ и чистым SQL. Вы можете использовать и то, и другое. Используйте функции, когда нужно вернуть данные, процедуры, когда нужно изменить данные со сложной логикой, и чистые SQL-запросы, когда LINQ не может эффективно удовлетворить ваши требования. Сочетание удобства EF Core и мощности БД даёт гибкость в выборе подходящего инструмента для каждого сценария.

Источник: https://www.milanjovanovic.tech/blog/using-stored-procedures-and-functions-with-ef-core-and-postgresql
  • 👍 10
More from @netdeveloperdiary
  1. Oct 2, 2026День 2802. #Карьера #Юмор Секреты Программирования, Известные Только Легендам Ещё один пос…
  2. Oct 1, 2026Post #3357
  3. Sep 30, 2026Post #3356
  4. Sep 29, 2026Фото 3 (с) Анатолий Кулаков
  5. Sep 29, 2026День 2799. Конференция DotNext 2026. Часть 1 25 и 26 сентября в Москве прошла очередная ко…
  6. Sep 28, 2026День 2798. #Оффтоп Утиная Типизация в C# с Помощью Перехватчиков. Часть 2 Некоторое время…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →