Değer nesneleri uygulama

İpucu

Bu içerik, .NET Docs'ta veya çevrimdışı olarak okunabilen ücretsiz indirilebilir bir PDF olarak sağlanan Kapsayıcılı .NET Uygulamaları için .NET Mikro Hizmet Mimarisi e-Kitabı'ndan bir alıntıdır.

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

Varlıklar ve toplamalar hakkında önceki bölümlerde açıklandığı gibi, kimlik varlıklar için temel öneme sahiptir. Ancak, bir sistemde değer nesneleri gibi kimlik ve kimlik izleme gerektirmeyen birçok nesne ve veri öğesi vardır.

Değer nesnesi diğer varlıklara başvurabilir. Örneğin, bir noktadan diğerine nasıl ulaşacağını açıklayan bir yol oluşturan bir uygulamada, bu yol bir değer nesnesi olabilir. Belirli bir rotadaki noktaların anlık görüntüsü olabilir, ancak şirket içinde City, Road gibi varlıklara başvuruda bulunsa bile bu önerilen yolun kimliği olmaz.

Şekil 7-13,Sipariş toplaması içindeki Adres değeri nesnesini gösterir.

Diagram showing the Address value-object inside the Order Aggregate.

Şekil 7-13. Order toplaması içindeki adres değeri nesnesi

Şekil 7-13'te gösterildiği gibi, bir varlık genellikle birden çok özniteliklerden oluşur. Örneğin, Order varlık bir kimliğe sahip bir varlık olarak modellenebilir ve OrderId, OrderDate, OrderItems gibi bir öznitelik kümesinden oluşturulmuş olabilir. Ancak yalnızca ülke/bölge, sokak, şehir vb.'lerden oluşan karmaşık bir değer olan ve bu etki alanında kimliği olmayan adres modellenmeli ve değer nesnesi olarak kabul edilmelidir.

Değer nesnelerinin önemli özellikleri

Değer nesneleri için iki ana özellik vardır:

  • Kimlikleri yok.

  • Bunlar sabit.

İlk özellik zaten tartışıldı. Değişmezlik önemli bir gereksinimdir. Nesne oluşturulduktan sonra değer nesnesinin değerleri sabit olmalıdır. Bu nedenle, nesne oluşturulduğunda gerekli değerleri sağlamanız gerekir, ancak nesnenin ömrü boyunca bunların değişmesine izin vermemelisiniz.

Değer nesneleri, sabit doğası sayesinde performans için bazı püf noktaları gerçekleştirmenize olanak sağlar. Bu durum özellikle çoğu aynı değerlere sahip olan binlerce değer nesnesi örneğinin bulunduğu sistemlerde geçerlidir. Değişmez doğaları, yeniden kullanılmasını sağlar; değerleri aynı olduğundan ve kimlikleri olmadığından, bunlar değiştirilebilir nesneler olabilir. Bu iyileştirme türü bazen yavaş çalışan yazılımlarla iyi performansa sahip yazılımlar arasında fark oluşturabilir. Elbette tüm bu durumlar uygulama ortamına ve dağıtım bağlamlarına bağlıdır.

C'de değer nesnesi uygulaması#

Uygulama açısından, tüm öznitelikler (bir değer nesnesinin kimliğe dayalı olmaması gerektiğinden) ve diğer temel özellikler arasındaki karşılaştırmaya göre eşitlik gibi temel yardımcı program yöntemlerine sahip bir değer nesnesi temel sınıfınız olabilir. Aşağıdaki örnekte, eShopOnContainers'dan mikro hizmeti sıralamada kullanılan bir değer nesnesi temel sınıfı gösterilmektedir.

public abstract class ValueObject
{
    protected static bool EqualOperator(ValueObject left, ValueObject right)
    {
        if (ReferenceEquals(left, null) ^ ReferenceEquals(right, null))
        {
            return false;
        }
        return ReferenceEquals(left, right) || left.Equals(right);
    }

    protected static bool NotEqualOperator(ValueObject left, ValueObject right)
    {
        return !(EqualOperator(left, right));
    }

    protected abstract IEnumerable<object> GetEqualityComponents();

    public override bool Equals(object obj)
    {
        if (obj == null || obj.GetType() != GetType())
        {
            return false;
        }

        var other = (ValueObject)obj;

        return this.GetEqualityComponents().SequenceEqual(other.GetEqualityComponents());
    }

    public override int GetHashCode()
    {
        return GetEqualityComponents()
            .Select(x => x != null ? x.GetHashCode() : 0)
            .Aggregate((x, y) => x ^ y);
    }
    // Other utility methods
}

ValueObject bir abstract class türdür, ancak bu örnekte ve != işleçlerini == aşırı yüklemez. Bunu yapmayı seçebilir ve karşılaştırmaları geçersiz kılma için Equals temsilci yapabilirsiniz. Örneğin, türüne aşağıdaki işleç aşırı yüklemelerini ValueObject göz önünde bulundurun:

