Karmaşık Türler

Veritabanına kaydedilen nesneler üç geniş kategoriye ayrılabilir:

  • Yapılandırılmamış ve tek bir değer tutan nesneler. Örneğin, int, Guid, string, IPAddress. Bunlar (biraz gevşek) ilkel türler olarak adlandırılır.
  • Birden çok değeri barındıracak şekilde yapılandırılmış nesneler ve nesnenin kimliğinin bir anahtar değeri tarafından tanımlandığı yer. Örneğin, Blog, Post, Customer. Bunlara varlık türleri adı verilir.
  • Birden çok değeri barındıracak şekilde yapılandırılmış nesneler, ancak nesnenin kimliğini tanımlayan bir anahtarı yoktur. Örneğin, Address, Coordinate, Money. Bunlara değer nesneleri denir ve EF Core bunları karmaşık türler olarak eşler.

Karmaşık bir tür, çeşitli özellikleri bir varlık türü içinde yer alan tek bir .NET türünde gruplandırmaktadır; kendi kimliği yoktur ve bağımsız olarak izlenemez veya sorgulanamaz. Bu, karmaşık türleri değer nesnelerini modellemenin doğal yolu yapar.

Tip

GitHub'da bu makalenin tam örnek projesinde komutunu çalıştırabilir ve hata ayıklayabilirsiniz.

Uyarı

Karmaşık türler EF Core 8'de kullanıma sunulmuştur ve sonraki sürümlerde önemli ölçüde genişletilmiştir. Özelliklere, bunları tanıtan sürümle aşağıda ek açıklama eklenmiştir.

Karmaşık türler ile sahip olunan varlık türleri karşılaştırması

Karmaşık türler mevcut olmadan önce , sahip olunan varlık türleri nesneleri anahtar özellikleri olmadan modellemek için önerilen yoldu. Ancak, sahip olunan türler hala arka planda varlık türleridir : gizli bir anahtara ve kimliğe sahiptirler ve bu nedenle başvuru semantiğiyle çalışırlar. Bu, karmaşık türlerin çözmek için tasarlandığı bir dizi sürtünme noktasına neden olur.

Önemli farklar şunlardır:

Aspect Sahip olunan varlık türleri Karmaşık türler
Identity Gizli bir anahtara ve kimliğe sahip olmak Kimlik yok; değere göre karşılaştır
Örnek paylaşımı Aynı örneğe iki kez başvurulamıyor Aynı örnek birden çok özelliğe atanabilir
Atama semantiği Başvuru semantiği Değer semantiği (özellikler kopyalanır)
.NET türü Yalnızca başvuru türleri Başvuru veya değer türleri
Tablo eşlemesi Kendi tablo, tablo bölme veya JSON Kapsayıcının tablosu (tablo bölme) veya JSON
Navigations Diğer varlıklara yönelik gezintiler içerebilir Gezinti içeremez
Toplu güncelleştirme (ExecuteUpdate) Desteklenmiyor Destekleniyor

Örneğin, aynı varlık örneğine birden çok kez başvurulamadığından müşterinin fatura adresini sevkiyat adresiyle aynı olacak şekilde atama işlemi sahip olunan varlık türleriyle başarısız olur:

var customer = await context.Customers.SingleAsync(c => c.Id == someId);
customer.BillingAddress = customer.ShippingAddress;
await context.SaveChangesAsync(); // Throws with owned entity types

Karmaşık türlerin değer semantiği olduğundan, aynı atama yalnızca özellikleri üzerine kopyalar ve beklendiği gibi çalışır. Benzer şekilde, linq sorgusundaki iki karmaşık değerin karşılaştırılması içeriklerini karşılaştırırken, sahip olunan iki varlığın karşılaştırması kimliklerini karşılaştırır.

Bu nedenlerden dolayı karmaşık türler genellikle tablo bölme veya JSON eşlemesi ile değer nesnelerini modellemek için daha iyi bir seçimdir. Şu anda bu senaryolar için sahip olunan varlık türlerini kullanan kullanıcıların karmaşık türlere geçmeyi düşünmesi teşvik edilir.

Basit bir örnek

Birkaç ilgili değerin bulunduğu ancak kendi kimliği olmayan bir Address türü düşünün:

public record Address
{
    public required string Line1 { get; init; }
    public string? Line2 { get; init; }
    public required string City { get; init; }
    public required string Country { get; init; }
    public required string PostCode { get; init; }
}

