소유한 엔터티 형식

EF Core를 사용 하면 다른 엔터티 형식의 탐색 속성에만 나타날 수 있는 엔터티 형식을 모델링할 수 있습니다. 이러한 엔터티를 소유 된 엔터티 형식이라고 합니다. 소유 된 엔터티 형식을 포함 하는 엔터티는 해당 소유자입니다.

소유 된 엔터티는 기본적으로 소유자의 일부 이며이를 제외 하 고는 집계와 개념적으로 유사 합니다. 즉, 소유 된 엔터티는 소유자와 관계의 종속 측에 정의 됩니다.

소유로 유형 구성

대부분의 공급자에서 엔터티 형식은 규칙에서 소유 하는 것으로 구성 되지 않습니다 .에서 메서드를 명시적으로 사용 OwnsOneOnModelCreating 하거나 형식에 주석을 추가 OwnedAttribute 하 여 형식을 소유로 구성 해야 합니다. Azure Cosmos DB 공급자는이에 대 한 예외입니다. Cosmos DB는 문서 데이터베이스 이기 때문에 공급자는 모든 관련 엔터티 형식을 기본적으로 소유 하는 것으로 구성 합니다.

이 예제에서 StreetAddress 는 identity 속성이 없는 형식입니다. 특정 주문의 배송 주소를 지정하기 위한 Order 형식 속성으로 사용됩니다.

를 사용 하 여 OwnedAttribute 다른 엔터티 형식에서 참조 하는 경우이를 소유 엔터티로 처리할 수 있습니다.

[Owned]
public class StreetAddress
{
    public string Street { get; set; }
    public string City { get; set; }
}
public class Order
{
    public int Id { get; set; }
    public StreetAddress ShippingAddress { get; set; }
}

또한에서 메서드를 사용 하 여 OwnsOneOnModelCreatingShippingAddress 속성이 엔터티 형식의 소유 된 엔터티 임을 지정 하 Order 고 필요한 경우 추가 패싯을 구성할 수 있습니다.

modelBuilder.Entity<Order>().OwnsOne(p => p.ShippingAddress);

ShippingAddress속성이 형식에서 private 이면 Order 메서드의 문자열 버전을 사용할 수 있습니다 OwnsOne .

modelBuilder.Entity<Order>().OwnsOne(typeof(StreetAddress), "ShippingAddress");

위의 모델은 다음 데이터베이스 스키마에 매핑됩니다.

Sceenshot of the database model for entity containing owned reference

자세한 컨텍스트는 전체 샘플 프로젝트 를 참조 하세요.

소유 된 엔터티 형식은 필수로 표시 될 수 있습니다. 자세한 내용은 필요한 일대일 종속 항목을 참조 하세요.

암시적 키

를 사용 하 여 구성 OwnsOne 하거나 참조 탐색을 통해 검색 된 형식에는 항상 소유자와 일 대 일 관계가 있으므로 외래 키 값이 고유 하므로 고유한 키 값이 필요 하지 않습니다. 이전 예제에서 StreetAddress 형식은 키 속성을 정의할 필요가 없습니다.

EF Core에서 이러한 개체를 추적 하는 방법을 이해 하기 위해 소유 된 형식에 대 한 그림자 속성 으로 기본 키가 생성 되는 것을 알고 있으면 유용 합니다. 소유 된 형식의 인스턴스 키 값은 소유자 인스턴스 키의 값과 동일 합니다.

소유 된 형식의 컬렉션

소유 된 형식의 컬렉션을 구성 하려면에서을 사용 OwnsManyOnModelCreating 합니다.

소유 된 형식에는 기본 키가 필요 합니다. .NET 형식에 적합 한 후보 속성이 없으면 하나를 만들 수 EF Core. 그러나 소유 된 형식이 컬렉션을 통해 정의 되는 경우에 대 한 것과 같이 소유 된 인스턴스의 소유자 및 기본 키에 대 한 외래 키 역할을 하는 그림자 속성을 만드는 것 만으로는 충분 하지 않습니다 OwnsOne . 각 소유자에 대해 소유 된 형식 인스턴스가 여러 개 있을 수 있으므로 소유자의 키로 소유 된 각 인스턴스에 대 한 고유 id를 제공 하기에 충분 하지 않습니다.

