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

Posted

技术标签:

【中文标题】这是如何使用 Entity Framework Core 和 ASP.NET Core MVC 2.2+ 和 3.0 创建数据传输对象 (DTO)【英文标题】:Is This How to Create a Data Transfer Object (DTO) with Entity Framework Core & ASP.NET Core MVC 2.2+ and 3.0 【发布时间】:2019-09-23 17:52:14 【问题描述】:

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

ASP.NET Core MVC 2.2 Rest api 2019 example

ASP.NET web-api 2014 example

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

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

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

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

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

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

    就此而言,服务器和 dbserver 使用这种模式是否会产生很大的计算成本?

    最后,与 EF 6 或 linq to sql 框架相比,下面的代码是否期望在 Entity Framework Core 中使用 DTO 模式?

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

Project(TodoApi) --> DTO --> 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; 
    

Project(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();
        
    

【问题讨论】:

Entity 在 c# 中创建映射到数据库的类。映射只需要完成一次,而不是在代码中。您通常可以使用菜单在 VS 中创建类/映射。 2014 链接底部的参考资料提供了进行映射的工具。由于映射只需要完成一次(或者当数据库表架构发生变化时),我建议使用 VS 菜单映射到数据库。 @jdweng 不完全关注我是 C# 新手。你能举个例子来说明你在说什么。页面底部的工具用于面向 EF6 的项目,这就是为什么我不愿深入研究的原因。另外,VS 使用菜单到底是什么意思? 我指的是自动映射器,它会自动从数据库中创建 c# 类。 @jdweng 是的,但那是为 EF6 而不是 EFCore 并且为每个创建类到底是什么?如果我需要更改所看到的内容和/或命名约定,那将有什么帮助 EFCore 是 Entity 的轻量级版本。映射是实体。您不能同时使用实体和 DTO。请参阅:driver733.com/2018/10/08/entity-and-dto.html 【参考方案1】:

我认为您对语义过于依赖了。严格来说,“实体”只是一个具有身份的对象(即具有标识符),与“值对象”之类的东西形成对比。 Entity Framework(Core or no)是一个抽象对象持久性的对象/关系映射器(ORM)。提供给 EF 的“实体”是一个类,它表示持久层中的一个对象(即特定表中的一行)。仅此而已。

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

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

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

【讨论】:

感谢您的回复。根据我对您的回答的理解,如果有必要,那么可以使用它......而且解耦方面也是一个好处。除了您的回答之外,您能否直接详细说明我的问题。如果您不介意,这将有助于解决问题并对我的询问非常有用。此外,归根结底,系统学是我们理解事物的方式。 在这里,语义模糊了问题。在一天结束时,你有课。你怎么称呼它们最终是无关紧要的。您可以有一个用于持久性的实体和一个用于 HTTP 传输的实体,它们不一定需要是相同的东西。就像我说的,实体只是意味着它有一个标识符。不要专注于术语,专注于应用程序的需求和良好的关注点分离。 但语义适用于所有意图,并且与设计模式相关。一种与您描述的模式不同的模式。 DTO 设计模式是您实现此目标的方式吗?有没有更好的设计模式。我正在寻找 C# 世界中的最佳实践。我最近负责一个 java 项目的 DTO,因为它是 java 中一种大量使用的设计模式。我根本不担心语义。我只是想从模型中抽象出来,并创建一个返回到按我想要的方式获得的视图。我试图确认这是一个很好的设计模式 嗨,我听说一些 php 框架允许您对定义了女巫属性的实体进行数据注释,作为回报应该是可见的。我相信他们称它为序列化程序和那些数据注释序列化程序组。 .Net 或 Core 中是否存在类似的东西?

以上是关于这是如何使用 Entity Framework Core 和 ASP.NET Core MVC 2.2+ 和 3.0 创建数据传输对象 (DTO)的主要内容,如果未能解决你的问题,请参考以下文章

在entity framework中怎么调用存储过程

如何将 C# 8.0 可空引用类型与 Entity Framework Core 模型一起使用?

C# Entity-Framework:如何在模型对象上组合 .Find 和 .Include?

如何使用 Entity Framework + Mysql(在控制台应用程序中)

如何将 Entity Framework 4.1 与 MVC 3 登录一起使用

C# Entity Framework Code-First-如何仅使用该外键的 id 添加带有外键的行?