这是如何使用 Entity Framework Core 和 ASP.NET Core MVC 2.2+ 和 3.0 创建数据传输对象 (DTO)

Chr*_*hew 9 c# asp.net dto entity-framework-core asp.net-core

在使用 ASP.NET Core MVC 2.2 创建 RESTful Api 时,我注意到没有像 2014 web api 示例那样的 DTO 示例。

ASP.NET Core MVC 2.2 Rest api 2019 示例

ASP.NET web-api 2014 示例

所以,我决定为我的一些控制器动词 HTTPGet、HTTPPost 和 HTTPPut 创建 DTO

我的最终结果有 2 个折叠问题。

  1. 这是一般意义上的推荐方法吗?或者,新的 Entity Framework Core 中是否有与基于 Entity Framework 6 或更早版本的 2014 示例不同或更好的内容?

  2. 一般应该使用 DTO 设计模式吗?或者 Entity Framework Core 中是否有与 DTO 模式完全不同的东西。具体来说,有没有办法从数据库中获取数据并将其传递给视图/客户端,就像我需要传递的确切方式一样?

提出问题的原因的更多背景第 2 部分。我已经阅读过有关 DTO 是反模式的内容,人们说出于某种原因不要使用它们。然而,许多开发人员恳求他们的使用以及何时以及为什么应该使用它们。我个人的一个例子是工作和 Angular 和 React 项目。接收我需要的数据是一件美妙的事情,我无法想象任何其他替代方案,即执行所有类型的箍和解析以通过整体对象在屏幕上显示地址和位置。

但是时代变了,是否有一种设计模式或另一种模式可以做完全相同的事情,但费用和计算成本更低。

  1. 就此而言,使用这种模式对服务器和 dbserver 有很大的计算成本吗?

  2. 最后,下面的代码是如何期望在 Entity Framework Core 中使用 DTO 模式而不是 EF 6 或 linq to sql 框架的?

我已经包含了下面的代码更改,以说明我在下面的练习中为 TodoItem 模型创建的 DTO。

项目(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; }
    }
}
Run Code Online (Sandbox Code Playgroud)

项目(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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Chr*_*att 12

我认为你太关注语义了。严格来说,“实体”只是一个具有身份(即具有标识符)的对象,与“值对象”之类的东西相反。实体框架(Core 或无)是抽象对象持久性的对象/关系映射器 (ORM)。提供给 EF 的“实体”是一个类,它表示持久层中的一个对象(即特定表中的一行)。就这些。

但是,因此,它在其他场景中通常不是非常有用。SRP(单一职责原则)几乎规定实体应该只关注对持久性很重要的实际内容。处理特定请求、为特定视图提供数据等的需求可能并且将会与此不同,这意味着您要么需要让实体类做太多事情,要么需要专门用于这些目的的其他类。这就是 DTO、视图模型等概念发挥作用的地方。

简而言之,正确的做法是使用在特定情况下有意义的东西。如果您正在处理 CRUD 类型的 API,则在该场景中直接使用实体类可能是有意义的。但是,通常情况下,即使在 CRUD 的情况下,通常仍然最好使用自定义类来绑定请求正文。这允许您控制诸如序列化之类的事情以及哪些属性是可见的、可编辑的等。从某种意义上说,您将 API 与持久层分离,允许两者相互独立工作。

例如,假设您需要更改实体的属性名称。如果您的 API 直接使用实体,那么这将需要对 API 进行版本控制并使用旧属性名称处理旧版本的弃用问题。为每个类使用单独的类,您可以简单地更改映射层,而 API 会愉快地不知道。客户端与 API 交互不需要任何更改。作为一般规则,您希望始终追求组件之间最小耦合的路径。