이에 대 한 가장 간단한 두 가지 솔루션은 다음과 같습니다.

  • 소유자를 가리키는 외래 키와 관계 없이 새 속성에 서로게이트 기본 키를 정의 합니다. 포함 된 값은 모든 소유자에서 고유 해야 합니다 (예: Parent {1} 에 자식이 있는 경우 {1} Parent는 {2} 자식을 가질 수 없음 {1} ). 따라서 값에 내재 된 의미가 없습니다. 외래 키가 기본 키의 일부가 아니므로 해당 값을 변경할 수 있으므로 한 부모에서 다른 부모로 자식 항목을 이동할 수 있습니다. 그러나 일반적으로 집계 의미 체계를 기반으로 합니다.
  • 외래 키 및 추가 속성을 복합 키로 사용 합니다. 이제 추가 속성 값은 지정 된 부모에 대해서만 고유 해야 합니다. 따라서 부모 {1} 에 자식 요소가 있으면 {1,1} 부모는 {2} 여전히 자식을 가질 수 있습니다 {2,1} . 기본 키의 외래 키 부분을 만들면 소유자와 소유 된 엔터티 간의 관계가 변경할 수 없게 되 고 집계 의미 체계가 향상 됩니다. 이 EF Core 기본적으로 수행 됩니다.

이 예제에서는 클래스를 사용 Distributor 합니다.

public class Distributor
{
    public int Id { get; set; }
    public ICollection<StreetAddress> ShippingCenters { get; set; }
}

기본적으로 탐색 속성을 통해 참조 되는 소유 된 형식에 사용 되는 기본 키는 ShippingCenters("DistributorId", "Id")"DistributorId" 이며, 여기서은 FK 이며 "Id" 고유한 int 값입니다.

다른 기본 키 호출을 구성 하려면 HasKey 입니다.

modelBuilder.Entity<Distributor>().OwnsMany(
    p => p.ShippingCenters, a =>
    {
        a.WithOwner().HasForeignKey("OwnerId");
        a.Property<int>("Id");
        a.HasKey("Id");
    });

위의 모델은 다음 데이터베이스 스키마에 매핑됩니다.

Sceenshot of the database model for entity containing owned collection

테이블 분할을 사용 하 여 소유 된 형식 매핑

관계형 데이터베이스를 사용 하는 경우 기본적으로 소유 하는 참조는 소유자와 동일한 테이블에 매핑됩니다. 이렇게 하려면 두 개의 테이블을 분할 해야 합니다. 일부 열은 소유자의 데이터를 저장 하는 데 사용 되 고 일부 열은 소유 된 엔터티의 데이터를 저장 하는 데 사용 됩니다. 이는 테이블 분할이라는 일반적인 기능입니다.

기본적으로 EF Core는 패턴 Navigation_OwnedEntityProperty따라 소유 된 엔터티 형식의 속성에 대 한 데이터베이스 열 이름을로 합니다. 따라서 StreetAddress 속성이 ' ShippingAddress_Street ' 및 ' ShippingAddress_City ' 인 ' Orders ' 테이블에 표시 됩니다.

메서드를 사용 HasColumnName 하 여 해당 열의 이름을 바꿀 수 있습니다.

modelBuilder.Entity<Order>().OwnsOne(
    o => o.ShippingAddress,
    sa =>
    {
        sa.Property(p => p.Street).HasColumnName("ShipsToStreet");
        sa.Property(p => p.City).HasColumnName("ShipsToCity");
    });

참고

Ignore 와 같은 일반적인 엔터티 형식 구성 메서드는 대부분 동일한 방식으로 호출할 수 있습니다.

여러 개의 소유 된 형식 간에 동일한 .NET 형식 공유

소유 된 엔터티 형식은 다른 소유 된 엔터티 형식과 동일한 .NET 형식일 수 있으므로 .NET 형식은 소유 된 형식을 식별 하기에 충분 하지 않을 수 있습니다.

이러한 경우 소유자를 가리키는 속성은 소유 된 엔터티 형식을 정의 하는 탐색 이 됩니다. EF Core 관점에서, 정의 탐색은 .NET 형식과 함께 형식의 id의 일부입니다.

예를 들어 다음 클래스에서 ShippingAddressBillingAddress 는 모두 동일한 .net 형식 StreetAddress 입니다.

public class OrderDetails
{
    public DetailedOrder Order { get; set; }
    public StreetAddress BillingAddress { get; set; }
    public StreetAddress ShippingAddress { get; set; }
}

EF Core에서 이러한 개체의 추적 된 인스턴스를 구분 하는 방법을 이해 하기 위해 정의 하는 탐색이 소유자의 키 값과 소유 된 형식의 .NET 형식으로 인스턴스 키의 일부가 되는 것으로 생각 하는 것이 유용할 수 있습니다.

중첩 된 소유 형식

이 예제에서는 OrderDetailsBillingAddressShippingAddress 둘 다 형식인 및를 소유 StreetAddress 합니다. 그리고 OrderDetailsDetailedOrder 형식입니다.

public class DetailedOrder
{
    public int Id { get; set; }
    public OrderDetails OrderDetails { get; set; }
    public OrderStatus Status { get; set; }
}
public enum OrderStatus
{
    Pending,
    Shipped
}

