Indexing ve Query Optimization¶
Doğru indexleme ve sorgu optimizasyonu veritabanı performansının temelidir; eksik veya yanlış index’ler yavaş sorgulara ve kaynak israfına yol açar.
1. Index Tanımlamamak¶
❌ Yanlış Kullanım: Sık sorgulanan alanlarda index olmadan çalışmak.
public class AppDbContext : DbContext
{
public DbSet<Order> Orders { get; set; }
}
// Her sorguda full table scan
var orders = await _context.Orders
.Where(o => o.CustomerId == customerId && o.Status == "Active")
.ToListAsync();
✅ İdeal Kullanım: Sorgu pattern’lerine göre index tanımlayın.
public class AppDbContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>(entity =>
{
// Tek alan index
entity.HasIndex(o => o.CustomerId);
// Composite index (sık kullanılan filtre kombinasyonu)
entity.HasIndex(o => new { o.CustomerId, o.Status })
.HasDatabaseName("IX_Orders_CustomerId_Status");
// Unique index
entity.HasIndex(o => o.OrderNumber).IsUnique();
// Filtered index (sadece aktif kayıtlar)
entity.HasIndex(o => o.CreatedAt)
.HasFilter("[Status] = 'Active'")
.HasDatabaseName("IX_Orders_CreatedAt_ActiveOnly");
});
}
}
2. Select * ile Tüm Alanları Çekmek¶
❌ Yanlış Kullanım: İhtiyaç dışı alanları sorgulamak.
var orders = await _context.Orders
.Include(o => o.Customer)
.Include(o => o.OrderItems)
.ThenInclude(oi => oi.Product)
.Where(o => o.CustomerId == customerId)
.ToListAsync();
// Tüm navigation property'ler ve alanlar çekilir
✅ İdeal Kullanım: Projection ile sadece gerekli alanları seçin.
var orders = await _context.Orders
.Where(o => o.CustomerId == customerId)
.Select(o => new OrderSummaryDto
{
Id = o.Id,
OrderNumber = o.OrderNumber,
TotalAmount = o.OrderItems.Sum(oi => oi.Price * oi.Quantity),
CustomerName = o.Customer.Name,
ItemCount = o.OrderItems.Count,
CreatedAt = o.CreatedAt
})
.ToListAsync();
// Tek sorgu, sadece gerekli alanlar, JOIN otomatik
3. Tracking’i Gereksiz Yere Açık Bırakmak¶
❌ Yanlış Kullanım: Read-only sorgularda change tracking aktif.
var products = await _context.Products
.Where(p => p.IsActive)
.ToListAsync(); // Change tracker tüm entity'leri izler
// Sadece okuma yapılacak ama bellek ve CPU harcanır
✅ İdeal Kullanım: Read-only sorgularda AsNoTracking kullanın.
// Tek sorguda
var products = await _context.Products
.AsNoTracking()
.Where(p => p.IsActive)
.ToListAsync();
// DbContext seviyesinde (read-only context)
public class ReadOnlyDbContext : DbContext
{
public ReadOnlyDbContext(DbContextOptions<ReadOnlyDbContext> options)
: base(options)
{
ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
ChangeTracker.AutoDetectChangesEnabled = false;
}
}
// Kayıt
builder.Services.AddDbContext<ReadOnlyDbContext>(options =>
options.UseSqlServer(connectionString)
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));
4. N+1 Sorgu Problemi¶
❌ Yanlış Kullanım: Döngü içinde veritabanı sorgusu yapmak.
var categories = await _context.Categories.ToListAsync();
foreach (var category in categories)
{
var productCount = await _context.Products
.CountAsync(p => p.CategoryId == category.Id); // N sorgu
category.ProductCount = productCount;
}
✅ İdeal Kullanım: Tek sorguda tüm veriyi çekin.
var categories = await _context.Categories
.Select(c => new CategoryDto
{
Id = c.Id,
Name = c.Name,
ProductCount = c.Products.Count
})
.ToListAsync(); // Tek sorgu, SQL GROUP BY
// Veya GroupBy ile
var productCounts = await _context.Products
.GroupBy(p => p.CategoryId)
.Select(g => new { CategoryId = g.Key, Count = g.Count() })
.ToDictionaryAsync(x => x.CategoryId, x => x.Count);
5. Raw SQL’i Güvensiz Kullanmak¶
❌ Yanlış Kullanım: String interpolation ile SQL injection riski.
var products = await _context.Products
.FromSqlRaw($"SELECT * FROM Products WHERE Name LIKE '%{searchTerm}%'")
.ToListAsync();
// SQL Injection: searchTerm = "'; DROP TABLE Products; --"
✅ İdeal Kullanım: Parametreli sorgular veya FromSqlInterpolated kullanın.
// Parametreli sorgu
var products = await _context.Products
.FromSqlInterpolated(
$"SELECT * FROM Products WHERE Name LIKE {'%' + searchTerm + '%'}")
.ToListAsync();
// Veya LINQ ile (daha güvenli)
var products = await _context.Products
.Where(p => EF.Functions.Like(p.Name, $"%{searchTerm}%"))
.ToListAsync();
// Karmaşık sorgular için stored procedure
var report = await _context.Database
.SqlQueryRaw<SalesReport>(
"EXEC sp_GetSalesReport @StartDate, @EndDate",
new SqlParameter("@StartDate", startDate),
new SqlParameter("@EndDate", endDate))
.ToListAsync();