Address daha sonra müşteri/sipariş modeli genelinde çeşitli yerlerde kullanılabilir:

public class Customer
{
    public int Id { get; set; }
    public required string Name { get; set; }

    // A required (non-nullable) complex property.
    public required Address Address { get; set; }

    // An optional (nullable) complex property.
    public Address? SecondaryAddress { get; set; }

    public List<Order> Orders { get; } = new();
}

public class Order
{
    public int Id { get; set; }
    public required string Contents { get; set; }
    public required Address ShippingAddress { get; set; }
    public required Address BillingAddress { get; set; }
    public Customer Customer { get; set; } = null!;
}

Müşteri oluşturma ve kaydetme her zamanki gibi çalışır:

var customer = new Customer
{
    Name = "Willow",
    Address = new Address
    {
        Line1 = "Barking Gate",
        City = "Walpole St Peter",
        Country = "UK",
        PostCode = "PE14 7AV"
    }
};

context.Add(customer);
await context.SaveChangesAsync();

İlişkisel veritabanında karmaşık tür kendi tablosunu almaz. Bunun yerine özellikleri, içeren varlığın tablosunda ek sütunlar olarak satır içinde kaydedilir (bu , tablo bölme olarak bilinir):

INSERT INTO [Customers] ([Name], [Address_City], [Address_Country], [Address_Line1], [Address_Line2], [Address_PostCode])
OUTPUT INSERTED.[Id]
VALUES (@p0, @p1, @p2, @p3, @p4, @p5);

Karmaşık türlerin değer semantiği olduğundan, aynı Address örnek herhangi bir sorun olmadan birden çok özellik arasında paylaşılabilir:

// The same Address instance can be assigned to multiple complex properties.
customer.Orders.Add(
    new Order
    {
        Contents = "Tesco Tasty Treats",
        BillingAddress = customer.Address,
        ShippingAddress = customer.Address
    });

await context.SaveChangesAsync();

Karmaşık türleri yapılandırma

Çoğu varlık türünden farklı olarak, karmaşık türler kural tarafından bulunmaz. Bunları, türüne ile ComplexTypeAttributeaçıklama ekleyerek veya karmaşık bir tür olarak eşlenmesi gereken her özellik için fluent API'sini OnModelCreating çağırarak ComplexProperty açıkça yapılandırmanız gerekir:

[ComplexType]
public record Address
{
    public required string Line1 { get; init; }
    public string? Line2 { get; init; }
    public required string City { get; init; }
    public required string Country { get; init; }
    public required string PostCode { get; init; }
}

Karmaşık tür özelliklerinin modellerini yapılandırma

İç içe Property oluşturucu, bir varlık türünün özellikleri gibi karmaşık bir türün skaler özelliklerini yapılandırmak için kullanılabilir ; örneğin, sütun adını veya uzunluk üst sınırını ayarlamak için:

modelBuilder.Entity<Order>()
    .ComplexProperty(
        o => o.ShippingAddress,
        b =>
        {
            b.Property(a => a.Line1).HasColumnName("ShipsToStreet").HasMaxLength(100);
            b.Property(a => a.City).HasColumnName("ShipsToCity");
        });

EF Core 11'den başlayarak, önce karmaşık tür oluşturucusunu almadan lambdada üye erişimini zincirleyerek karmaşık bir türün içinde iç içe yerleştirilmiş bir özelliği yapılandırabilirsiniz:

// EF Core 11 allows configuring a complex-type property directly by chaining
// member access, without first obtaining the complex-type builder.
modelBuilder.Entity<Customer>()
    .Property(c => c.Address.Line1)
    .HasMaxLength(200);

Başvuru ve değer türleri

