소유한 엔터티 형식Owned Entity Types

EF Core를 사용 하면 다른 엔터티 형식의 탐색 속성에만 나타날 수 있는 엔터티 형식을 모델링할 수 있습니다.EF Core allows you to model entity types that can only ever appear on navigation properties of other entity types. 이러한 엔터티를 _소유 된 엔터티 형식_이라고 합니다.These are called owned entity types. 소유 된 엔터티 형식을 포함 하는 엔터티는 해당 _소유자_입니다.The entity containing an owned entity type is its owner.

소유 된 엔터티는 기본적으로 소유자의 일부 이며이를 제외 하 고는 집계와 개념적으로 유사 합니다.Owned entities are essentially a part of the owner and cannot exist without it, they are conceptually similar to aggregates. 즉, 소유 된 엔터티는 소유자와 관계의 종속 측에 정의 됩니다.This means that the owned entity is by definition on the dependent side of the relationship with the owner.

명시적 구성Explicit configuration

소유 된 엔터티 형식은 규칙에 따라 모델의 EF Core에 포함 되지 않습니다.Owned entity types are never included by EF Core in the model by convention. 에서 메서드를 사용 OwnsOne OnModelCreating 하거나 OwnedAttribute (EF Core 2.1의 새로운) 형식에 주석을 추가 하 여 형식을 소유 된 형식으로 구성할 수 있습니다.You can use the OwnsOne method in OnModelCreating or annotate the type with OwnedAttribute (new in EF Core 2.1) to configure the type as an owned type.

이 예제에서 StreetAddress 는 identity 속성이 없는 형식입니다.In this example, StreetAddress is a type with no identity property. 특정 주문의 배송 주소를 지정하기 위한 Order 형식 속성으로 사용됩니다.It is used as a property of the Order type to specify the shipping address for a particular order.

를 사용 하 여 OwnedAttribute 다른 엔터티 형식에서 참조 하는 경우이를 소유 엔터티로 처리할 수 있습니다.We can use the OwnedAttribute to treat it as an owned entity when referenced from another entity type:

[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; }
}

또한에서 메서드를 사용 하 여 OwnsOne OnModelCreating ShippingAddress 속성이 엔터티 형식의 소유 된 엔터티 임을 지정 하 Order 고 필요한 경우 추가 패싯을 구성할 수 있습니다.It is also possible to use the OwnsOne method in OnModelCreating to specify that the ShippingAddress property is an Owned Entity of the Order entity type and to configure additional facets if needed.

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

ShippingAddress속성이 형식에서 private 이면 Order 메서드의 문자열 버전을 사용할 수 있습니다 OwnsOne .If the ShippingAddress property is private in the Order type, you can use the string version of the OwnsOne method:

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

자세한 컨텍스트는 전체 샘플 프로젝트 를 참조 하세요.See the full sample project for more context.

암시적 키Implicit keys

를 사용 하 여 구성 OwnsOne 하거나 참조 탐색을 통해 검색 된 형식에는 항상 소유자와 일 대 일 관계가 있으므로 외래 키 값이 고유 하므로 고유한 키 값이 필요 하지 않습니다.Owned types configured with OwnsOne or discovered through a reference navigation always have a one-to-one relationship with the owner, therefore they don't need their own key values as the foreign key values are unique. 이전 예제에서 StreetAddress 형식은 키 속성을 정의할 필요가 없습니다.In the previous example, the StreetAddress type does not need to define a key property.

EF Core에서 이러한 개체를 추적 하는 방법을 이해 하기 위해 소유 된 형식에 대 한 그림자 속성 으로 기본 키가 생성 되는 것을 알고 있으면 유용 합니다.In order to understand how EF Core tracks these objects, it is useful to know that a primary key is created as a shadow property for the owned type. 소유 된 형식의 인스턴스 키 값은 소유자 인스턴스 키의 값과 동일 합니다.The value of the key of an instance of the owned type will be the same as the value of the key of the owner instance.

소유 된 형식의 컬렉션Collections of owned types

참고

이 기능은 EF Core 2.2의 새로운 기능입니다.This feature is new in EF Core 2.2.

소유 된 형식의 컬렉션을 구성 하려면에서을 사용 OwnsMany OnModelCreating 합니다.To configure a collection of owned types use OwnsMany in OnModelCreating.

