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 Like

일반적으로 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 Likes

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

{
“name”: “hoon”
}

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

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

1 Like

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

1 Like

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

1 Like

예, 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 Likes

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

1 Like