Это как создать объект передачи данных (DTO) с помощью Entity Framework Core и ASP.NET Core MVC 2.2+ и 3.0 - PullRequest
3 голосов
/ 06 мая 2019

При создании RESTful Api с ASP.NET Core MVC 2.2 я заметил, что не было примера DTO, подобного примеру веб-API 2014 года.

ASP.NET Core MVC 2.2 Пример использования API 2019

Пример ASP.NET web-api 2014

Итак, я решил создать DTO для нескольких моих команд-контроллеров HTTPGet, HTTPPost и HTTPPut

У меня есть два вопроса из моего окончательного результата.

  1. Это рекомендуемый способ сделать это в общем смысле. Или в новом ядре Entity Framework есть что-то, что отличается или лучше, чем в примере 2014 года, который был основан на Entity Framework 6 или предыдущих версиях?

  2. Следует ли вообще использовать шаблон проектирования DTO? Или в Entity Framework Core есть что-то, что отличается от шаблона DTO. В частности, есть ли способ взять данные из базы данных и передать их представлению / клиенту именно так, как мне нужно, чтобы они передавались?

Дополнительные сведения о причине, по которой следует задать вопрос, часть 2. Я читал о том, что DTO является анти-паттернами, и люди говорят, что не используют их по той или иной причине. Тем не менее, многие разработчики умоляют их использовать и когда и почему их следует использовать. Личный пример для меня - работа над проектами Angular и React. Получение данных, которые мне нужны, это прекрасная вещь, которую я не могу представить ни одной другой альтернативой, которая заключалась бы в том, чтобы делать все типы обручей и разбора, чтобы пройти через монолитный объект для отображения адреса и местоположения на экране.

Но времена изменились, и существует ли шаблон проектирования или другой шаблон, который будет делать то же самое, но с меньшими затратами и вычислительными затратами.

  1. В связи с этим, существуют ли большие вычислительные затраты для сервера и dbserver для использования этого шаблона?

  2. И наконец, приведенный ниже код позволяет ожидать использования шаблона DTO в Entity Framework Core в отличие от EF 6 или linq to sql framework?

Я включил изменения кода ниже, чтобы проиллюстрировать мое создание DTO для модели TodoItem из нижеприведенного упражнения.

Проект (TodoApi) -> DTOs -> TodoItemDTO.cs:

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace TodoApi.Models
{
    public class TodoItemDTO
    {
        [Required]
        public string Names { get; set; }

        [DefaultValue(false)]
        public bool IsCompletes { get; set; }
    }

    public class TodoItemDetailDTO
    {
        public long Id { get; set; }

        [Required]
        public string Names { get; set; }

        [DefaultValue(false)]
        public bool IsCompletes { get; set; }
    }
}

Проект (TodoApi) -> Контроллеры -> TodoController.cs:

using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860

namespace TodoApi.Controllers
{
    [Produces("application/json")]
    [Route("api/[controller]")]
    [ApiController]
    public class TodoController: ControllerBase
    {
        private readonly TodoContext _context;

        public TodoController(TodoContext context)
        {
            _context = context;

            if (_context.TodoItems.Count() == 0)
            {
                // Create a new TodoItem if collection is empty, 
                // which means you can't delte all TodoItems.
                _context.TodoItems.Add(new TodoItem { Name = "Item1" });
                _context.SaveChanges();
            }

            // Console.WriteLine(GetTodoItems());
        }

        // Get: api/Todo
        [HttpGet]
        public async Task<ActionResult<IQueryable<TodoItem>>> GetTodoItems()
        {
            var todoItems = await _context.TodoItems.Select(t =>
                        new TodoItemDetailDTO()
                        {
                            Id = t.Id,
                            Names = t.Name,
                            IsCompletes = t.IsComplete
                        }).ToListAsync();

            return Ok(todoItems);

            // previous return statement
            //return await _context.TodoItems.ToListAsync();
        }

        // Get: api/Todo/5
        [HttpGet("{id}")]
        [ProducesResponseType(typeof(TodoItemDetailDTO), 201)]
        public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
        {
            var todoItem = await _context.TodoItems.Select(t =>
            new TodoItemDetailDTO()
            {
                Id = t.Id,
                Names = t.Name,
                IsCompletes = t.IsComplete
            }).SingleOrDefaultAsync(t => t.Id == id);

            if (todoItem == null)
            {
                return NotFound();
            }

            return Ok(todoItem);

            //var todoItem = await _context.TodoItems.FindAsync(id);

            //////if (todoItem == null)
            //{
            //    return NotFound();
            //}

            //return todoItem;
        }