Karmaşık tür, .NET başvuru türü (a class veya record) veya değer türü (EF Core 10'da tanıtılan veya structrecord struct) olabilir.

Mutability

Başvuru türündeki bir örnek birden çok özellik tarafından paylaşılabildiğinden, özelliklerinden biri değiştirildiğinde, değeri kullanıldığı her yerde değiştirir. Bu genellikle istediğiniz şey değildir. Bunu önlemenin iyi bir yolu ( ve değer nesneleri için doğal bir uyum), karmaşık türü sabit hale getirmektir, böylece bir değeri değiştirmek için yeni bir örnek oluşturulması gerekir. Address Bu makalede kullanılan tür sabittirrecord; bu nedenle adresi değiştirmek bir with ifadeyle gerçekleştirilir:

// Address is an immutable record, so create a new instance to change a value.
customer.Address = customer.Address with { Line1 = "Peacock Lodge" };

await context.SaveChangesAsync();

Tamamen yeni Address bir örnek atanmış olsa da EF, değişiklikleri tek tek özellik düzeyinde izler, bu nedenle yalnızca değerleri gerçekten değiştirilen sütunlar güncelleştirilir:

UPDATE [Customers] SET [Address_Line1] = @p0
OUTPUT 1
WHERE [Id] = @p1;

Değişmezlik, sabit (yalnızca başlatma veya salt okunur özellikler), bir record, veya readonly structile readonly record structifade class edilebilir. Değer türleri (struct) kopyalama semantiğine sahiptir, bu nedenle bunların atanması her zaman değerleri kopyalar ve değiştirilebilir olsa bile yanlışlıkla paylaşma sorununu önler, ancak değiştirilebilir yapılar C# dilinde genellikle önerilmez, bu nedenle sabit bir form hala önerilir.

Tip

Birkaç varlık gerçekten aynı adresi gözlemlemesi ve değiştiğinde birlikte güncelleştirilmesi gerekiyorsa, adresi kendi kimliğine sahip bir varlık türü olarak modelleyin ve karmaşık bir tür kullanmak yerine gezinti yoluyla başvuruda bulunın.

İç içe karmaşık türler

Karmaşık bir tür, yapılandırılmış nesneleri herhangi bir derinliğe kadar oluşturmanıza olanak sağlayan diğer karmaşık türlerin özelliklerini içerebilir. Örneğin, karmaşık bir Contact tür hem bir hem de bir Address veya daha fazla PhoneNumber karmaşık tür içerebilir:

public record Address(string Line1, string? Line2, string City, string Country, string PostCode);

public record PhoneNumber(int CountryCode, long Number);

public record Contact
{
    public required Address Address { get; init; }
    public required PhoneNumber HomePhone { get; init; }
    public required PhoneNumber WorkPhone { get; init; }
}

Tablo bölme yoluyla eşlendiğinde, iç içe yerleştirilmiş karmaşık türün sütunlarına özelliğin tam yolu ön eklenir (örneğin, Contact_HomePhone_Number).

İsteğe bağlı karmaşık türler

Varsayılan olarak, karmaşık bir özellik gereklidir: CLR özelliğinin her zaman bir değeri olmalıdır ve boş değer atanamayan sütunlara eşlenmelidir. EF Core 10'dan başlayarak, karmaşık bir özellik null atanabilir olarak bildirilerek isteğe bağlı hale getirilebilir:

public class Customer
{
    public int Id { get; set; }
    public required string Name { get; set; }

    // A required (non-nullable) complex property.
    public required Address Address { get; set; }

    // An optional (nullable) complex property.
    public Address? SecondaryAddress { get; set; }

    public List<Order> Orders { get; } = new();
}

public class Order
{
    public int Id { get; set; }
    public required string Contents { get; set; }
    public required Address ShippingAddress { get; set; }
    public required Address BillingAddress { get; set; }
    public Customer Customer { get; set; } = null!;
}

Tüm sütunlarında NULL değerlere neden olan null isteğe bağlı bir karmaşık özellik.

Uyarı

İsteğe bağlı karmaşık tür şu anda karmaşık tür üzerinde en az bir gerekli özelliğin tanımlanmasını gerektirir. Bunun nedeni, EF'nin karmaşık bir değeri, özellikleri olan karmaşık bir null değerden ayırt etmek için en az bir null atanamaz sütuna ihtiyacı olmasıdır null.

Karmaşık türün kendi gerekli özelliği yoksa, bunun yerine bir ayrıştırıcı özelliği yapılandırabilirsiniz. EF Core henüz karmaşık türler için devralmayı desteklemese de, ayırıcı varsayılan olarak gerekli bir gölge özelliği olarak oluşturulur ve bu da yukarıdaki gereksinimi karşılar:

// GeoLocation has no required property, so configure a discriminator. EF creates
// it as a required shadow property, which satisfies the requirement that an
// optional complex type have at least one required property.
modelBuilder.Entity<Place>()
    .ComplexProperty(p => p.Location, b => b.HasDiscriminator());

Karmaşık tür koleksiyonları

EF Core 10'dan başlayarak, bir özellik karmaşık türlerden oluşan bir koleksiyonu barındırabilir. İlişkisel veritabanlarında karmaşık koleksiyonlar kullanılarak tek bir JSON sütununa ToJson eşlenmelidir; bunlar farklı bir tabloya eşlenemez:

public class Distributor
{
    public int Id { get; set; }
    public required string Name { get; set; }

    // A collection of complex types, mapped to a single JSON column.
    public List<Address> ShippingCenters { get; set; } = new();
}
// Collections of complex types must be mapped to JSON on relational providers.
modelBuilder.Entity<Distributor>()
    .ComplexCollection(d => d.ShippingCenters, b => b.ToJson());

Koleksiyonun her öğesi dizinin içinde bir JSON nesnesi olarak depolanır ve koleksiyonun tamamı bir sütuna eşler:

CREATE TABLE [Distributors] (
    [Id] int NOT NULL IDENTITY,
    [Name] nvarchar(max) NOT NULL,
    [ShippingCenters] json NOT NULL,
    CONSTRAINT [PK_Distributors] PRIMARY KEY ([Id])
);

Uyarı

Değer türü koleksiyonları (struct) şu anda desteklenmiyor; karmaşık koleksiyon öğeleri için bir başvuru türü (class veya record) kullanın.

Karmaşık türleri JSON'a eşleme

EF Core 10, tablo bölmeye ek olarak bir (koleksiyon olmayan) karmaşık özelliği ile ToJsontek bir JSON sütununa eşlemeye olanak tanır:

modelBuilder.Entity<Customer>(b =>
{
    b.ComplexProperty(c => c.Address, c => c.ToJson());
    b.ComplexProperty(c => c.SecondaryAddress, c => c.ToJson());
});

Daha sonra her karmaşık değer birden çok sütuna yayılmak yerine tek bir JSON sütunu halinde serileştirilir:

CREATE TABLE [Customers] (
    [Id] int NOT NULL IDENTITY,
    [Name] nvarchar(max) NOT NULL,
    [Address] json NOT NULL,
    [SecondaryAddress] json NULL,
    CONSTRAINT [PK_Customers] PRIMARY KEY ([Id])
);

SQL Server 2025 ve Azure SQL'da EF varsayılan olarak yerel json veri türünü kullanır; diğer veritabanlarında ve eski SQL Server sürümlerinde JSON bir metin sütununda depolanır. Gerekirse ile sütun türünü HasColumnType geçersiz kılabilirsiniz.

Tablo bölmeden farklı olarak, JSON eşlemesi eşlenen türdeki koleksiyonlara olanak tanır ve belgenin içindeki özellikleri diğer tüm özellikler gibi sorgulamanıza ve güncelleştirmenize olanak tanır. JSON sütunlarının içindeki değerler de ile ExecuteUpdateAsynctoplu olarak verimli bir şekilde güncelleştirilebilir.

Karmaşık tür özelliklerindeki anahtarlar ve dizinler

EF Core 11'den başlayarak, anahtarlar ve dizinler koleksiyon dışı karmaşık türlerin içinde iç içe yerleştirilmiş skaler özellikleri hedefleyebilir. Bu bir lambda ile yapılabilir:

// EF Core 11 allows keys and indexes to target scalar properties nested
// inside non-collection complex types.
modelBuilder.Entity<Customer>()
    .HasIndex(c => c.Address.PostCode);

Aynı yollar, karmaşık bir özelliğe gitmek için kullanılarak . ada göre yapılandırılabilir:

modelBuilder.Entity<Customer>()
    .HasIndex("Address.PostCode");

İlişkisel sağlayıcılar için dizinler, JSON sütunlarına eşlenmiş karmaşık türlerin içindeki yolları da hedefleyebilir. Karmaşık koleksiyon yolları, tüm öğelere başvurmak için veya belirli bir öğenin sayısal dizin oluşturucusuna başvurmak için kullanılır [] :

// Index a scalar inside every element of a JSON-mapped complex collection.
// Requires a provider/database with JSON index support, such as SQL Server 2025.
modelBuilder.Entity<Distributor>()
    .HasIndex("ShippingCenters[].City");

Uyarı

JSON eşlemeli bir karmaşık koleksiyonda dizin oluşturmak için SQL Server 2025 gibi JSON dizinlerini destekleyen bir veritabanı gerekir.

Daha fazla bilgi için bkz . Anahtarlar , Dizinler ve kısıtlamalar.

Varlık devralma ile karmaşık türler

EF Core 11'den başlayarak, karmaşık türler ve JSON sütunları TPT (tür başına tablo) veya TPC (beton türü başına tablo) kullanan varlık türlerinde kullanılabilir. Bu, bu devralma stratejilerinin esnekliğini karmaşık türlerin modelleme gücüyle birleştirmenizi sağlar.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Animal>()
        .UseTptMappingStrategy()
        .ComplexProperty(a => a.Details);
}