소유 된 형식에는 기본 키가 필요 합니다.Owned types need a primary key. .NET 형식에 적합 한 후보 속성이 없으면 하나를 만들 수 EF Core.If there are no good candidates properties on the .NET type, EF Core can try to create one. 그러나 소유 된 형식이 컬렉션을 통해 정의 되는 경우에 대 한 것과 같이 소유 된 인스턴스의 소유자 및 기본 키에 대 한 외래 키 역할을 하는 그림자 속성을 만드는 것 만으로는 충분 하지 않습니다 OwnsOne . 각 소유자에 대해 소유 된 형식 인스턴스가 여러 개 있을 수 있으므로 소유자의 키로 소유 된 각 인스턴스에 대 한 고유 id를 제공 하기에 충분 하지 않습니다.However, when owned types are defined through a collection, it isn't enough to just create a shadow property to act as both the foreign key into the owner and the primary key of the owned instance, as we do for OwnsOne: there can be multiple owned type instances for each owner, and hence the key of the owner isn't enough to provide a unique identity for each owned instance.

이에 대 한 가장 간단한 두 가지 솔루션은 다음과 같습니다.The two most straightforward solutions to this are:

  • 소유자를 가리키는 외래 키와 관계 없이 새 속성에 서로게이트 기본 키를 정의 합니다.Defining a surrogate primary key on a new property independent of the foreign key that points to the owner. 포함 된 값은 모든 소유자에서 고유 해야 합니다 (예: Parent {1} 에 자식이 있는 경우 {1} Parent는 {2} 자식을 가질 수 없음 {1} ). 따라서 값에 내재 된 의미가 없습니다.The contained values would need to be unique across all owners (e.g. if Parent {1} has Child {1}, then Parent {2} cannot have Child {1}), so the value doesn't have any inherent meaning. 외래 키가 기본 키의 일부가 아니므로 해당 값을 변경할 수 있으므로 한 부모에서 다른 부모로 자식 항목을 이동할 수 있습니다. 그러나 일반적으로 집계 의미 체계를 기반으로 합니다.Since the foreign key is not part of the primary key its values can be changed, so you could move a child from one parent to another one, however this usually goes against aggregate semantics.
  • 외래 키 및 추가 속성을 복합 키로 사용 합니다.Using the foreign key and an additional property as a composite key. 이제 추가 속성 값은 지정 된 부모에 대해서만 고유 해야 합니다. 따라서 부모 {1} 에 자식 요소가 있으면 {1,1} 부모는 {2} 여전히 자식을 가질 수 있습니다 {2,1} .The additional property value now only needs to be unique for a given parent (so if Parent {1} has Child {1,1} then Parent {2} can still have Child {2,1}). 기본 키의 외래 키 부분을 만들면 소유자와 소유 된 엔터티 간의 관계가 변경할 수 없게 되 고 집계 의미 체계가 향상 됩니다.By making the foreign key part of the primary key the relationship between the owner and the owned entity becomes immutable and reflects aggregate semantics better. 이 EF Core 기본적으로 수행 됩니다.This is what EF Core does by default.

이 예제에서는 클래스를 사용 합니다 Distributor .In this example we'll use the Distributor class:

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

기본적으로 탐색 속성을 통해 참조 되는 소유 된 형식에 사용 되는 기본 키는 ShippingCenters ("DistributorId", "Id") "DistributorId" 이며, 여기서은 FK 이며 "Id" 고유한 int 값입니다.By default the primary key used for the owned type referenced through the ShippingCenters navigation property will be ("DistributorId", "Id") where "DistributorId" is the FK and "Id" is a unique int value.

다른 PK 호출을 구성 하려면 HasKey :To configure a different PK call HasKey:

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

참고

EF Core 3.0 WithOwner() 메서드가 없으므로이 호출을 제거 해야 합니다.Before EF Core 3.0 WithOwner() method didn't exist so this call should be removed. 또한 기본 키는 자동으로 검색 되지 않으므로 항상 지정 해야 합니다.Also the primary key was not discovered automatically so it always had to be specified.

테이블 분할을 사용 하 여 소유 된 형식 매핑Mapping owned types with table splitting

관계형 데이터베이스를 사용 하는 경우 기본적으로 소유 하는 참조는 소유자와 동일한 테이블에 매핑됩니다.When using relational databases, by default reference owned types are mapped to the same table as the owner. 이렇게 하려면 두 개의 테이블을 분할 해야 합니다. 일부 열은 소유자의 데이터를 저장 하는 데 사용 되 고 일부 열은 소유 된 엔터티의 데이터를 저장 하는 데 사용 됩니다.This requires splitting the table in two: some columns will be used to store the data of the owner, and some columns will be used to store data of the owned entity. 이는 테이블 분할이라는 일반적인 기능입니다.This is a common feature known as table splitting.

