[모범 사례] EF Core 6을 사용하여 DDD 값 개체로 C# 9 레코드

Antonio Falcao / Dotnet 6.EF Core 6.Record.Value 개체


이 프로젝트는 EF Core 6을 사용하여 레코드를 값 개체로 구성하고 사용하는 것을 보여 주는 것을 목표로 합니다.




가치 객체



값 개체는 동일성이 ID를 기반으로 하지 않는 단순한 엔터티를 나타내는 작은 개체입니다. 두 개의 값 개체는 동일한 값을 가질 때 동일하며 반드시 동일한 개체일 필요는 없습니다.

주요 특징:
  • 정체성이 없습니다.
  • 변경할 수 없습니다.

  • 기록



    데이터 캡슐화를 위한 기본 제공 기능을 제공하는 참조 형식을 정의합니다. 변경할 수 없는 속성이 있는 유형record을 생성할 수 있습니다.

    불변성



    Arecord 유형이 반드시 변경할 수 있는 것은 아닙니다. 읽기 전용이 아닌 set 접근자와 필드를 사용하여 속성을 선언할 수 있습니다. 그러나 레코드는 변경할 수 있지만 변경할 수 없는 데이터 모델을 쉽게 만들 수 있습니다.

    평등



    값 같음은 유형이 일치하고 모든 속성 및 필드 값이 일치하는 경우 record 유형의 두 변수가 같음을 의미합니다. 다른 참조 유형의 경우 같음은 ID를 의미합니다. 즉, 참조 유형의 두 변수는 동일한 개체를 참조하는 경우 동일합니다.
    record 유형의 값 개체의 경우 기본적으로 합성된 같음 멤버는 다음과 같습니다.

    class R1 : IEquatable<R1>
    {
        public T1 P1 { get; init; }
    
        protected virtual Type EqualityContract => typeof(R1);
    
        public override bool Equals(object? obj) => Equals(obj as R1);
    
        public virtual bool Equals(R1? other)
        {
            return !(other is null) &&
                EqualityContract == other.EqualityContract &&
                EqualityComparer<T1>.Default.Equals(P1, other.P1);
        }
    
        public static bool operator==(R1? left, R1? right)
            => (object)left == right || (left?.Equals(right) ?? false);
    
        public static bool operator!=(R1? left, R1? right)
            => !(left == right);
    
        public override int GetHashCode()
        {
            return Combine(EqualityComparer<Type>.Default.GetHashCode(EqualityContract),
                EqualityComparer<T1>.Default.GetHashCode(P1));
        }
    }
    


    EF Core에서 소유 엔터티 형식으로 값 개체 유지



    Even with some gaps between the canonical value object pattern in DDD and the owned entity type in EF Core, it's currently the best way to persist value objects with EF Core.



    소유 엔터티 형식을 사용하면 도메인 모델에 명시적으로 정의된 자체 ID가 없고 값 개체와 같은 속성으로 사용되는 형식을 매핑할 수 있습니다.

    소유된 유형의 인스턴스 ID는 완전히 고유하지 않습니다. 세 가지 구성 요소로 구성됩니다.
  • 소유자의 신원
  • 이를 가리키는 탐색 속성
  • 소유 유형 컬렉션의 경우 독립 구성 요소입니다.

  • 실재



    public class Person : Entity<Guid>
    {
        public Person(string name, int age) 
            => (Name, Age) = (name, age);
    
        public string Name { get; }
        public int Age { get; }
        public Address Address { get; private set; }
    
        public void DefineAddress(Address address)
        {
            if (address is null) throw new BusinessException("Home address must be informed");
            // Benefit of the record Equality Contract
            if(address.Equals(Address)) return;
            Address = address;
        }
    }
    

    값 개체



    public record Address : Abstractions.ValueObject
    {
        // Empty constructor in this case is required by EF Core,
        // because has a complex type as a parameter in the default constructor.
        private Address() { }
    
        public Address(Street street, string zipCode)
            => (Street, ZipCode) = (street, zipCode);
    
        public Street Street { get; private init; }
        public string ZipCode { get; private init; }
    }
    

    구성



    관계형 데이터베이스에 필요한 관계를 해결하기 위해서는 식별자를 섀도우 속성으로 정의해야 합니다.

    public class PersonConfiguration : IEntityTypeConfiguration<Person>
    {
        public void Configure(EntityTypeBuilder<Person> builder)
        {
            builder.HasKey(user => user.Id);
    
            ...
    
            // Configures a relationship where the Address is owned by (or part of) Person.
            builder.OwnsOne(
                person => person.Address,
                addressNavigationBuilder =>
                {
                    // Configures a different table that the entity type maps to when targeting a relational database.
                    addressNavigationBuilder
                        .ToTable("Addresses");
    
                    // Configures the relationship to the owner, and indicates the Foreign Key.
                    addressNavigationBuilder
                        .WithOwner()
                        .HasForeignKey("PersonId"); // Shadow Foreign Key
    
                    // Configure a property of the owned entity type, in this case the to be used as Primary Key
                    addressNavigationBuilder
                        .Property<Guid>("Id"); // Shadow property
    
                    // Sets the properties that make up the primary key for this owned entity type.
                    addressNavigationBuilder
                        .HasKey("Id"); // Shadow Primary Key
    
                    // Configures a relationship where the Street is owned by (or part of) Addresses.
                    // In this case, is not used "ToTable();" to maintain the owned and owner in the same table. 
                    addressNavigationBuilder.OwnsOne(
                        address => address.Street,
                        streetNavigationBuilder =>
                        {
                            ...
    
                            // Configures a relationship where the City is owned by (or part of) Street.
                            // In this case, is not used "ToTable();" to maintain the owned and owner in the same table. 
                            streetNavigationBuilder.OwnsOne(
                                street => street.City,
                                cityNavigationBuilder =>
                                {
                                    ...
                                });
                        });
                });
        }
    }
    

    스크립트 생성



    CREATE TABLE [Persons]
    (
        [Id]   uniqueidentifier NOT NULL,
        [Name] varchar(128)     NOT NULL,
        [Age]  int              NOT NULL,
        CONSTRAINT [PK_Persons] PRIMARY KEY ([Id])
    );
    
    CREATE TABLE [Addresses]
    (
        [Id]                                 uniqueidentifier NOT NULL,
        [Street_City_Name]                   varchar(128)     NULL,
        [Street_City_State_Country_Initials] varchar(8)       NULL,
        [Street_City_State_Country_Name]     varchar(128)     NULL,
        [Street_City_State_Initials]         varchar(8)       NULL,
        [Street_City_State_Name]             varchar(128)     NULL,
        [Street_Name]                        varchar(128)     NULL,
        [Street_Number]                      int              NULL,
        [ZipCode]                            varchar(32)      NULL,
        [PersonId]                           uniqueidentifier NOT NULL,
        CONSTRAINT [PK_Addresses] PRIMARY KEY ([Id]),
        CONSTRAINT [FK_Addresses_Persons_PersonId] FOREIGN KEY ([PersonId]) REFERENCES [Persons] ([Id]) ON DELETE CASCADE
    );
    
    CREATE UNIQUE INDEX [IX_Addresses_PersonId] ON [Addresses] ([PersonId]);
    


    EF Core를 사용하여 고아 값 개체 삭제



    상위 및 하위 엔터티 간의 관계는 필수 또는 선택 사항일 수 있습니다. 필수관계란 자식은 부모 없이는 존재할 수 없다는 것을 의미하며, 부모가 삭제되거나 자식과 부모의 관계가 단절되면 자식은 고아가 된다. 이 경우 EF Core는 자동으로 하위 삭제를 수행합니다.




    Antonio Falcao / Dotnet 6.EF Core 6.Record.Value 개체


    이 프로젝트는 EF Core 6을 사용하여 레코드를 값 개체로 구성하고 사용하는 것을 보여 주는 것을 목표로 합니다.

    좋은 웹페이지 즐겨찾기