İLERİ
Raw SQL & Transactions
EF Core'un LINQ çevirisi her senaryoya yetmez — karmaşık sorgular, performans-kritik SQL'ler veya stored procedure çağrıları için raw SQL kullanırsın.
Veritabanı sağlayıcısı
Bu sayfadaki eşleşen örnekleri seçilen sağlayıcıya göre gösterir.
Raw SQL Sorguları
// Parametreli sorgu (SQL Injection korumalı!)
var minPrice = 1000m;
var products = context.Products
.FromSqlInterpolated($"SELECT * FROM Products WHERE Price > {minPrice}")
.ToList();
// Stored Procedure çağrısı
var categoryId = 1;
var products = context.Products
.FromSqlRaw("EXEC GetProductsByCategory @CategoryId = {0}", categoryId)
.ToList();
// Non-query (INSERT/UPDATE/DELETE)
var affected = context.Database
.ExecuteSqlInterpolated($"UPDATE Products SET IsActive = 0 WHERE Stock = 0");
GÜVENLİK: Asla string concatenation kullanma!
// ❌ YANLIŞ — SQL Injection açığı! var sql = $"SELECT * FROM Products WHERE Name = '{userInput}'"; // ✅ DOĞRU — Parametreli context.Products.FromSqlInterpolated($"SELECT * FROM Products WHERE Name = {userInput}");🆕 EF Core 10: Yeni bir Roslyn analyzer eklendi —
FromSqlRawiçinde string concatenation yaparsanız derleme zamanında uyarı verir:// ⚠️ EF10 Analyzer uyarısı: EF1002 - SQL injection risk var users = context.Users.FromSqlRaw("SELECT * FROM Users WHERE [" + fieldName + "] IS NULL"); // Güvenli olduğundan eminseniz: #pragma warning disable EF1002
FromSqlRaw vs FromSqlInterpolated
| Metot | Parametre Stili | Güvenlik | Ne Zaman Kullan |
|---|---|---|---|
FromSqlInterpolated |
$"... {variable}" |
Otomatik parametre | Varsayılan tercih |
FromSqlRaw |
"... {0}", arg |
Placeholder bazlı | Dinamik SQL, sp_executesql |
ExecuteSqlInterpolated |
$"... {variable}" |
Otomatik parametre | Non-query (UPDATE/DELETE) |
ExecuteSqlRaw |
"... {0}", arg |
Placeholder bazlı | Dinamik DDL |
ExecuteSqlRaw güvenlik notu:
FromSqlRawile aynı riskleri taşır. String concatenation ile değişken ekleme = SQL Injection. Her zaman{0}placeholder veyaExecuteSqlInterpolatedtercih et.
// FromSqlInterpolated — $ ile yazılan her değişken parametre olur
var city = "İstanbul";
var orders = context.Orders
.FromSqlInterpolated($"SELECT * FROM Orders WHERE City = {city}")
.Where(o => o.IsActive) // ← LINQ zincirlenebilir!
.OrderBy(o => o.OrderDate)
.ToListAsync();
// FromSqlRaw — pozisyonel placeholder
var orders = context.Orders
.FromSqlRaw("SELECT * FROM Orders WHERE City = {0} AND Total > {1}", city, 1000)
.ToListAsync();
// ⚠️ FromSqlRaw'da string interpolation KULLANMA:
// ❌ context.FromSqlRaw($"...WHERE Name = '{input}'") ← SQL INJECTION!
LINQ zincirleme:
FromSql*sonrasına.Where(),.OrderBy(),.Include()eklenebilir — EF bunları alt sorgu (subquery) olarak sarar.
Unmapped Type Queries (EF Core 8+)
// SqlQuery<T> — DbSet'e bağlı olmayan sorgular (DTO, scalar, aggregate)
var stats = await context.Database
.SqlQuery<CategoryStats>($"""
SELECT c.Name AS CategoryName, COUNT(*) AS ProductCount, AVG(p.Price) AS AvgPrice
FROM Categories c
JOIN Products p ON p.CategoryId = c.Id
GROUP BY c.Name
""")
.OrderByDescending(s => s.ProductCount) // ← LINQ zincirlenebilir!
.ToListAsync();
// Scalar değer
var count = await context.Database
.SqlQuery<int>($"SELECT COUNT(*) AS [Value] FROM Products WHERE IsActive = 1")
.SingleAsync();
// DTO tanımı (entity olmak zorunda değil, DbSet gerekmez)
public record CategoryStats(string CategoryName, int ProductCount, decimal AvgPrice);
SqlQuery
kuralları:
- Sütun adları property adlarıyla eşleşmeli (AS ile alias ver)
- Scalar sorgularda sütun adı
Valueolmalı- LINQ zincirleme desteklenir (subquery olarak sarılır)
Transaction Yönetimi
// Basit — SaveChanges zaten transaction kullanır
context.Products.Add(product);
context.Orders.Add(order);
await context.SaveChangesAsync(); // Tek transaction'da ikisi de kaydedilir
// Manuel transaction (birden fazla SaveChanges gerekiyorsa)
using var transaction = await context.Database.BeginTransactionAsync();
try
{
context.Accounts.Update(sender);
await context.SaveChangesAsync();
context.Accounts.Update(receiver);
await context.SaveChangesAsync();
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
SQL karşılığı:
BEGIN TRANSACTION;
UPDATE [Accounts] SET [Balance] = [Balance] - 1000 WHERE [Id] = 1;
UPDATE [Accounts] SET [Balance] = [Balance] + 1000 WHERE [Id] = 2;
COMMIT TRANSACTION;
-- Hata olursa: ROLLBACK TRANSACTION;
BEGIN;
UPDATE accounts SET balance = balance - 1000 WHERE id = 1;
UPDATE accounts SET balance = balance + 1000 WHERE id = 2;
COMMIT;
-- Hata olursa: ROLLBACK;
-- Not: PostgreSQL'de DDL (CREATE TABLE vs.) de transaction'a dahildir!
-- SQL Server'da DDL transaction'ı implicit commit edebilir.