기본적으로 EF Core는 패턴 _Navigation_OwnedEntityProperty_따라 소유 된 엔터티 형식의 속성에 대 한 데이터베이스 열 이름을로 합니다.By default, EF Core will name the database columns for the properties of the owned entity type following the pattern Navigation_OwnedEntityProperty. 따라서 StreetAddress 속성이 ' ShippingAddress_Street ' 및 ' ShippingAddress_City ' 인 ' Orders ' 테이블에 표시 됩니다.Therefore the StreetAddress properties will appear in the 'Orders' table with the names 'ShippingAddress_Street' and 'ShippingAddress_City'.

메서드를 사용 HasColumnName 하 여 해당 열의 이름을 바꿀 수 있습니다.You can use the HasColumnName method to rename those columns:

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

참고

Ignore 와 같은 일반적인 엔터티 형식 구성 메서드는 대부분 동일한 방식으로 호출할 수 있습니다.Most of the normal entity type configuration methods like Ignore can be called in the same way.

여러 개의 소유 된 형식 간에 동일한 .NET 형식 공유Sharing the same .NET type among multiple owned types

소유 된 엔터티 형식은 다른 소유 된 엔터티 형식과 동일한 .NET 형식일 수 있으므로 .NET 형식은 소유 된 형식을 식별 하기에 충분 하지 않을 수 있습니다.An owned entity type can be of the same .NET type as another owned entity type, therefore the .NET type may not be enough to identify an owned type.

이러한 경우 소유자를 가리키는 속성은 소유 된 엔터티 형식을 정의 하는 탐색 이 됩니다.In those cases, the property pointing from the owner to the owned entity becomes the defining navigation of the owned entity type. EF Core 관점에서, 정의 탐색은 .NET 형식과 함께 형식의 id의 일부입니다.From the perspective of EF Core, the defining navigation is part of the type's identity alongside the .NET type.

예를 들어 다음 클래스에서 ShippingAddressBillingAddress 는 모두 동일한 .net 형식입니다 StreetAddress .For example, in the following class ShippingAddress and BillingAddress are both of the same .NET type, StreetAddress:

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

EF Core에서 이러한 개체의 추적 된 인스턴스를 구분 하는 방법을 이해 하기 위해 정의 하는 탐색이 소유자의 키 값과 소유 된 형식의 .NET 형식으로 인스턴스 키의 일부가 되는 것으로 생각 하는 것이 유용할 수 있습니다.In order to understand how EF Core will distinguish tracked instances of these objects, it may be useful to think that the defining navigation has become part of the key of the instance alongside the value of the key of the owner and the .NET type of the owned type.

중첩 된 소유 형식Nested owned types

이 예제에서는 OrderDetails BillingAddress ShippingAddress 둘 다 형식인 및를 소유 StreetAddress 합니다.In this example OrderDetails owns BillingAddress and ShippingAddress, which are both StreetAddress types. 그리고 OrderDetailsDetailedOrder 형식입니다.Then OrderDetails is owned by the DetailedOrder type.

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

소유 된 형식에 대 한 각 탐색은 완전히 독립적으로 구성 된 별도의 엔터티 형식을 정의 합니다.Each navigation to an owned type defines a separate entity type with completely independent configuration.

중첩 된 소유 형식 외에도 소유 된 형식은 소유 된 엔터티가 종속 측에 있는 한 소유자 또는 다른 엔터티가 될 수 있는 일반 엔터티를 참조할 수 있습니다.In addition to nested owned types, an owned type can reference a regular entity which can be either the owner or a different entity as long as the owned entity is on the dependent side. 이 기능은 소유 하는 엔터티 형식을 EF6의 복합 형식과 분리 하 여 설정 합니다.This capability sets owned entity types apart from complex types in EF6.

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

소유 형식 구성Configuring Owned Types

OwnsOne흐름 호출에서 메서드를 연결 하 여이 모델을 구성할 수 있습니다.It is possible to chain the OwnsOne method in a fluent call to configure this model:

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소유자의 탐색 속성을 정의 하는 데 사용 되는 호출을 확인 합니다.Notice the WithOwner call used to define the navigation property pointing back at the owner. 소유권 관계의 일부가 아닌 소유자 엔터티 형식에 대 한 탐색을 정의 하려면 WithOwner() 인수 없이 호출 해야 합니다.To define a navigation to the owner entity type that's not part of the ownership relationship WithOwner() should be called without any arguments.

OwnedAttribute및 둘 다에서를 사용 하 여이 결과를 얻을 수도 있습니다 OrderDetails StreetAddress .It is also possible to achieve this result using OwnedAttribute on both OrderDetails and StreetAddress.

