现在,我们的 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 )。