EFEF Core Handbook

İLERİ

Shadow Properties

C# entity class'ında tanımlanmayan ama veritabanında var olan sütunlar. Entity'yi "kirletmeden" audit alanları (CreatedAt, ModifiedBy), FK'lar veya altyapısal sütunlar eklemek için kullanılır.

Veritabanı sağlayıcısı Bu sayfadaki eşleşen örnekleri seçilen sağlayıcıya göre gösterir.
C# Class int Id string Name decimal Price CreatedAt yok! CreatedBy yok! EF Model ekler EF Core Model int Id string Name decimal Price DateTime CreatedAt shadow string CreatedBy shadow DateTime? UpdatedAt shadow Migration → SQL DB Table IdINT PK NameNVARCHAR PriceDECIMAL CreatedAtDATETIME2 CreatedByNVARCHAR UpdatedAtDATETIME2 C# class temiz kalır — shadow property'ler sadece EF & DB'de yaşar
// Shadow property tanımlama
builder.Property<DateTime>("CreatedAt");
builder.Property<string>("CreatedBy").HasMaxLength(100);

// Shadow property'ye erişim
var createdAt = context.Entry(entity).Property<DateTime>("CreatedAt").CurrentValue;

// Shadow FK (navigation property olmaksızın)
builder.HasOne<User>()
       .WithMany()
       .HasForeignKey("UserId");
// ☝️ Entity class'ında UserId property'si yok — EF bunu shadow olarak yönetir.
//    DB'de FK sütunu oluşur, JOIN/Include çalışır — ama C# class temiz kalır.

// SaveChanges override ile otomatik set
public override Task<int> SaveChangesAsync(CancellationToken ct = default)
{
    foreach (var entry in ChangeTracker.Entries<BaseEntity>())
    {
        if (entry.State == EntityState.Added)
            entry.Property("CreatedAt").CurrentValue = DateTime.UtcNow;
        if (entry.State is EntityState.Added or EntityState.Modified)
            entry.Property("UpdatedAt").CurrentValue = DateTime.UtcNow;
    }
    return base.SaveChangesAsync(ct);
}

SQL'de shadow property'ler normal sütun olarak görünür:

CREATE TABLE [Products] (
    [Id]        INT           IDENTITY(1,1) NOT NULL,
    [Name]      NVARCHAR(200) NOT NULL,
    [CreatedAt] DATETIME2     NOT NULL,    -- Shadow property → DB'de normal sütun
    [CreatedBy] NVARCHAR(100) NULL,        -- Entity class'ında yok ama DB'de var!
    [UpdatedAt] DATETIME2     NULL,
    CONSTRAINT [PK_Products] PRIMARY KEY CLUSTERED ([Id])
);
CREATE TABLE products (
    id         INTEGER GENERATED ALWAYS AS IDENTITY,
    name       VARCHAR(200) NOT NULL,
    created_at TIMESTAMPTZ  NOT NULL,    -- Shadow property → DB'de normal sütun
    created_by VARCHAR(100) NULL,        -- Entity class'ında yok ama DB'de var!
    updated_at TIMESTAMPTZ  NULL,
    CONSTRAINT pk_products PRIMARY KEY (id)
);

Örnek veri:

Id Name CreatedAt CreatedBy UpdatedAt
1 Laptop 2025-01-15 08:30:00 admin@firma.com 2025-02-01 10:00:00
2 iPhone 2025-01-16 10:00:00 ahmet@firma.com NULL

Product class'ında CreatedAt, CreatedBy yok — ama DB'de var ve EF otomatik dolduruyor!

Shadow Property ile Sorgulama

// EF.Property<T>() ile LINQ'da shadow property kullanılabilir
var recentProducts = await context.Products
    .OrderByDescending(p => EF.Property<DateTime>(p, "CreatedAt"))
    .Take(10)
    .ToListAsync();

// Filtreleme
var todayAdded = await context.Products
    .Where(p => EF.Property<DateTime>(p, "CreatedAt").Date == DateTime.Today)
    .ToListAsync();

Oluşan SQL:

SELECT TOP(10) [p].[Id], [p].[Name], [p].[Price]
FROM [Products] AS [p]
ORDER BY [p].[CreatedAt] DESC;
SELECT p.id, p.name, p.price
FROM products AS p
ORDER BY p.created_at DESC
LIMIT 10;

Shadow Property'ye Index Ekleme

// Shadow property üzerine index — sorgulama performansı için
builder.HasIndex("CreatedAt");
builder.HasIndex("TenantId", "CreatedAt");  // Composite

Ne Zaman Shadow Property, Ne Zaman Normal Property?

Kriter Shadow Property Normal Property
Domain logic'te kullanılıyor mu? Hayır → Shadow Evet → Normal
API response'ta dönülecek mi? Hayır → Shadow Evet → Normal
Sadece altyapısal (audit, FK) Shadow
Entity class'ı temiz kalsın mı? Shadow