共用方式為


建立資料傳輸物件 (DTO)

下載已完成的專案

現在,我們的 Web 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 類別包含 Book 模型的所有屬性,除了 AuthorName 是將保存作者名稱的字串。 BookDto 類別包含 BookDetailDto 屬性的子集。

接下來,將 BooksController 類別中的兩個 GET 方法替換為傳回 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]

最後,修改 PostBook 方法以傳回 DTO。

[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