public static bool operator ==(ValueObject one, ValueObject two)
{
    return EqualOperator(one, two);
}

public static bool operator !=(ValueObject one, ValueObject two)
{
    return NotEqualOperator(one, two);
}

Aşağıdaki örnekte gösterilen değer nesnesinde olduğu gibi Address gerçek değer nesnenizi uygularken bu sınıfı kullanabilirsiniz:

public class Address : ValueObject
{
    public String Street { get; private set; }
    public String City { get; private set; }
    public String State { get; private set; }
    public String Country { get; private set; }
    public String ZipCode { get; private set; }

    public Address() { }

    public Address(string street, string city, string state, string country, string zipcode)
    {
        Street = street;
        City = city;
        State = state;
        Country = country;
        ZipCode = zipcode;
    }

    protected override IEnumerable<object> GetEqualityComponents()
    {
        // Using a yield return statement to return each element one at a time
        yield return Street;
        yield return City;
        yield return State;
        yield return Country;
        yield return ZipCode;
    }
}

bu değer nesnesi uygulamasının Address kimliği yoktur ve bu nedenle sınıf tanımında veya sınıf tanımında AddressValueObject kimlik alanı tanımlanmamıştır.

Entity Framework (EF) tarafından kullanılacak bir sınıfta kimlik alanı olmaması, KIMLIKsiz daha iyi değer nesnelerinin uygulanmasına büyük ölçüde yardımcı olan EF Core 2.0'a kadar mümkün değildi. Bu tam olarak bir sonraki bölümün açıklamasıdır.

Sabit olan değer nesnelerinin salt okunur olması (yani, yalnızca alma özelliklerine sahip olması) gerektiği tartışılabilir ve bu gerçekten doğrudur. Ancak, değer nesneleri genellikle ileti kuyruklarından geçmek için serileştirilir ve seri durumdan çıkarılır ve salt okunur olması seri durumdan çıkarıcının değerleri atamasını durdurur, bu nedenle bunları private setolarak bırakırsınız; bu da pratik olması için salt okunurdur.

Değer nesnesi karşılaştırma semantiği

Türün Address iki örneği aşağıdaki yöntemlerin tümü kullanılarak karşılaştırılabilir:

var one = new Address("1 Microsoft Way", "Redmond", "WA", "US", "98052");
var two = new Address("1 Microsoft Way", "Redmond", "WA", "US", "98052");

Console.WriteLine(EqualityComparer<Address>.Default.Equals(one, two)); // True
Console.WriteLine(object.Equals(one, two)); // True
Console.WriteLine(one.Equals(two)); // True
Console.WriteLine(one == two); // True

Tüm değerler aynı olduğunda, karşılaştırmalar doğru şekilde olarak truedeğerlendirilir. ve işleçlerini == aşırı yüklemeyi seçmediyseniz son karşılaştırması one == two olarak falsedeğerlendirilir.!= Daha fazla bilgi için bkz . Overload ValueObject equality operators.

EF Core 2.0 ve sonraki sürümleriyle veritabanındaki değer nesnelerini kalıcı hale getir

Az önce etki alanı modelinizde bir değer nesnesinin nasıl tanımlanacağına bakın. Ancak genellikle kimliği olan varlıkları hedeflediğinden Entity Framework Core kullanarak veritabanında nasıl kalıcı hale getirebilirsiniz?

EF Core 1.1 kullanan arka plan ve eski yaklaşımlar

Arka plan olarak, EF Core 1.0 ve 1.1 kullanırken bir sınırlama, geleneksel .NET Framework'te EF 6.x'te tanımlandığı gibi karmaşık türleri kullanamadığınızdır. Bu nedenle, EF Core 1.0 veya 1.1 kullanıyorsanız, değer nesnenizi kimlik alanına sahip bir EF varlığı olarak depolamanız gerekir. Ardından, daha çok kimliği olmayan bir değer nesnesi gibi göründüğünden, bir değer nesnesinin kimliğinin etki alanı modelinde önemli olmadığını açıkça belirterek kimliğini gizleyebilirsiniz. Kimliği gölge özellik olarak kullanarak bu kimliği gizleyebilirsiniz. Modelde kimliği gizleme yapılandırması EF altyapısı düzeyinde ayarlandığından, etki alanı modeliniz için saydam olabilir.

eShopOnContainers'ın (.NET Core 1.1) ilk sürümünde EF Core altyapısı için gereken gizli kimlik, altyapı projesinde Fluent API kullanılarak DbContext düzeyinde aşağıdaki şekilde uygulandı. Bu nedenle, kimlik etki alanı modeli açısından gizlendi, ancak altyapıda hala mevcuttu.

