skip to Main Content

I created a .NET Core API project as below. Everything works very well. But when I send a small JSON file, the null fields in the DTO are reflected in the database. So how can I update only the submitted fields?

When I send only the name field in the JSON object, it updates all the other fields, how can I do this in SaveChangesAsync in the DataContext?

When I send only the name field in the JSON object, it records all the fields as null. How can I prevent this? In other words, only the sent data should be updated, and the others should be recorded with their original values. Is there any way I can achieve this within the dbcontext object?

I am sending a JSON like here, but because the description field is empty inside the JSON object, it is changed to null in the database.

{
  "id": 2,
  "name": "test"
}

CompanyController, I am sending the JSON object via the body:

[HttpPut]
public async Task<IActionResult> Update([FromBody] CompanyUpdateDto updateCompany)
{
    await _service.UpdateAsync(_mapper.Map<Company>(updateCompany));
    return CreateActionResult(CustomResponseDto<CompanyUpdateDto>.Success(204));
}

I am sending my updatedDto object, sometimes name, and description fields, sometimes just the name field.

public class CompanyUpdateDto
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public string? Description { get; set; }
    public DateTime? UpdatedDate { get; set; }
}

CompanyModel:

public class Company
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public string? Description { get; set; }
    public DateTime? CreatedDate { get; set; }
    public DateTime? UpdatedDate { get; set; }
}

DataContext:

public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
    foreach (var item in ChangeTracker.Entries())
    {
        if (item.Entity is BaseEntity entityReference)
        {
            switch (item.State)
            {
                case EntityState.Added:
                    {
                        entityReference.CreatedDate = DateTime.UtcNow;
                        break;
                    }
                case EntityState.Modified:
                    {
                        Entry(entityReference).Property(x => x.CreatedDate).IsModified = false;
                        break;
                    }
            }
        }
    }
    return base.SaveChangesAsync(cancellationToken);
}

3

Answers


  1. With AutoMapper, you can define a rule that only map from the source member to the destination member if the source member is not null via .Condition().

    You may refer to the example in here.

    CreateMap<CompanyUpdateDto, Company>()
        .ForAllMembers(opt => opt.Condition((src, dest, value) => value != null)); 
    

    Demo @ .NET Fiddle

    A concern is that you need to fetch the existing entity and map it with the received object to be updated as below:

    [HttpPut]
    public async Task<IActionResult> Update([FromBody] CompanyUpdateDto updateCompany)
    {
        // Get existing entity by id (Example)
        var _company = await _service.GetAsync(updateCompany.Id);
        
        // Map source to destination
        _mapper.Map<CompanyUpdateDto, Company>(updateCompany, _company);
    
        await _service.UpdateAsync(_company);
        return CreateActionResult(CustomResponseDto<CompanyUpdateDto>.Success(204));
    }
    
    Login or Signup to reply.
  2. You can also ignore null values during serialization:

     var company = new CompanyUpdateDto();
     company.Description = "New description";
     JsonSerializerOptions options = new()
         {
              DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
          };
    
      var serialized = JsonSerializer.Serialize(company,options);
    
    Login or Signup to reply.
  3. You will have to make design decisions here for your API update operation.

    1. Get and Put the objects in full.

    When retrieving an object, your Get operation must return the object in full detail. Then, when any fields change, the client will send the object back in full to the Put endpoint. In this way, you will keep the values for all fields.

    However, in some cases, you only want to expose a subset of the fields and leave some of the fields untouched or updated by the system. In those cases, you will have to retrieve the object from the database by some identifier and then assign the fields from the incoming object.

    1. Use JSON Patch

    You will have a Patch endpoint for the resource. In the request body, you specify what operation for the object and which field has changed. When receiving the request, you will apply the changes based on the operation and fields in the request body.

    The downside for the second option is that your client must follow the JSON Patch standards.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search