ParentDataModel는 주 엔터티가 되고 ChildA, ChildB, ChildAa는 종속 엔터티가 됩니다.
별다른 키를 특성으로 표현하지 않았다면 Id나 [Class이름]Id의 이름으로 키 속성을 표현할 수 있습니다. 그리고 탐색 속성에 의해 엔터티 간의 관계가 형성됩니다.
ChildA와 ChildB가 같이 이어지게 표현하는것을 의미하나요? 그렇다면 아래와 같이 탐색 속성으로 표현할 수 있습니다.
public class ChildA : IEntity
{
....
public virtual ChildB ChildB { get; set; }
}
public class ChildB : IEntity
{
...
public virtual ChildA ChildA { get; set; }
}
그리고 탐색 속성을 통해 결과를 확인할 수 있습니다.
var info = context.ParentDataModels.Where(x => x.Id = 10);
foreach (var childA in info.ChildAs) { ... }
foreach (var childB in info.ChildBs) { ... }
탐색 속성에 의해서 관계를 따라갈 수 있으므로 이렇게 질의도 가능합니다.
var result = context.ParentDataModels.Where(x => x.Id = 10).ChildAs
.First()
.ChildB;
List형식의 자식이 제 관점에서는 맞지 않는 표현 같습니다. 자식이 아니라 엔터티 관계상의 주/종 관계일 뿐입니다. 예를들어 사용자정보가 있고 사용자로그인이력이 있다면 사용자정보는 주 엔터티이고 사용자로그인이력은 종 엔터티 입니다. 이것을 탐색 속성으로 표현할 수 있겠고요, 사용자로그인이력은 사용자정보 관점에서 일대다이므로 목록으로 표현됩니다.
이렇게 관계가 형성되었다는 의미는 관계가 형석되었다는 것이고 그 관계는 ID로 식별하므로… 주 엔터티 ID로 이어지게 됩니다.
efcore는 엔티티의 상태를 보고 추가할지 업데이트할지를 판단합니다.
Parent를 새로 생성하셨고, Child의 Collection도 새로 생성하셨으면 둘다 insert 가 발생 됩니다.
Query를 통해 조회된 내용을 바탕으로 Child의 특정 index의 속성을 변경하셨으면 업데이트가 발생 되실 것 같아요.
그나저나 모델에 기본키 특성이 없어서 Id 값에 제대로 매핑이 안될 것 같은데 DbContext 쪽에 선언하셨나 모르겠네요.
public class Entity
{
[Key]
[Column(Order = 0)]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get;set; }
}
public class Customer : Entity
{
public virtual ICollection<Order> Orders { get; set; } = new List<Order>();
}
public class Order : Entity
{
[Required]
public int CustomerId { get; set; }
[Required]
[ForeignKey(nameof(CustomerId))]
public Customer Customer { get; set; }
public virtual ICollection<OrderDetail> Details { get; set; } = new List<OrderDetail>();
}
public class OrderDetail : Entity
{
[Required]
public int OrderId { get; set; }
[Required]
[ForeignKey(nameof(OrderId))]
public Order Order { get; set; }
}
// See https://aka.ms/new-console-template for more information
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection;
using Microsoft.EntityFrameworkCore;
await using var context = new CustomerContext();
var customer = new Customer { Name = "hoya" };
var product = new Product { Customer = customer, Name = "p1" };
var jobDetail = new JobDetail { Product = product, Name = "j1"};
product.JobDetails.Add(jobDetail);
customer.Products.Add(product);
context.Add(customer);
await context.SaveChangesAsync().ConfigureAwait(false);
public class Entity
{
[Key]
[Column(Order = 0)]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get;set; }
}
public class Customer : Entity
{
[Required]
public string Name { get; set; }
public virtual ICollection<Product> Products { get; set; } = new List<Product>();
}
public class Product : Entity
{
[Required]
public int CustomerId { get; set; }
[Required]
[ForeignKey(nameof(CustomerId))]
public Customer Customer { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<JobDetail> JobDetails { get; set; } = new List<JobDetail>();
}
public class JobDetail : Entity
{
[Required]
public int ProductId { get; set; }
[Required]
public string Name { get; set; }
[Required]
[ForeignKey(nameof(ProductId))]
public Product Product { get; set; }
}
public class CustomerContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<JobDetail> JobDetails { get; set; }
public string DbPath { get; }
public CustomerContext()
{
var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
DbPath = System.IO.Path.Join(path, "customer.db");
}
// The following configures EF to create a Sqlite database file in the
// special "local" folder for your platform.
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlite($"Data Source={DbPath}");
}
잘 되는거 같은데 어떤 점이 다를까요?
sqlite 상에서 foreign key도 잡혀 있네요.
추가 하실 때 parent가 되는 Customer나 Product에 인스턴스가 설정 되어 있는지 확인해 보시는 것도 좋을 것 같네요.
아 네네네 ! 아주 잘되고 있습니다.
instance 설정이안되어 있어서 안들어갔었습니다 !! ㅎㅎ
감사합니다.
추가적으로 궁금한것이 있는데
ModelConfiguration 에서 관계를 형성 짓는 방법이 따로 없는듯 해서
막연하게 따라하긴 했지만 조금 더 세부적으로 다시 한번 알려주실 수 있을까요 …?
특히 이해안가는 부분이 이전에 1:N 방식을 했을때는
[Key]
[Column(Order = 0)]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[ForeignKey(nameof(ProductId))]
요런 문구를 사용하지 않고 사용했었습니다.
제 눈치로는 저 attribute 가 관계 형성을 이어주는것 같은데 아닐까요…?
int형 속성에 Key 특성을 주고 DatabaseGeneratedOption.Identity를 적용했으니 AUTO INCREMENT가 된다고 보시면 됩니다.
그 외에도 DateTime형 속성에 줄수도 있고, DatabaseGeneratedOption.Computed 값으로 다른 형태의 시나리오도 적용하실 수 있습니다. 댓글로 모두 알려드리기는 어려워 관련 시나리오 발생시 관련 문서를 찾아 보시면 좋을 것 같네요.
ForeignKey 특성
이 특성이 1:N을 만드는 특성입니다.
Parent 클래스에 Child 클래스의 ICollection 속성을 선언하고, Child 클래스에 Parent 속성을 선언하시면 기본 동작합니다.
그래서 예제 코드를 보시면, Customer 클래스에 ICollection Products 속성이 있고, Product 클래스에는 Customer 속성이 선언되어 있습니다.
public class Customer : Entity
{
[Required]
public string Name { get; set; }
public virtual ICollection<Product> Products { get; set; } = new List<Product>();
}
public class Product : Entity
{
[Required]
public int CustomerId { get; set; }
[Required]
[ForeignKey(nameof(CustomerId))]
public Customer Customer { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<JobDetail> JobDetails { get; set; } = new List<JobDetail>();
}
결론은 1,2번은 관계 설정과 연관이 없고, 3번이 관계 설정과 연관이 있습니다.
아 그리고 특성을 선언하는 것 외에 @suwoo 님이 언급하신 것처럼 ModelConfiguration 에서 FluentAPI로 관계를 설정하실 수 있습니다.