Создание объектов передачи данных (DTO)Create Data Transfer Objects (DTOs)

по Майк Уоссонby Mike Wasson

Скачать завершенный проектDownload Completed Project

Сейчас наш веб-API предоставляет клиенту сущности базы данных.Right now, our web API exposes the database entities to the client. Клиент получает данные, которые непосредственно сопоставляются с таблицами базы данных.The client receives data that maps directly to your database tables. Однако это не всегда хорошая идея.However, that's not always a good idea. Иногда требуется изменить форму данных, отправляемых клиенту.Sometimes you want to change the shape of the data that you send to client. Например, может понадобиться:For example, you might want to:

  • Удалите циклические ссылки (см. предыдущий раздел).Remove circular references (see previous section).
  • Скрыть определенные свойства, которые не должны просматривать клиенты.Hide particular properties that clients are not supposed to view.
  • Чтобы уменьшить размер полезной нагрузки, пропустите некоторые свойства.Omit some properties in order to reduce payload size.
  • Сведение графов объектов, содержащих вложенные объекты, чтобы сделать их более удобными для клиентов.Flatten object graphs that contain nested objects, to make them more convenient for clients.
  • Избегайте перебора уязвимостей.Avoid "over-posting" vulnerabilities. (Дополнительные сведения см. в статье Проверка модели .)(See Model Validation for a discussion of over-posting.)
  • Отделяйте слой служб от уровня базы данных.Decouple your service layer from your database layer.

Для этого можно определить объект передачи данных (DTO).To accomplish this, you can define a data transfer object (DTO). DTO — это объект, который определяет, как данные будут передаваться по сети.A DTO is an object that defines how the data will be sent over the network. Давайте посмотрим, как это работает с сущностью Book.Let's see how that works with the Book entity. В папке Models добавьте два класса DTO:In the Models folder, add two DTO classes:

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 является строкой, в которой будет содержаться имя автора.The BookDetailDto class includes all of the properties from the Book model, except that AuthorName is a string that will hold the author name. Класс BookDto содержит подмножество свойств из BookDetailDto.The BookDto class contains a subset of properties from BookDetailDto.

Затем замените два метода GET в классе BooksController на версии, возвращающие DTO.Next, replace the two GET methods in the BooksController class, with versions that return DTOs. Мы будем использовать инструкцию LINQ SELECT для преобразования сущностей Book в DTO.We'll use the LINQ Select statement to convert from Book entities into DTOs.

// 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);
}

Ниже приведен SQL, созданный с помощью нового метода GetBooks.Here is the SQL generated by the new GetBooks method. Видно, что EF преобразует LINQ SELECT в инструкцию SQL SELECT.You can see that EF translates the LINQ Select into a SQL SELECT statement.

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.Finally, modify the PostBook method to return a 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);
}

Note

В этом руководстве мы преобразуемся в DTO вручную в коде.In this tutorial, we're converting to DTOs manually in code. Другой вариант — использовать библиотеку, например автосопоставитель , которая автоматически обрабатывает преобразование.Another option is to use a library like AutoMapper that handles the conversion automatically.