EFEF Core Handbook

ORTA

Projection & DTO Mapping

Veritabanından sadece ihtiyacın olan sütunları çeker — tüm entity'yi yüklemek yerine. Include() tüm sütunları getirir, Select() sadece belirttiklerini.

Veritabanı sağlayıcısı Bu sayfadaki eşleşen örnekleri seçilen sağlayıcıya göre gösterir.

Neden Projection?

// ❌ Kötü: Tüm sütunları çek, sonra DTO'ya maple
var products = await context.Products
    .Include(p => p.Category)
    .ToListAsync();
var dtos = products.Select(p => new ProductDto { ... });
// Problem: Tüm sütunlar çekilir + tracking overhead

// ✅ İyi: Direkt projection — sadece gereken sütunlar
var dtos = await context.Products
    .Select(p => new ProductDto
    {
        Id = p.Id,
        Name = p.Name,
        CategoryName = p.Category.Name
    })
    .ToListAsync();
-- ❌ Kötü yaklaşım: her şeyi çeker
SELECT [p].*, [c].* FROM [Products] AS [p]
INNER JOIN [Categories] AS [c] ON [p].[CategoryId] = [c].[Id];

-- ✅ İyi yaklaşım: sadece 3 sütun
SELECT [p].[Id], [p].[Name], [c].[Name] AS [CategoryName]
FROM [Products] AS [p]
INNER JOIN [Categories] AS [c] ON [p].[CategoryId] = [c].[Id];
-- ❌ Kötü yaklaşım: her şeyi çeker
SELECT p.*, c.* FROM products AS p
INNER JOIN categories AS c ON p.category_id = c.id;

-- ✅ İyi yaklaşım: sadece 3 sütun
SELECT p.id, p.name, c.name AS category_name
FROM products AS p
INNER JOIN categories AS c ON p.category_id = c.id;

Nested (İç İçe) Projection

var orders = await context.Orders
    .Select(o => new OrderDetailDto
    {
        OrderId = o.Id,
        CustomerName = o.Customer.Name,
        TotalAmount = o.LineItems.Sum(li => li.Quantity * li.UnitPrice),
        Items = o.LineItems.Select(li => new OrderItemDto
        {
            ProductName = li.Product.Name,
            Quantity = li.Quantity,
            LineTotal = li.Quantity * li.UnitPrice
        }).ToList(),
        ItemCount = o.LineItems.Count()
    })
    .ToListAsync();
SELECT [o].[Id] AS [OrderId],
       [c].[Name] AS [CustomerName],
       (SELECT SUM([li].[Quantity] * [li].[UnitPrice]) FROM [OrderLineItems] AS [li]
        WHERE [li].[OrderId] = [o].[Id]) AS [TotalAmount],
       (SELECT COUNT(*) FROM [OrderLineItems] AS [li0]
        WHERE [li0].[OrderId] = [o].[Id]) AS [ItemCount],
       [li1].[ProductName], [li1].[Quantity], [li1].[Quantity] * [li1].[UnitPrice] AS [LineTotal]
FROM [Orders] AS [o]
INNER JOIN [Customers] AS [c] ON [o].[CustomerId] = [c].[Id]
LEFT JOIN (
    SELECT [li].[OrderId], [p].[Name] AS [ProductName], [li].[Quantity], [li].[UnitPrice]
    FROM [OrderLineItems] AS [li]
    INNER JOIN [Products] AS [p] ON [li].[ProductId] = [p].[Id]
) AS [li1] ON [li1].[OrderId] = [o].[Id]
ORDER BY [o].[Id];
SELECT o.id AS order_id,
       c.name AS customer_name,
       (SELECT SUM(li.quantity * li.unit_price) FROM order_line_items AS li
        WHERE li.order_id = o.id) AS total_amount,
       (SELECT COUNT(*) FROM order_line_items AS li0
        WHERE li0.order_id = o.id) AS item_count,
       li1.product_name, li1.quantity, li1.quantity * li1.unit_price AS line_total
FROM orders AS o
INNER JOIN customers AS c ON o.customer_id = c.id
LEFT JOIN (
    SELECT li.order_id, p.name AS product_name, li.quantity, li.unit_price
    FROM order_line_items AS li
    INNER JOIN products AS p ON li.product_id = p.id
) AS li1 ON li1.order_id = o.id
ORDER BY o.id;

Koşullu Projection

// Farklı roller için farklı DTO
var products = await context.Products
    .Select(p => isAdmin
        ? new ProductDto { Id = p.Id, Name = p.Name, Cost = p.Cost, Margin = p.Price - p.Cost }
        : new ProductDto { Id = p.Id, Name = p.Name, Cost = 0, Margin = 0 })
    .ToListAsync();

// Null-safe navigation (EF otomatik LEFT JOIN yapar)
var dtos = await context.Products
    .Select(p => new
    {
        p.Name,
        CategoryName = p.Category != null ? p.Category.Name : "Kategorisiz"
    })
    .ToListAsync();
SELECT [p].[Name], COALESCE([c].[Name], N'Kategorisiz') AS [CategoryName]
FROM [Products] AS [p]
LEFT JOIN [Categories] AS [c] ON [p].[CategoryId] = [c].[Id];
SELECT p.name, COALESCE(c.name, 'Kategorisiz') AS category_name
FROM products AS p
LEFT JOIN categories AS c ON p.category_id = c.id;

Projection vs Include — Karar Tablosu

Senaryo Tercih Neden
API'ye DTO dönüyorsun Projection Sadece gereken sütunlar
Entity üzerinde güncelleme yapacaksın Include Change tracking gerekli
Rapor / Dashboard Projection Aggregate + minimal veri
Admin panel (CRUD) Include Entity'yi komple ihtiyacın var
Performans kritik (liste sorguları) Projection Network + bellek tasarrufu