建立資料傳輸物件 (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。