EFEF Core Handbook

İLERİ

Owned Entities (Sahipli Varlıklar)

Bağımsız kimliği olmayan, her zaman sahibiyle birlikte var olan alt nesneleri aynı tabloda saklar. Adres, koordinat, para birimi gibi yapılar için — ayrı tablo + FK gerektirmez.

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

Gerçek Senaryo: E-ticaret müşterisinin fatura ve teslimat adresi

Müşterinin iki adresi var. Adresin kendi kimliği yok, müşteri silinince adres de gitmeli, ayrı tabloya gerek yok.

Tablo Yapısı:

Customers int IdPK nvarchar FullName nvarchar Email Owned: ShippingAddress nvarchar Ship_Street nvarchar Ship_City nvarchar Ship_PostalCode nvarchar Ship_Country Owned: BillingAddress nvarchar Bill_Street nvarchar Bill_City nvarchar Bill_PostalCode nvarchar Bill_Country
// --- Entity'ler ---

public class Address
{
    public string Street     { get; set; }
    public string City       { get; set; }
    public string PostalCode { get; set; }
    public string Country    { get; set; }
}

public class Customer
{
    public int     Id              { get; set; }
    public string  FullName        { get; set; }
    public string  Email           { get; set; }
    public Address ShippingAddress { get; set; }   // teslimat adresi
    public Address BillingAddress  { get; set; }   // fatura adresi
}

// --- Config ---

public class CustomerConfiguration : IEntityTypeConfiguration<Customer>
{
    public void Configure(EntityTypeBuilder<Customer> builder)
    {
        builder.ToTable("Customers");
        builder.HasKey(c => c.Id);
        builder.Property(c => c.FullName).IsRequired().HasMaxLength(150);
        builder.Property(c => c.Email)   .IsRequired().HasMaxLength(200);

        builder.OwnsOne(c => c.ShippingAddress, addr =>
        {
            addr.Property(a => a.Street)    .HasColumnName("Ship_Street")    .HasMaxLength(200);
            addr.Property(a => a.City)      .HasColumnName("Ship_City")      .HasMaxLength(100);
            addr.Property(a => a.PostalCode).HasColumnName("Ship_PostalCode").HasMaxLength(10);
            addr.Property(a => a.Country)   .HasColumnName("Ship_Country")   .HasMaxLength(50);
        });

        builder.OwnsOne(c => c.BillingAddress, addr =>
        {
            addr.Property(a => a.Street)    .HasColumnName("Bill_Street")    .HasMaxLength(200);
            addr.Property(a => a.City)      .HasColumnName("Bill_City")      .HasMaxLength(100);
            addr.Property(a => a.PostalCode).HasColumnName("Bill_PostalCode").HasMaxLength(10);
            addr.Property(a => a.Country)   .HasColumnName("Bill_Country")   .HasMaxLength(50);
        });
    }
}

Oluşan SQL:

CREATE TABLE [Customers] (
    [Id]              INT           IDENTITY(1,1) NOT NULL,
    [FullName]        NVARCHAR(150) NOT NULL,
    [Email]           NVARCHAR(200) NOT NULL,
    -- ShippingAddress (Owned — aynı tabloda)
    [Ship_Street]     NVARCHAR(200) NULL,
    [Ship_City]       NVARCHAR(100) NULL,
    [Ship_PostalCode] NVARCHAR(10)  NULL,
    [Ship_Country]    NVARCHAR(50)  NULL,
    -- BillingAddress (Owned — aynı tabloda)
    [Bill_Street]     NVARCHAR(200) NULL,
    [Bill_City]       NVARCHAR(100) NULL,
    [Bill_PostalCode] NVARCHAR(10)  NULL,
    [Bill_Country]    NVARCHAR(50)  NULL,
    CONSTRAINT [PK_Customers] PRIMARY KEY CLUSTERED ([Id])
);
CREATE TABLE customers (
    id              INT           GENERATED BY DEFAULT AS IDENTITY,
    full_name       VARCHAR(150)  NOT NULL,
    email           VARCHAR(200)  NOT NULL,
    -- shipping_address (Owned — aynı tabloda)
    ship_street     VARCHAR(200)  NULL,
    ship_city       VARCHAR(100)  NULL,
    ship_postal_code VARCHAR(10)  NULL,
    ship_country    VARCHAR(50)   NULL,
    -- billing_address (Owned — aynı tabloda)
    bill_street     VARCHAR(200)  NULL,
    bill_city       VARCHAR(100)  NULL,
    bill_postal_code VARCHAR(10)  NULL,
    bill_country    VARCHAR(50)   NULL,
    CONSTRAINT pk_customers PRIMARY KEY (id)
);

Örnek veri:

Id FullName Email Ship_Street Ship_City Ship_PostalCode Ship_Country Bill_Street Bill_City Bill_PostalCode Bill_Country
1 Ahmet Yılmaz ahmet@mail.com Bağdat Cad. No:42 İstanbul 34710 Türkiye Atatürk Blv. No:1 Ankara 06100 Türkiye
2 Elif Demir elif@mail.com Kordon Boyu 15 İzmir 35220 Türkiye Kordon Boyu 15 İzmir 35220 Türkiye

OwnsMany — Sipariş kalemleri

Bir siparişin satır kalemleri (line items) vardır. Kalem, sipariş dışında anlam taşımaz.

ER Diyagramı:

Orders int IdPK nvarchar Reference OrderLineItems int IdPK int OrderIdFK nvarchar ProductName int Quantity decimal UnitPrice 1 * owns
public class OrderLineItem
{
    public string ProductName { get; set; }
    public int    Quantity    { get; set; }
    public decimal UnitPrice  { get; set; }
}

public class Order
{
    public int    Id        { get; set; }
    public string Reference { get; set; }
    public ICollection<OrderLineItem> LineItems { get; set; }
}

public class OrderConfiguration : IEntityTypeConfiguration<Order>
{
    public void Configure(EntityTypeBuilder<Order> builder)
    {
        builder.ToTable("Orders");
        builder.HasKey(o => o.Id);

        builder.OwnsMany(o => o.LineItems, item =>
        {
            item.ToTable("OrderLineItems");          // OwnsMany ayrı tablo zorunlu
            item.WithOwner().HasForeignKey("OrderId");
            item.Property(i => i.ProductName).IsRequired().HasMaxLength(200);
            item.Property(i => i.Quantity)   .IsRequired();
            item.Property(i => i.UnitPrice)  .HasPrecision(18, 2);
        });
    }
}

Oluşan SQL:

CREATE TABLE [Orders] (
    [Id]        INT          IDENTITY(1,1) NOT NULL,
    [Reference] NVARCHAR(MAX) NULL,
    CONSTRAINT [PK_Orders] PRIMARY KEY CLUSTERED ([Id])
);

CREATE TABLE [OrderLineItems] (
    [Id]          INT           IDENTITY(1,1) NOT NULL,
    [OrderId]     INT           NOT NULL,
    [ProductName] NVARCHAR(200) NOT NULL,
    [Quantity]    INT           NOT NULL,
    [UnitPrice]   DECIMAL(18,2) NOT NULL,
    CONSTRAINT [PK_OrderLineItems] PRIMARY KEY CLUSTERED ([Id]),
    CONSTRAINT [FK_OrderLineItems_Orders] FOREIGN KEY ([OrderId]) REFERENCES [Orders]([Id]) ON DELETE CASCADE
);
CREATE TABLE orders (
    id        INT          GENERATED BY DEFAULT AS IDENTITY,
    reference TEXT         NULL,
    CONSTRAINT pk_orders PRIMARY KEY (id)
);

CREATE TABLE order_line_items (
    id           INT           GENERATED BY DEFAULT AS IDENTITY,
    order_id     INT           NOT NULL,
    product_name VARCHAR(200)  NOT NULL,
    quantity     INT           NOT NULL,
    unit_price   NUMERIC(18,2) NOT NULL,
    CONSTRAINT pk_order_line_items PRIMARY KEY (id),
    CONSTRAINT fk_order_line_items_orders FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE
);

Örnek veri:

Orders:

Id Reference
1 ORD-2025-001
2 ORD-2025-002

OrderLineItems:

Id OrderId ProductName Quantity UnitPrice
1 1 MacBook Pro 16" 1 84999.99
2 1 USB-C Hub 2 1299.00
3 1 Magic Mouse 1 3499.00
4 2 iPhone 15 Pro 1 54999.00

JSON'a map etme (EF Core 7+)

Kalemler ayrı tablo yerine JSON sütununda saklanabilir.

builder.OwnsMany(o => o.LineItems, item =>
{
    item.ToJson();   // ayrı tablo yerine Orders.LineItems sütununa JSON yazar
});

JSON formatında veri (Orders tablosu):

Id Reference LineItems
1 ORD-2025-001 [{"ProductName":"MacBook Pro","Quantity":1,"UnitPrice":84999.99},{"ProductName":"USB-C Hub","Quantity":2,"UnitPrice":1299.00}]

Ne zaman Owned Entity, ne zaman ayrı tablo?

Durum Karar
Nesne her zaman sahibiyle birlikte sorgulanıyor Owned Entity
Nesnenin geçmişi tutulacak (eski siparişin adresi değişmemeli) Ayrı tablo
Aynı nesneyi birden fazla entity paylaşabilir Ayrı tablo
Nesne tek başına listelenmeyecek Owned Entity