        // POST: api/Todo
        /// <summary>
        /// Creates a TodoItem.
        /// </summary>
        /// <remarks>
        /// Sample request:
        ///
        ///     POST /Todo
        ///     {
        ///        "id": 1,
        ///        "name": "Item1",
        ///        "isComplete": true
        ///     }
        ///
        /// </remarks>
        /// <param name="item"></param>
        /// <returns>A newly created TodoItem</returns>
        /// <response code="201">Returns the newly created item</response>
        /// <response code="400">If the item is null</response>            
        [HttpPost]
        [ProducesResponseType(typeof(TodoItemDTO), 201)]
        [ProducesResponseType(typeof(TodoItemDTO), 400)]
        public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem item)
        {
            _context.TodoItems.Add(item);
            await _context.SaveChangesAsync();

            _context.Entry(item).Property(x => x.Name);
            var dto = new TodoItemDTO()
            {
                Names = item.Name,
                IsCompletes = item.IsComplete
            };

            // didn't use because CreatedAtAction Worked
            // return CreatedAtRoute("DefaultApi", new { id = item.Id }, dto);

            return CreatedAtAction(nameof(GetTodoItem), new { id = item.Id }, dto);

            // original item call for new todoitem post
            //return CreatedAtAction(nameof(GetTodoItem), new { id = item.Id }, item);
        }

        // PUT: api/Todo/5
        [HttpPut("{id}")]
        [ProducesResponseType(typeof(TodoItemDTO), 201)]
        [ProducesResponseType(typeof(TodoItemDTO), 400)]
        public async Task<IActionResult> PutTodoItem(long id, TodoItem item)
        {
            if (id != item.Id)
            {
                return BadRequest();
            }

            _context.Entry(item).State = EntityState.Modified;
            await _context.SaveChangesAsync();

            var dto = new TodoItemDTO()
            {
                Names = item.Name,
                IsCompletes = item.IsComplete
            };

            return CreatedAtAction(nameof(GetTodoItem), new { id = item.Id }, dto);
        }

        // DELETE: api/Todo/5
        [HttpDelete("{id}")]
        public async Task<IActionResult> DeleteTodoItem(long id)
        {
            var todoItem = await _context.TodoItems.FindAsync(id);

            if (todoItem == null)
            {
                return NotFound();
            }

            _context.TodoItems.Remove(todoItem);
            await _context.SaveChangesAsync();

            return NoContent();
        }
    }
}

1 Ответ

1 голос
/ 06 мая 2019

Я думаю, вы слишком зациклены на семантике.Строго говоря, «сущность» - это просто объект с идентичностью (т. Е. Имеет идентификатор), в отличие от чего-то вроде «объекта ценности».Entity Framework (Core или нет) - это объект / реляционный преобразователь (ORM), который абстрагирует постоянство объекта.«Сущность», передаваемая в EF, представляет собой класс, который представляет объект на уровне постоянства (то есть строку в конкретной таблице).Это все.

Однако, как таковое, это часто не невероятно полезно в других сценариях.SRP (принцип единственной ответственности) в значительной степени диктует, что субъект должен заниматься только фактическим материалом, который важен для настойчивости.Потребности в обработке определенного запроса, предоставлении определенного представления данными и т. Д. Могут и будут отличаться от этого, что означает, что вам либо нужно заставить класс сущности делать слишком много, либо вам нужны дополнительные классы специально для этих целей.Вот где в игру вступают такие понятия, как DTO, модели представлений и т. Д.

Короче говоря, правильная вещь - это использовать то, что имеет смысл в определенных обстоятельствах.Если вы имеете дело с API CRUD-типа, возможно, имеет смысл использовать класс сущностей непосредственно в этом сценарии.Однако чаще всего даже в сценарии CRUD обычно предпочтительнее иметь собственный класс, к которому привязывается тело запроса.Это позволяет вам управлять такими вещами, как сериализация и какие свойства можно просматривать, редактировать и т. Д. В некотором смысле вы отделяете API от уровня персистентности, позволяя им работать независимо друг от друга.

ДляНапример, предположим, вам нужно изменить имя свойства вашей сущности.Если ваш API использует сущность напрямую, то для этого потребуется управление версиями API и отказ от предыдущей версии со старым именем свойства.Используя отдельный класс для каждого, вы можете просто изменить слой отображения, и API, к счастью, не знает этого.Клиенты, взаимодействующие с API, не требуют никаких изменений.Как правило, вы всегда хотите идти по пути наименьшей связи между компонентами.

...