// Old approach with EF Core 1.1
// Fluent API within the OrderingContext:DbContext in the Infrastructure project
void ConfigureAddress(EntityTypeBuilder<Address> addressConfiguration)
{
    addressConfiguration.ToTable("address", DEFAULT_SCHEMA);

    addressConfiguration.Property<int>("Id")  // Id is a shadow property
        .IsRequired();
    addressConfiguration.HasKey("Id");   // Id is a shadow property
}

Ancak, bu değer nesnesinin veritabanında kalıcılığı farklı bir tablodaki normal bir varlık gibi gerçekleştirildi.

EF Core 2.0 ve üzeri ile değer nesnelerini kalıcı hale getirmenin yeni ve daha iyi yolları vardır.

EF Core 2.0 ve sonraki sürümlerde sahip olunan varlık türleri olarak değer nesnelerini kalıcı hale getirme

DDD'deki kurallı değer nesnesi deseni ile EF Core'daki sahip olunan varlık türü arasında bazı boşluklar olsa bile, şu anda EF Core 2.0 ve üzeri ile değer nesnelerini kalıcı hale getirmenin en iyi yoludur. Bu bölümün sonunda sınırlamaları görebilirsiniz.

Sahip olunan varlık türü özelliği, sürüm 2.0'dan bu yana EF Core'a eklendi.

Sahip olunan varlık türü, etki alanı modelinde açıkça tanımlanmış kendi kimliği olmayan ve varlıklarınızdan herhangi birinde değer nesnesi gibi özellikler olarak kullanılan türleri eşlemenize olanak tanır. Sahip olunan varlık türü aynı CLR türünü başka bir varlık türüyle paylaşır (yani, yalnızca normal bir sınıftır). Tanımlama gezintisini içeren varlık sahip varlığıdır. Sahibi sorgularken, sahip olunan türler varsayılan olarak eklenir.

Yalnızca etki alanı modeline bakarak, sahip olunan bir tür herhangi bir kimliğe sahip değil gibi görünür. Ancak, kapakların altında sahip olunan türler kimliğe sahiptir, ancak sahip gezinti özelliği bu kimliğin bir parçasıdır.

Sahip olunan türlerin örneklerinin kimliği tamamen kendilerine ait değildir. Üç bileşenden oluşur:

  • Sahibin kimliği

  • Bunları işaret eden gezinti özelliği

  • Sahip olunan tür koleksiyonları söz konusu olduğunda, bağımsız bir bileşen (EF Core 2.2 ve sonraki sürümlerde desteklenir).

Örneğin, eShopOnContainers'daki Sıralama etki alanı modelinde, Order varlığının bir parçası olarak, Adres değeri nesnesi sahip varlığı içinde sahip varlık türü olarak (Order varlığı olan) uygulanır. Address , etki alanı modelinde tanımlı kimlik özelliği olmayan bir türdür. Belirli bir siparişin sevkiyat adresini belirtmek için Sipariş türünün özelliği olarak kullanılır.

Kural gereği, sahip olunan tür için bir gölge birincil anahtar oluşturulur ve tablo bölme kullanılarak sahiple aynı tabloya eşlenir. Bu, geleneksel .NET Framework'te EF6'da karmaşık türlerin nasıl kullanıldığına benzer sahip türlerin kullanılmasına olanak tanır.

Sahip olunan türlerin EF Core'da hiçbir zaman kural tarafından bulunamayacağına dikkat etmek önemlidir, bu nedenle bunları açıkça bildirmeniz gerekir.

eShopOnContainers'da, OrderingContext.cs dosyasında, yönteminde OnModelCreating() birden çok altyapı yapılandırması uygulanır. Bunlardan biri Order varlığıyla ilgilidir.

// Part of the OrderingContext.cs class at the Ordering.Infrastructure project
//
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.ApplyConfiguration(new ClientRequestEntityTypeConfiguration());
    modelBuilder.ApplyConfiguration(new PaymentMethodEntityTypeConfiguration());
    modelBuilder.ApplyConfiguration(new OrderEntityTypeConfiguration());
    modelBuilder.ApplyConfiguration(new OrderItemEntityTypeConfiguration());
    //...Additional type configurations
}

Aşağıdaki kodda Order varlığı için kalıcılık altyapısı tanımlanır:

// Part of the OrderEntityTypeConfiguration.cs class
//
public void Configure(EntityTypeBuilder<Order> orderConfiguration)
{
    orderConfiguration.ToTable("orders", OrderingContext.DEFAULT_SCHEMA);
    orderConfiguration.HasKey(o => o.Id);
    orderConfiguration.Ignore(b => b.DomainEvents);
    orderConfiguration.Property(o => o.Id)
        .ForSqlServerUseSequenceHiLo("orderseq", OrderingContext.DEFAULT_SCHEMA);

    //Address value object persisted as owned entity in EF Core 2.0
    orderConfiguration.OwnsOne(o => o.Address);

    orderConfiguration.Property<DateTime>("OrderDate").IsRequired();

    //...Additional validations, constraints and code...
    //...
}

