创建数据传输对象 (DTO)
现在,Web API 向客户端公开数据库实体。 客户端接收直接映射到数据库表的数据。 但是,这并不总是一个好主意。 有时需要更改发送到客户端的数据的形状。 例如,您可能希望:
- 删除循环引用 (请参阅上一部分) 。
- 隐藏客户端不应查看的特定属性。
- 省略一些属性以缩减有效负载大小。
- 平展包含嵌套对象的对象图,使其更方便客户端。
- 避免“过度发布”漏洞。 (有关 over-posting 的讨论,请参阅 模型验证 。)
- 将服务层与数据库层分离。
为此,可以 (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 )。