Rafael Hirooka
Rafael Hirooka

Reputation: 11

.NET 5 API update only fields on JSON (Partial Update)

I'm building a .NET 5 API restful and I'm facing some issues.

I would like to know if is possible to update only fields that I send on JSON.

e.g.:

JSON on Postman

{
    "id": 14,
    "name": "Pillar 1"
}

My Model:

public class Pillar
  {
    [FromRoute]
    public int ID { get; set; }
    public string Name { get; set; }
    public bool IsActive { get; set; }

    public int IDEvaluationType { get; set; }
    
    [ForeignKey("IDEvaluationType")]
    public EvaluationType EvaluationType { get; set; }

    public ICollection<Competence> Competences { get; set; }
  }

My APIController

[HttpPut("{id}")]
        public async Task<IActionResult> PutPillar(int id, Pillar pillar)
        {
            if (id != pillar.ID)
            {
                return BadRequest();
            }

            _context.Entry(pillar).State = EntityState.Modified;

            try
            {
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!PillarExists(id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return NoContent();
        }

But when request and debug on VS, all data that I did not send, is setting with null or 0:

debug

And that is updating my database with null values, losing my data that I did not send on JSON. I think the Entity is building a query like this:

update Pillar set ID = 14, Name = 'Pillar 123', IsActive = null, IDEvaluationType = null where ID = 14

But what a need is a query like this:

update Pillar set Name = 'Pillar 123' where ID = 14

Is there a way to update only the fields sent on JSON?

Any questions let me know.

Upvotes: 0

Views: 2600

Answers (3)

Serge
Serge

Reputation: 43870

The only way to do this is like this:

Modify your class or create viewModel:

public class Pillar
  {
    [FromRoute]
    public int ID { get; set; }
    public string? Name { get; set; }
    public bool? IsActive { get; set; }
    [Required]
    public int? IDEvaluationType { get; set; }
    
    [ForeignKey("IDEvaluationType")]
    public EvaluationType EvaluationType { get; set; }

    public ICollection<Competence> Competences { get; set; }
  }

Try this code

 public async Task<IActionResult> PutPillar(int id, Pillar pillar)
        {
            if (id != pillar.ID)
            {
                return BadRequest();
            }
            var existed = _context.Pillars.FirstOrDefault(i=>i.ID=pillar.ID);
            if(pillar.Name!=null) existed.Name=pillar.Name;
            if(pillar.IsActive!=null) existed.IsActive=pillar.IsActive;
            .... and so on

            _context.Entry(existed).State = EntityState.Modified;

            try
            {
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!PillarExists(id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return NoContent();
        }

for more complicated models you can init default constructor:

public  Pillar()
{
  Name="NotAssigned";
}

and use it like this

  if(pillar.Name!="NotAssigned") existed.Name=pillar.Name;

Upvotes: 0

Igor Goyda
Igor Goyda

Reputation: 2017

It is possible to change only a subset of fields of some particular object, and the operation is known as "patching". For REST services you have to use PATCH instead of PUT, because PUT always means a change of a whole object.

There is an article on Microsoft website, how to implement PATСH requests handling, please have a look: https://learn.microsoft.com/en-us/aspnet/core/web-api/jsonpatch?view=aspnetcore-5.0

Upvotes: 1

Parfait Mutshipayi
Parfait Mutshipayi

Reputation: 1

I could have commented but I don't have enough privileges.

Yes, it is possible. just create a DTO (data transfer object) to only update certain fields example.

The Model

<!-- language-all: c# -->

public class PillarNameUpdateDTO {
     [FromRoute]
     public int ID { get; set; }
     public string Name { get; set; }
}

The controller

<!-- language-all: c# -->

[HttpPut("{id}/name")]
    public async Task<IActionResult> PutPillar(int id, PillarNameUpdateDTO dto)
    {
        if (id != dto.ID)
        {
            return BadRequest();
        }

        // Get the pillar

        var pillar = _context.Pillars.Find(dto.ID);

        if (pillar == null) {
             // throw error,
            return NotFound();
        }

        pillar.Name = dto.Name;
        _context.Update(pillar);
        _context.SaveChanges();
        
        return NoContent();
    }

Upvotes: 0

Related Questions