또한 호출을 확인 Navigation 합니다.In addition, notice the Navigation call. EFCore 5.0에서는 소유 된 형식에 대 한 탐색 속성을 소유 하지 않는 탐색 속성에 대해추가로 구성할 수 있습니다.In EFCore 5.0, navigation properties to owned types can be further configured as for non-owned navigation properties.

소유 형식을 별도의 테이블에 저장Storing owned types in separate tables

또한 EF6 복합 형식과 달리 소유 된 형식은 소유자와는 별도의 테이블에 저장 될 수 있습니다.Also unlike EF6 complex types, owned types can be stored in a separate table from the owner. 소유 된 형식을 소유자와 동일한 테이블에 매핑하는 규칙을 재정의 하기 위해를 호출 하 ToTable 고 다른 테이블 이름을 제공할 수 있습니다.In order to override the convention that maps an owned type to the same table as the owner, you can simply call ToTable and provide a different table name. 다음 예에서는 OrderDetails 및 두 개의 주소를의 별도 테이블에 매핑합니다 DetailedOrder .The following example will map OrderDetails and its two addresses to a separate table from DetailedOrder:

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

를 사용 하 여 TableAttribute 이를 수행할 수도 있지만이 경우 여러 엔터티 형식이 동일한 테이블에 매핑되므로 소유 된 형식에 대해 여러 탐색이 있으면이 작업이 실패 합니다.It is also possible to use the TableAttribute to accomplish this, but note that this would fail if there are multiple navigations to the owned type since in that case multiple entity types would be mapped to the same table.

소유 형식 쿼리Querying owned types

소유자를 쿼리할 때 소유된 형식은 기본적으로 포함됩니다.When querying the owner the owned types will be included by default. Include소유 된 형식이 별도의 테이블에 저장 된 경우에도 메서드를 사용할 필요가 없습니다.It is not necessary to use the Include method, even if the owned types are stored in a separate table. 앞에서 설명한 모델에 따라 다음 쿼리는 Order OrderDetails 데이터베이스에서 소유 하 고 두 가지를 가져옵니다 StreetAddresses .Based on the model described before, the following query will get Order, OrderDetails and the two owned StreetAddresses from the database:

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

제한 사항Limitations

이러한 제한 사항 중 일부는 소유 된 엔터티 형식이 작동 하는 방식에 대 한 기본 이지만 다른 일부는 이후 릴리스에서 제거할 수 있는 제한 사항이 있습니다.Some of these limitations are fundamental to how owned entity types work, but some others are restrictions that we may be able to remove in future releases:

디자인 제한 사항By-design restrictions

  • DbSet<T>소유 된 형식에 대해서는를 만들 수 없습니다.You cannot create a DbSet<T> for an owned type
  • Entity<T>()에서 소유 된 형식을 사용 하 여를 호출할 수 없습니다.ModelBuilderYou cannot call Entity<T>() with an owned type on ModelBuilder

현재 단점Current shortcomings

  • 소유 된 엔터티 형식에는 상속 계층을 사용할 수 없습니다.Owned entity types cannot have inheritance hierarchies
  • 소유 된 엔터티 형식에 대 한 참조 탐색은 소유자와 별도의 테이블에 명시적으로 매핑되지 않는 한 null 일 수 없습니다.Reference navigations to owned entity types cannot be null unless they are explicitly mapped to a separate table from the owner
  • 소유 된 엔터티 형식의 인스턴스는 여러 소유자가 공유할 수 없습니다 .이는 소유 된 엔터티 형식을 사용 하 여 구현할 수 없는 값 개체에 대 한 잘 알려진 시나리오입니다.Instances of owned entity types cannot be shared by multiple owners (this is a well-known scenario for value objects that cannot be implemented using owned entity types)

이전 버전의 단점Shortcomings in previous versions

  • EF Core 2.0에서 소유 된 엔터티는 소유자 계층 구조와 별도의 테이블에 명시적으로 매핑되어 있지 않는 한, 파생 엔터티 형식에서 소유 된 엔터티 형식에 대 한 탐색을 선언할 수 없습니다.In EF Core 2.0, navigations to owned entity types cannot be declared in derived entity types unless the owned entities are explicitly mapped to a separate table from the owner hierarchy. EF Core 2.1에서이 제한이 제거 되었습니다.This limitation has been removed in EF Core 2.1
  • EF Core 2.0 및 2.1에서는 소유 된 형식에 대 한 참조 탐색만 지원 됩니다.In EF Core 2.0 and 2.1 only reference navigations to owned types were supported. EF Core 2.2에서이 제한이 제거 되었습니다.This limitation has been removed in EF Core 2.2