DTO(데이터 전송 개체) 만들기

완료된 프로젝트 다운로드

현재 웹 API는 데이터베이스 엔터티를 클라이언트에 노출합니다. 클라이언트는 데이터베이스 테이블에 직접 매핑되는 데이터를 받습니다. 그러나 항상 좋은 생각은 아닙니다. 클라이언트에 보내는 데이터의 모양을 변경하려는 경우가 있습니다. 예를 들어, 다음을 수행합니다.

  • 순환 참조를 제거합니다(이전 섹션 참조).
  • 클라이언트에서 볼 수 없는 특정 속성을 숨깁니다.
  • 페이로드 크기를 줄이기 위해 일부 속성을 생략합니다.
  • 중첩된 개체가 포함된 개체 그래프를 평면화하여 클라이언트에 더 편리하게 만듭니다.
  • "초과 게시" 취약성을 방지합니다. (초과 게시에 대한 논의는 모델 유효성 검사를 참조하세요.)
  • 서비스 계층을 데이터베이스 계층과 분리합니다.

이를 위해 DTO( 데이터 전송 개체 )를 정의할 수 있습니다. DTO는 네트워크를 통해 데이터를 보내는 방법을 정의하는 개체입니다. Book 엔터티에서 작동하는 방식을 살펴보겠습니다. Models 폴더에 두 개의 DTO 클래스를 추가합니다.

namespace BookService.Models
{
    public class BookDto
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string AuthorName { get; set; }
    }
}

namespace BookService.Models
{
    public class BookDetailDto
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public int Year { get; set; }
        public decimal Price { get; set; }
        public string AuthorName { get; set; }
        public string Genre { get; set; }
    }
}

클래스에는 BookDetailDto 작성자 이름을 보유할 문자열을 AuthorName 제외하고 Book 모델의 모든 속성이 포함됩니다. 클래스는 BookDto 의 속성 하위 집합을 BookDetailDto포함합니다.

다음으로 클래스의 두 GET 메서드를 BooksController DTO를 반환하는 버전으로 바꿉 있습니다. LINQ Select 문을 사용하여 Book 엔터티에서 DTO로 변환합니다.

// GET api/Books
public IQueryable<BookDto> GetBooks()
{
    var books = from b in db.Books
                select new BookDto()
                {
                    Id = b.Id,
                    Title = b.Title,
                    AuthorName = b.Author.Name
                };

    return books;
}

// GET api/Books/5
[ResponseType(typeof(BookDetailDto))]
public async Task<IHttpActionResult> GetBook(int id)
{
    var book = await db.Books.Include(b => b.Author).Select(b =>
        new BookDetailDto()
        {
            Id = b.Id,
            Title = b.Title,
            Year = b.Year,
            Price = b.Price,
            AuthorName = b.Author.Name,
            Genre = b.Genre
        }).SingleOrDefaultAsync(b => b.Id == id);
    if (book == null)
    {
        return NotFound();
    }

    return Ok(book);
}

다음은 새 GetBooks 메서드에서 생성된 SQL입니다. EF가 LINQ Select 를 SQL SELECT 문으로 변환하는 것을 볼 수 있습니다.

SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Title] AS [Title], 
    [Extent2].[Name] AS [Name]
    FROM  [dbo].[Books] AS [Extent1]
    INNER JOIN [dbo].[Authors] AS [Extent2] ON [Extent1].[AuthorId] = [Extent2].[Id]

마지막으로 DTO를 PostBook 반환하도록 메서드를 수정합니다.

[ResponseType(typeof(BookDto))]
public async Task<IHttpActionResult> PostBook(Book book)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    db.Books.Add(book);
    await db.SaveChangesAsync();

    // New code:
    // Load author name
    db.Entry(book).Reference(x => x.Author).Load();

    var dto = new BookDto()
    {
        Id = book.Id,
        Title = book.Title,
        AuthorName = book.Author.Name
    };

    return CreatedAtRoute("DefaultApi", new { id = book.Id }, dto);
}

참고

이 자습서에서는 코드에서 DTO로 수동으로 변환합니다. 또 다른 옵션은 자동 변환을 자동으로 처리하는 AutoMapper 와 같은 라이브러리를 사용하는 것입니다.