소유 된 형식에 대 한 각 탐색은 완전히 독립적으로 구성 된 별도의 엔터티 형식을 정의 합니다.

중첩 된 소유 형식 외에도 소유 된 형식은 소유 된 엔터티가 종속 측에 있는 한 소유자 또는 다른 엔터티가 될 수 있는 일반 엔터티를 참조할 수 있습니다. 이 기능은 소유 하는 엔터티 형식을 EF6의 복합 형식과 분리 하 여 설정 합니다.

public class OrderDetails
{
    public DetailedOrder Order { get; set; }
    public StreetAddress BillingAddress { get; set; }
    public StreetAddress ShippingAddress { get; set; }
}

소유 형식 구성

OwnsOne흐름 호출에서 메서드를 연결 하 여이 모델을 구성할 수 있습니다.

modelBuilder.Entity<DetailedOrder>().OwnsOne(
    p => p.OrderDetails, od =>
    {
        od.WithOwner(d => d.Order);
        od.Navigation(d => d.Order).UsePropertyAccessMode(PropertyAccessMode.Property);
        od.OwnsOne(c => c.BillingAddress);
        od.OwnsOne(c => c.ShippingAddress);
    });

WithOwner소유자의 탐색 속성을 정의 하는 데 사용 되는 호출을 확인 합니다. 소유권 관계의 일부가 아닌 소유자 엔터티 형식에 대 한 탐색을 정의 하려면 WithOwner() 인수 없이 호출 해야 합니다.

OwnedAttribute및 둘 다에서를 사용 하 여이 결과를 얻을 수도 있습니다 OrderDetailsStreetAddress .

또한 호출을 확인 Navigation 합니다. EFCore 5.0에서는 소유 된 형식에 대 한 탐색 속성을 소유 하지 않는 탐색 속성에 대해추가로 구성할 수 있습니다.

위의 모델은 다음 데이터베이스 스키마에 매핑됩니다.

Screenshot of the database model for entity containing nested owned references

소유 형식을 별도의 테이블에 저장

또한 EF6 복합 형식과 달리 소유 된 형식은 소유자와는 별도의 테이블에 저장 될 수 있습니다. 소유 된 형식을 소유자와 동일한 테이블에 매핑하는 규칙을 재정의 하기 위해를 호출 하 ToTable 고 다른 테이블 이름을 제공할 수 있습니다. 다음 예에서는 OrderDetails 및 두 개의 주소를의 별도 테이블에 매핑합니다 DetailedOrder .

modelBuilder.Entity<DetailedOrder>().OwnsOne(p => p.OrderDetails, od => { od.ToTable("OrderDetails"); });

를 사용 하 여 TableAttribute 이를 수행할 수도 있지만이 경우 여러 엔터티 형식이 동일한 테이블에 매핑되므로 소유 된 형식에 대해 여러 탐색이 있으면이 작업이 실패 합니다.

소유 형식 쿼리

소유자를 쿼리할 때 소유된 형식은 기본적으로 포함됩니다. Include소유 된 형식이 별도의 테이블에 저장 된 경우에도 메서드를 사용할 필요가 없습니다. 앞에서 설명한 모델에 따라 다음 쿼리는 OrderOrderDetails 데이터베이스에서 소유 하 고 두 가지를 가져옵니다 StreetAddresses .

var order = context.DetailedOrders.First(o => o.Status == OrderStatus.Pending);
Console.WriteLine($"First pending order will ship to: {order.OrderDetails.ShippingAddress.City}");

제한 사항

이러한 제한 사항 중 일부는 소유 된 엔터티 형식이 작동 하는 방식에 대 한 기본 이지만 다른 일부는 이후 릴리스에서 제거할 수 있는 제한 사항이 있습니다.

디자인 제한 사항

  • 소유 된 형식에 대해서는를 만들 수 없습니다 DbSet<T> .
  • Entity<T>()에서 소유 된 형식을 사용 하 여를 호출할 수 없습니다 ModelBuilder .
  • 소유 된 엔터티 형식의 인스턴스는 여러 소유자가 공유할 수 없습니다 .이는 소유 된 엔터티 형식을 사용 하 여 구현할 수 없는 값 개체에 대 한 잘 알려진 시나리오입니다.

현재 단점

  • 소유 된 엔터티 형식에는 상속 계층을 사용할 수 없습니다.

이전 버전의 단점

  • EF Core 2.x에서 소유 된 엔터티 형식에 대 한 참조 탐색은 소유자와 별도의 테이블에 명시적으로 매핑되지 않는 한 null 일 수 없습니다.
  • EF Core 3.x에서 소유자와 동일한 테이블에 매핑되는 소유 된 엔터티 형식에 대 한 열은 항상 nullable로 표시 됩니다.