Değişiklik izleme

EF Core, karmaşık bir türün tek tek özelliklerindeki değişiklikleri izler, dolayısıyla çağrısı SaveChangesyaptığınızda yalnızca etkilenen sütunlar güncelleştirilir. Değişiklik izleyicisi aracılığıyla bu izleme durumunu inceleyebilir ve işleyebilirsiniz.

Karmaşık bir özelliğe ulaşmak için kullanın EntityEntry.ComplexProperty ve ardından skaler özelliklerinde detaya gidin:

var addressEntry = context.Entry(customer).ComplexProperty(c => c.Address);
Console.WriteLine($"City is currently: {addressEntry.Property(a => a.City).CurrentValue}");
Console.WriteLine($"Address was modified: {addressEntry.Property(a => a.City).IsModified}");

API varlık API'sini ComplexPropertyEntry yansıtır: okuyup ayarlayabilirCurrentValue, denetleyip ayarlayabilir IsModifiedve daha fazla iç içe yerleştirilmiş karmaşık özelliklere veya karmaşık koleksiyonlara gidebilirsiniz.EntityEntry Karmaşık özellikler, varlığın özellik değerleri API'leri (CurrentValues/OriginalValues) aracılığıyla da sunulur.

Karmaşık türleri sorgulama

Karmaşık tür üyeleri, VARLıĞın özellikleri gibi LINQ sorgularında da kullanılabilir; bunlara filtreleyebilir, bunları yansıtabilir ve sıralayabilirsiniz:

