Chapter11:WORKING WITH PUT REQUESTS

11 WORKING WITH PUT REQUESTS

In this section, we are going to show you how to update a resource using the PUT request. We are going to update a child resource first and then we are going to show you how to execute insert while updating a parent resource.
在本节中,我们将向您展示如何使用 PUT 请求更新资源。我们将首先更新子资源,然后向您展示如何在更新父资源时执行插入。

11.1 Updating Employee

In the previous sections, we first changed our interface, then the repository class, and finally the controller. But for the update, this doesn’t have to be the case.
在前面的部分中,我们首先更改了接口,然后更改了存储库类,最后更改了控制器。但是对于更新,情况不一定如此。

Let’s go step by step.
让我们一步一步来。

The first thing we are going to do is to create another DTO class for update purposes:
我们要做的第一件事是创建另一个 DTO 类以进行更新:

//EmployeeForUpdateDto.cs

namespace Entities.DataTransferObjects
{
    public class EmployeeForUpdateDto
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public string Position { get; set; }
    }
}

We do not require the Id property because it will be accepted through the URI, like with the DELETE requests. Additionally, this DTO contains the same properties as the DTO for creation, but there is a conceptual difference between those two DTO classes. One is for updating and the other is for creating. Furthermore, once we get to the validation part, we will understand the additional difference between those two.
我们不需要 Id 属性,因为它将通过 URI 接受,就像 DELETE 请求一样。此外,此 DTO 包含与用于创建的 DTO 相同的属性,但这两个 DTO 类之间存在概念差异。一个用于更新,另一个用于创建。此外,一旦我们进入验证部分,我们将了解这两者之间的其他区别。

Because we have additional DTO class, we require an additional mapping rule:
因为我们有额外的 DTO 类,所以我们需要一个额外的映射规则:

//MappingProfile.cs

CreateMap<EmployeeForUpdateDto, Employee>();

Now, when we have all of these, let’s modify the EmployeesController:
现在,当我们拥有所有这些时,让我们修改员工控制器:

//EmployeesController.cs

    [HttpPut("{id}")]
    public IActionResult UpdateEmployeeForCompany(Guid companyId, Guid id, [FromBody] EmployeeForUpdateDto employee)
    {
        if (employee == null)
        {
            _logger.LogError("EmployeeForUpdateDto object sent from client is null.");
            return BadRequest("EmployeeForUpdateDto object is null");
        }
        var company = _repository.Company.GetCompany(companyId, trackChanges: false);
        if (company == null)
        {
            _logger.LogInfo($"Company with id: {companyId} doesn't exist in the database.");
            return NotFound();
        }
        var employeeEntity = _repository.Employee.GetEmployee(companyId, id, trackChanges: true);
        if (employeeEntity == null)
        {
            _logger.LogInfo($"Employee with id: {id} doesn't exist in the database.");
            return NotFound();
        }
        _mapper.Map(employee, employeeEntity);
        _repository.Save();
        return NoContent();
    }

We are using the PUT attribute with the id parameter to annotate this action. That means that our route for this action is going to be:
api/companies/{companyId}/employees/{id}.

我们将 PUT 属性与 id 参数一起使用来注释此操作。这意味着我们执行此操作的路由将是:
api/companies/{companyId}/employees/{id}。

As you can see, we have three checks in our code and they are familiar to us. But we have one difference. Pay attention to the way we fetch the company and the way we fetch the employeeEntity. Do you see the difference?
如您所见,我们的代码中有三个检查,我们很熟悉它们。但我们有一个区别。注意我们获取公司的方式以及我们获取员工实体的方式。你看出区别了吗?

The trackChanges parameter is set to true for the employeeEntity. That’s because we want EF Core to track changes on this entity. This means that as soon as we change any property in this entity, EF Core will set the state of that entity to Modified.
对于 employeeEntity,trackChanges 参数设置为 true。这是因为我们希望 EF Core 跟踪此实体上的更改。这意味着,只要我们更改此实体中的任何属性,EF Core 就会将该实体的状态设置为“已修改”。

As you can see, we are mapping from the employee object (we will change just the age property in a request) to the employeeEntity — thus changing the state of the employeeEntity object to Modified.
如您所见,我们正在从 employee 对象(我们将只更改请求中的 age 属性)映射到 employeeEntity,从而将 employeeEntity 对象的状态更改为 Modified。

Because our entity has a modified state, it is enough to call the Save method without any additional update actions. As soon as we call the Save method, our entity is going to be updated in the database.
由于我们的实体具有修改状态,因此无需任何其他更新操作即可调用 Save 方法。一旦我们调用 Save 方法,我们的实体就会在数据库中更新。

Finally, we return the 204 NoContent status.
最后,我们返回 204 NoContent 状态。

We can test our action:
我们可以测试我们的操作:

{
    "name":"Sam Raiden",
    "age":25,
    "position":"Software developer"
}

https://localhost:5001/api/companies/C9D4C053-49B6-410C-BC78-2D54A9991870/employees/80ABBCA8-664D-4B20-B5DE-024705497D4A

Alt text

And it works; we get the 204 No Content status.
它有效;我们得到 204 无内容状态。

We can check our executed query through EF Core to confirm that only the Age column is updated:
我们可以通过 EF Core 检查已执行的查询,以确认仅更新了“年龄”列:

Alt text

Excellent.
非常好。

You can send the same request with the invalid company id or employee id. In both cases, you should get a 404 response, which is a valid response to this kind of situation.
您可以使用无效的公司 ID 或员工 ID 发送相同的请求。在这两种情况下,您都应该得到 404 响应,这是对这种情况的有效响应。