Önceki kodda orderConfiguration.OwnsOne(o => o.Address) yöntemi özelliğin türüne Address ait bir varlık Order olduğunu belirtir.

Varsayılan olarak, EF Core kuralları sahip olunan varlık türünün özellikleri için veritabanı sütunlarını olarak EntityProperty_OwnedEntityPropertyadlandırıyor. Bu nedenle, iç özellikleri Address tablosunda , Address_City (, ve ZipCodeiçin CountryStatevb.) adlarıyla Address_StreetgörünürOrders.

Bu sütunları yeniden adlandırmak için fluent yöntemini ekleyebilirsiniz Property().HasColumnName() . Ortak özelliğin olduğu Address durumlarda eşlemeler aşağıdaki gibi olacaktır:

orderConfiguration.OwnsOne(p => p.Address)
                            .Property(p=>p.Street).HasColumnName("ShippingStreet");

orderConfiguration.OwnsOne(p => p.Address)
                            .Property(p=>p.City).HasColumnName("ShippingCity");

Yöntemi akıcı bir eşlemede OwnsOne zincirleme yapmak mümkündür. Aşağıdaki varsayımsal örnekte, OrderDetails her ikisi de Address türü olan ve ShippingAddress'nin sahibidirBillingAddress. Ardından OrderDetails türüne Order aittir.

orderConfiguration.OwnsOne(p => p.OrderDetails, cb =>
    {
        cb.OwnsOne(c => c.BillingAddress);
        cb.OwnsOne(c => c.ShippingAddress);
    });
//...
//...
public class Order
{
    public int Id { get; set; }
    public OrderDetails OrderDetails { get; set; }
}

public class OrderDetails
{
    public Address BillingAddress { get; set; }
    public Address ShippingAddress { get; set; }
}

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

Sahip olunan varlık türleriyle ilgili ek ayrıntılar

  • Sahip olunan türler, OwnsOne akıcı API'sini kullanarak belirli bir türe gezinti özelliği yapılandırdığınızda tanımlanır.

  • Meta veri modelimizdeki sahip olunan bir türün tanımı, sahip türü, gezinti özelliği ve sahip olunan türün CLR türü bileşimidir.

  • Yığınımızda sahip olunan bir tür örneğinin kimliği (anahtar), sahip türünün kimliğinin ve sahip olunan türün tanımının bir bileşimidir.

Sahip olunan varlıklar özellikleri

  • Sahip olunan türler, sahip olunan (iç içe sahip olunan türler) veya sahip olunmayan (diğer varlıklara normal başvuru gezinti özellikleri) diğer varlıklara başvurabilir.

  • Ayrı gezinti özellikleri aracılığıyla aynı sahip varlığındaki farklı sahip türler ile aynı CLR türünü eşleyebilirsiniz.

  • Tablo bölme kuralına göre ayarlanır, ancak ToTable kullanarak sahip olunan türü farklı bir tabloya eşleyerek bu seçeneği geri çevirebilirsiniz.

  • İstekli yükleme, sahip olunan türlerde otomatik olarak gerçekleştirilir, yani sorguda çağrı .Include() yapılması gerekmez.

  • EF Core 2.1 ve üzeri kullanılarak özniteliğiyle [Owned]yapılandırılabilir.

  • Sahip olunan tür koleksiyonlarını işleyebilir (sürüm 2.2 ve üzerini kullanarak).

Sahip olunan varlık sınırlamaları

  • Sahip olunan bir DbSet<T> tür oluşturamazsınız (tasarım gereği).

  • Sahip olunan türlerde (şu anda tasarım gereği) çağrı ModelBuilder.Entity<T>() yapamazsınız.

  • Aynı tablodaki sahiple eşlenen isteğe bağlı (null atanabilir) sahip türler (yani, tablo bölme kullanılarak) için destek yoktur. Bunun nedeni eşlemenin her özellik için yapılmasıdır; null karmaşık değer için bir bütün olarak ayrı bir sentinel yoktur.

  • Sahip olunan türler için devralma eşleme desteği yoktur, ancak aynı devralma hiyerarşilerinin iki yaprak türünü farklı sahip olunan türlerle eşleyebilmeniz gerekir. EF Core, bunların aynı hiyerarşinin bir parçası olması için neden olmaz.

EF6'nın karmaşık türleriyle ilgili temel farklar

  • Tablo bölme isteğe bağlıdır, yani isteğe bağlı olarak ayrı bir tabloyla eşlenebilir ve yine de sahip olunan türler olabilir.

Ek kaynaklar