Сырой SQL, хранимые процедуры и интерполяция
Безопасно выполняйте сырой SQL с помощью FromSqlRaw, FromSqlInterpolated и хранимых процедур, избегая внедрения кода.
«Сырой SQL, хранимые процедуры и интерполяция» — бесплатный урок C# Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C# Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C# Academy содержит 4 уроков всего.
Когда использовать сырой SQL в EF Core
Большинство запросов хорошо работает с LINQ, но для некоторых сценариев нужен сырой SQL: сложная аналитика, функции конкретного поставщика, хранимые процедуры или критичные к производительности запросы, которые LINQ переводит неудачно.
EF Core предоставляет безопасные способы использования сырого SQL без ущерба для безопасности.
FromSqlRaw: безопасные параметризованные запросы
FromSqlRaw выполняет сырой SQL и преобразует результаты обратно в сущности. Всегда используйте заполнители {0} — никогда не объединяйте строки — чтобы предотвратить SQL-инъекции.
// SAFE: parameterized
var products = await _db.Products
.FromSqlRaw("SELECT * FROM Products WHERE Price > {0}", 100m)
.ToListAsync();
// DANGEROUS: never do this
// FromSqlRaw("SELECT * FROM Products WHERE Price > " + price)FromSqlInterpolated: безопасность интерполированных строк
FromSqlInterpolated принимает интерполированную строку C#. EF Core автоматически преобразует интерполированные значения в параметры SQL — это безопасно и удобно для чтения.
decimal minPrice = 100m;
string category = "Electronics";
var products = await _db.Products
.FromSqlInterpolated(
$"SELECT * FROM Products WHERE Price > {minPrice} AND Category = {category}")
.OrderBy(p => p.Name)
.ToListAsync();
// Values become @p0, @p1 parameters automaticallyСоставление запросов LINQ после FromSql
После FromSql можно объединять операторы LINQ в цепочку. EF Core оборачивает сырой SQL во вложенный запрос и применяет поверх него LINQ, что удобно для добавления фильтрации или разбиения на страницы.
decimal min = 50m;
var page = await _db.Products
.FromSqlInterpolated($"SELECT * FROM Products WHERE Price > {min}")
.Where(p => p.IsActive) // additional LINQ filter
.OrderBy(p => p.Price)
.Skip(0).Take(20) // pagination
.ToListAsync();
// Generates: SELECT ... FROM (raw SQL) AS p WHERE ...Выполнение хранимых процедур
Вызывайте хранимые процедуры, возвращающие строки сущностей, через FromSqlRaw. Для процедур, которые не возвращают строки (например, INSERT/UPDATE), используйте ExecuteSqlRawAsync.
// Stored proc returning Products
var products = await _db.Products
.FromSqlRaw("EXEC GetProductsByCategory {0}", "Books")
.ToListAsync();
// Stored proc with no return rows
await _db.Database.ExecuteSqlRawAsync(
"EXEC ArchiveOldOrders {0}",
DateTime.UtcNow.AddYears(-1));ExecuteSqlInterpolated для DML
ExecuteSqlInterpolated выполняет инструкции DML (INSERT, UPDATE, DELETE) с безопасностью интерполированных строк. Метод возвращает количество затронутых строк.
int days = 30;
int affected = await _db.Database.ExecuteSqlInterpolatedAsync(
$"UPDATE Orders SET Status = 'Expired' WHERE PlacedAt < {DateTime.UtcNow.AddDays(-days)}");
Console.WriteLine($"{affected} orders expired");Запросы к типам, не являющимся сущностями
Используйте SqlQuery<T> (.NET 7+), чтобы запрашивать произвольные скалярные типы или DTO, которые не являются сущностями EF Core. DbSet не требуется.
// Query a DTO directly — no entity needed
var stats = await _db.Database
.SqlQuery<ProductStats>(
$"SELECT Category, COUNT(*) AS Count, AVG(Price) AS AvgPrice FROM Products GROUP BY Category")
.ToListAsync();
public record ProductStats(string Category, int Count, decimal AvgPrice);Типы сущностей без ключа для представлений
Сопоставьте представление базы данных или результат сырого запроса с типом сущности без ключа с помощью HasNoKey() и ToView(). Запрашивайте его как DbSet, не задавая первичный ключ.
public class ProductSalesSummary
{
public string Category { get; set; } = "";
public int TotalSold { get; set; }
public decimal Revenue { get; set; }
}
// In OnModelCreating:
modelBuilder.Entity<ProductSalesSummary>()
.HasNoKey()
.ToView("vw_ProductSalesSummary");
// Query:
var summary = await _db.Set<ProductSalesSummary>().ToListAsync();Как избежать ошибок, связанных с SQL-инъекциями
Золотое правило: никогда не объединяйте пользовательский ввод со строками SQL. Всегда используйте параметризованные запросы через заполнители FromSqlRaw, FromSqlInterpolated или LINQ в EF Core.
// SAFE
var name = userInput;
var result = await _db.Products
.FromSqlInterpolated($"SELECT * FROM Products WHERE Name = {name}")
.ToListAsync();
// INJECTION RISK (never do this)
// FromSqlRaw("SELECT * FROM Products WHERE Name = '" + name + "'");Выходные параметры и резервный вариант ADO.NET
EF Core не поддерживает выходные параметры хранимых процедур напрямую. В таких случаях используйте ADO.NET через _db.Database.GetDbConnection().
await _db.Database.OpenConnectionAsync();
var conn = _db.Database.GetDbConnection();
using var cmd = conn.CreateCommand();
cmd.CommandText = "EXEC GetOrderCount @CustomerId, @Count OUTPUT";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter("@CustomerId", 5));
var outParam = new SqlParameter("@Count", SqlDbType.Int) { Direction = ParameterDirection.Output };
cmd.Parameters.Add(outParam);
await cmd.ExecuteNonQueryAsync();
int count = (int)outParam.Value;Реальный пример: резервный вариант для полнотекстового поиска
С помощью LINQ нельзя выразить предикаты полнотекстового поиска, специфичные для базы данных. Используйте FromSqlInterpolated, чтобы задействовать встроенные возможности FREETEXT или tsvector.
string query = "keyboard mechanical";
var results = await _db.Products
.FromSqlInterpolated(
$"SELECT * FROM Products WHERE FREETEXT(Name, {query})")
.AsNoTracking()
.Take(20)
.ToListAsync();Быстрая проверка
Почему FromSqlInterpolated безопаснее объединения строк при создании параметризованных запросов?
Итоги: сырой SQL в EF Core
Главное:
FromSqlRaw: используйте заполнители {0}; никогда не объединяйте пользовательский ввод со строкамиFromSqlInterpolated: безопасные интерполированные строки → параметры ADO.NET- Объединяйте операторы LINQ в цепочку после FromSql, чтобы добавлять фильтрацию, сортировку и разбиение на страницы
ExecuteSqlInterpolatedAsync: безопасное выполнение DMLSqlQuery<T>: запросы к DTO, не являющимся сущностями (.NET 7+)- Используйте ADO.NET напрямую для выходных параметров или сложных хранимых процедур
Часто задаваемые вопросы
Урок «Сырой SQL, хранимые процедуры и интерполяция» бесплатный?
Да — полный текст урока «Сырой SQL, хранимые процедуры и интерполяция» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C# Academy, подпишись на CoddyKit PRO. Курс C# Academy содержит 4 уроков всего.
Чему я научусь в уроке «Сырой SQL, хранимые процедуры и интерполяция»?
Безопасно выполняйте сырой SQL с помощью FromSqlRaw, FromSqlInterpolated и хранимых процедур, избегая внедрения кода. Ты практикуешь C# Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать C# Academy?
Предыдущий опыт не требуется. C# Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «Сырой SQL, хранимые процедуры и интерполяция»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке C# Academy?
Да. Каждый урок C# Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Жадная, ленивая и явная загрузка
- Сырой SQL, хранимые процедуры и интерполяция
- Компилируемые запросы и производительность
- Глобальные фильтры запросов и принадлежащие сущности