Additional note: As you can see, we have changed only the Age property, but we have sent all the other properties with unchanged values as well. Therefore, Age is only updated in the database. But if we send the object with just the Age property, without the other properties, those other properties will be set to their default values and the whole object will be updated — not just the Age column. That’s because the PUT is a request for a full update. This is very important to know.
附加说明: 如您所见,我们只更改了 Age 属性,但我们也发送了具有未更改值的所有其他属性。因此,年龄仅在数据库中更新。但是,如果我们发送的对象只包含 Age 属性,而不发送其他属性,则这些其他属性将设置为其默认值,并且整个对象将被更新 — 而不仅仅是 Age 列。这是因为 PUT 是对完整更新的请求。了解这一点非常重要。

11.1.1 About the Update Method from the RepositoryBase Class

Right now, you might be asking: “Why do we have the Update method in the RepositoryBase class if we are not using it?”
现在,您可能会问:“如果我们不使用 RepositoryBase 类中的 Update 方法,为什么会有它?

The update action we just executed is a connected update (an update where we use the same context object to fetch the entity and to update it). But sometimes we can work with disconnected updates. This kind of update action uses different context objects to execute fetch and update actions or sometimes we can receive an object from a client with the Id property set as well, so we don’t have to fetch it from the database. In that situation, all we have to do is to inform EF Core to track changes on that entity and to set its state to modified. We can do both actions with the Update method from our RepositoryBase class. So, you see, having that method is crucial as well.
我们刚刚执行的更新操作是连接更新(我们使用相同的上下文对象来获取实体并对其进行更新的更新)。但有时我们可以处理断开连接的更新。这种更新操作使用不同的上下文对象来执行获取和更新操作,或者有时我们也可以从设置了 Id 属性的客户端接收对象,因此我们不必从数据库中获取它。在这种情况下,我们要做的就是通知 EF Core 跟踪该实体上的更改,并将其状态设置为已修改。我们可以使用 RepositoryBase 类中的 Update 方法执行这两个操作。所以,你看,拥有这种方法也是至关重要的。

One note, though. If we use the Update method from our repository, even if we change just the Age property, all properties will be updated in the database.
不过,有一点需要注意。如果我们使用存储库中的 Update 方法,即使我们只更改 Age 属性,数据库中的所有属性都将更新。

11.2 Inserting Resources while Updating one

While updating a parent resource, we can create child resources as well without too much effort. EF Core helps us a lot with that process. Let’s see how.
在更新父资源时,我们也可以毫不费力地创建子资源。EF Core 在此过程方面为我们提供了很多帮助。让我们看看如何。

The first thing we are going to do is to create a DTO class for update:
我们要做的第一件事是创建一个用于更新的 DTO 类:

//CompanyForUpdateDto.cs

using System.Collections.Generic;

namespace Entities.DataTransferObjects
{
    public class CompanyForUpdateDto
    {
        public string Name { get; set; }
        public string Address { get; set; }
        public string Country { get; set; }
        public IEnumerable<EmployeeForCreationDto> Employees { get; set; }
    }
}

After this, let’s create a new mapping rule:
在此之后,让我们创建一个新的映射规则:

//MappingProfile.cs
CreateMap<CompanyForUpdateDto, Company>();

Right now, we can modify our controller:
现在,我们可以修改我们的控制器:

//CompaniesController.cs
        [HttpPut("{id}")]
        public IActionResult UpdateCompany(Guid id, [FromBody] CompanyForUpdateDto company)
        {
            if (company == null)
            {
                _logger.LogError("CompanyForUpdateDto object sent from client is null.");
                return BadRequest("CompanyForUpdateDto object is null");
            }
            var companyEntity = _repository.Company.GetCompany(id, trackChanges: true);
            if (companyEntity == null)
            {
                _logger.LogInfo($"Company with id: {id} doesn't exist in the database.");
                return NotFound();
            }
            _mapper.Map(company, companyEntity);
            _repository.Save(); return NoContent();
        }

That’s it. You can see that this action is almost the same as the employee update action.
就是这样。您可以看到此操作与员工更新操作几乎相同。

Let’s test this now:
现在让我们测试一下:

{
    "name":"Admin_Solutions Ltd Upd",
    "address":"312 Forest Avenue,BF 923",
    "country":"USA",
    "employees":[
        {
            "name":"Geil Metain",
            "age":23,
            "position":"Admin"
        }
    ]
}

https://localhost:5001/api/companies/3d490a70-94ce-4d15-9494-5248280c2ce3

Alt text

We modify the name of the company and attach an employee as well. As a result, we can see 204, which means that the entity has been updated. But what about that new employee?
我们修改公司名称并附加员工。结果,我们可以看到 204,这意味着实体已更新。但是那个新员工呢?

Let’s inspect our query:
让我们检查一下我们的查询:
Alt text

You can see that we have created the employee entity in the database. So, EF Core does that job for us because we track the company entity. As soon as mapping occurs, EF Core sets the state for the company entity to modified and for all the employees to added. After we call the Save method, the Name property is going to be modified and the employee entity is going to be created in the database.
您可以看到我们已经在数据库中创建了员工实体。因此,EF Core 为我们完成了这项工作,因为我们跟踪公司实体。发生映射后,EF Core 将设置要修改的公司实体和要添加的所有员工的状态。调用 Save 方法后,将修改 Name 属性,并在数据库中创建雇员实体。

We are finished with the PUT requests, so let’s continue with PATCH.
我们已经完成了 PUT 请求,所以让我们继续使用 PATCH。

发表评论