現在、Web API はデータベース エンティティをクライアントに公開しています。 クライアントは、データベース テーブルに直接マップされるデータを受け取ります。 ただし、これは常に良い考えであるとは限りません。 クライアントに送信するデータの形状を変更したい場合があります。 たとえば、次のようなことをしたいかもしれません:
- 循環参照を削除します (前のセクションを参照)。
- クライアントが表示しない特定のプロパティを非表示にします。
- ペイロード サイズを減らすために、いくつかのプロパティを省略する。
- 入れ子になったオブジェクトを含むオブジェクト グラフをフラット化して、クライアントにとってより便利にします。
- "過剰投稿" の脆弱性を回避します。 (オーバーポスティングの詳細については、 モデル 検証を参照してください)。
- サービス レイヤーをデータベース レイヤーから切り離します。
これを実現するには、 データ転送オブジェクト (DTO) を定義します。 DTO は、ネットワーク経由でのデータの送信方法を定義するオブジェクトです。 Book エンティティでの動作を見てみましょう。 Models フォルダーに、次の 2 つの 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 クラスの 2 つの 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]
最後に、DTO を返すように PostBook メソッドを変更します。
[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 に手動で変換します。 もう 1 つのオプションは、変換を自動的に処理する AutoMapper などのライブラリを使用することです。