// Filter and project members of a complex property.
var ukCities = await context.Customers
    .Where(c => c.Address.Country == "UK")
    .Select(c => c.Address.City)
    .ToListAsync();

Karmaşık türlerin değer semantiği olduğundan, sorgudaki karmaşık değerin tamamını karşılaştırabilirsiniz ve EF tüm özelliklerini karşılaştırır:

var ordersToHomeAddress = await context.Orders
    .Where(o => o.ShippingAddress == o.BillingAddress)
    .ToListAsync();

JSON ile eşlenen karmaşık türler için EF Core 11, belgede belirli bir JSON yolunun bulunup olmadığını denetleyen öğesini ekler EF.Functions.JsonPathExists:

var withPostCode = await context.Customers
    .Where(c => EF.Functions.JsonPathExists(c.Address, "$.PostCode"))
    .ToListAsync();

Limitations

Karmaşık türler değer nesnelerini modellemek için tasarlanmıştır ve varlık türlerinin her özelliğini kasıtlı olarak desteklemez. Başlıca sınırlamalar şunlardır:

  • Kendi kimlikleri veya takipleri yok. Karmaşık bir tür yalnızca bir varlığın parçası olarak bulunabilir; karmaşık bir DbSet<T> türe sahip olamazsınız veya bağımsız olarak izleyemez veya sorgulayamazsınız.
  • Gezinti yok. Karmaşık bir tür, varlık türlerine gezinti özellikleri içeremez.
  • Ayrı tablo yok. İlişkisel veritabanlarında karmaşık bir tür her zaman kapsayıcının tablosunda (tablo bölme yoluyla) veya bir JSON sütununda depolanır; hiçbir zaman kendi tablosunda depolanmaz.
  • Koleksiyonlar JSON gerektirir. İlişkisel sağlayıcılarda karmaşık koleksiyonlar ile JSON ile ToJsoneşlenmelidir; tablo bölme yoluyla eşlenemezler.
  • Değer türü koleksiyonları desteklenmez. Karmaşık koleksiyon öğeleri başvuru türleri olmalıdır.
  • İsteğe bağlı karmaşık türler için gerekli bir özellik gerekir. İsteğe bağlı (null atanabilir) karmaşık tür en az bir gerekli özellik tanımlamalıdır.

Karmaşık tür desteği sürümler arasında genişletilmeye devam eder; en son eklemeler için yeni sayfalara bakın.