ASP.NET CORE MVC에서는 외래키를 어떻게 사용해야하나요?

우선 검색을 하여 모델에 [ForeignKey] 애트리뷰트를 이용하여

[ForeignKey(“user_id”)]
public User user {get; set;} 이렇게 선얼을 한 후 마이그레이션을 하니간

데이터베이스에는 user_id라는 int 형의 외래키의 컬럼이 생성 된 것 같습니다.

그런데 여기서 혼동이 오는 부분이 앱에서 api를 호출하면서 POST로 json 형태로 넘겨주면 위의 모델이 매게변수로 있고 [FromBody]로 되어 있어서 자동으로 해당 매개변수에 매핑이 되게 하려고하는데요, 이 때 데이터베이스에는 int 형태의 외래키로 되어있고 DbSet의 해당 모델은 User라는 클래스의 참조 형태로 되어있는데, 그럼 앱에서 Json으로 넘겨줄 때는 어떤식으로 념겨줘야하나요?

여기서 혼동이 와서 애초에 사용법 자체가 틀린건지 의문이 생겨 여쭤봅니다.

1개의 좋아요

일반적으로 client - api - DB 로 설계되는 three tier 시스템에서는,

db 에 대한 접근은 오로지 api 에게만 허용되고, DB는 외부로 노출되지 않습니다.

api 가 DB 에 접근할 때는 생쿼리 또는 DbContext 중 하나를 선택할 수 있습니다.

클라이언트(프론트 엔드, 데스크탑 앱, 모바일 앱)는 api 를 통해서만 데이터를 얻을 수 있기에, Http 만 있지, 생쿼리도, DbContext도 개입할 여지가 없습니다.

클라이언트와 api 는 Http 를 통해 json 데이터를 주고 받는데, serialization 을 통해 인스턴스를 생성합니다. 이때, json 과 맵핑되는 데이터 모델이 별도로 필요하게 됩니다.

namespace MyProject.Core.Entities;
public class MyDataEntity
{
   // ...
   [ForeignKey(“user_id”)] public User? user {get; set;} 
   // ...
}
namespace MyProject.Api.Contracts;
public class MyDataInsertion
{
   // ...
   public Guid UserId { get; set; }
   // ...
}
// WPF 앱
using MyProject.Api.Contracts;

namespace MyProject.Client.Windows.Services;

class MyDataService
{
   private HttpClient _httpClinet;

   // ...
   
   public async Task Add(MyDataInsertion data)
   {
       // data 를 json 객체로 serializaiton 한 다음,
       // 요청의 바디 또는 form data 로 api 에 Post 합니다. 
   }
}
// Wep Api Program.cs
using MyProject.Api.Contracts;
using MyProject.Core.Entities;

namespace MyProject.Api;
// ...
app.MapPost("/api/mydata", 
   async ([FromBody] MyDataInsertion data, [FromServices] AppDbContext context) =>
{
   var user = await context.Users
                   .FirstOrDefaultAsync(x => x.User.Id == data.UserId);

   if (user is null) return Results.BadRequest();

   // MyData => MyDataEntity 로 맵핑
   var entity = new MyDataEntity 
   {
      User = user,
      // ...
   };

   context.MyDataEntities.Add(entity);
   context.SaveChanges();
   reutrn Results.Ok();
});
// ...

Api.Contracts 에 속한 데이터 모델들은 API가 외부(인터넷으)로 천명하는 계약이라, 신중하게 설계해야 합니다. 런칭 후 변경할 일이 있다면, 모델을 수정하는 것보다, Api.V2.Contracts 와 같이 별도의 네임스페이스에 새로 추가하는 것이 좋습니다.

객체간 매핑은 별도의 맵퍼를 통해 처리하는 것이 코드 관리 측면에서 유리합니다.

5개의 좋아요

아, 제가 이제 막 배우고 있어서 잘 이해를 못하는건지도 모르겠는데요. 제가 하려는 방식은

{
“name”: “hoon”
}

이렇게 json으로 만들어서 이걸 body에 실어서 보내려고 하는데요. 이 때 다른 필드는 그냥 있는 그대로 넣으면 되는데

외래키 부분이 데이터베이스는 int 형태고 모델에는 객체 참조형태이고 이렇게 다를 경우 어떤 거에 맞춰 json 에 key:value 형태로 필드를 넣어야 할지 혼동이 와서 여쭤봤습니다.

1개의 좋아요

엔티티 클래스와 MVC의 모델을 혼용하지 않으면 - 별도로 정의하면 됩니다.

1개의 좋아요

아하! 우선 말씀하시는 바는 이해했습니다.
그런데 검색을 좀 해보니간 DTO라는게 있던데, 제가 볼 때는 위에 예제에서 정보를 받기 위해 사용한 MyDataInsertion이 DTO랑 다를게 없어 보이던데요.
제가 맞게 이해한게 맞는건가요? 즉, DTO를 사용해도 되는건가요?

1개의 좋아요

예, DTO의 개념입니다.

그런데, DTO로 한정하지 마시고, 외부에 노출하는 계약으로 보는 게 좋습니다.
계약을 모델로 정의할 수도 있고, 아래와 같이 API로도 제공할 수 있기 때문입니다.

// api 레이어가 제공하는 메서드 형식의 계약

namespace MyProject.Api.Contracts;

public interface IMyDataService
{
   Task AddMyData(string name, string userId, ...);
}

internal class MyDataService : IMyDataService
{

   private HttpClient _httpClinet;

   // ...
   
   public async Task Add(string name, string userId, ...)
   {
       // data 를 json 객체로 serializaiton 한 다음,
       // 요청의 바디 또는 form data 로 api 에 Post 합니다. 
   }
}

public static class IServiceCollectionEntensions
{
    public static IServiceCollection AddApiServices(this IServiceCollection services)
   {
      services.AddTransients<IMyDataService, MyDataService>();
      return services;
   }
}
2개의 좋아요

아하, 예시까지 들어주시고 너무 상세한 설명 덕에 이해가 됐습니다!
상세한 답변 감사합니다!

1개의 좋아요