If I have an object like this
public class Movie
{
public long Id { get; set; }
public string Name { get; set; }
public long? Length { get; set; }
...
}
and have a controller to update its values:
[HttpPatch("{id}")]
public IActionResult Patch(long id, [FromBody] Movie item)
{
Movie movie = (....
if (item.Length.HasValue)
{
movie.Length = item.Length;
_context.Entry<Movie>(movie).Property(w => w.Length).IsModified = true;
}
if (item.Name.HasValue)
{
movie.Name = item.Length;
_context.Entry<Movie>(movie).Property(w => w.Name).IsModified = true;
}
}
It works when a Length is supplied, but what if I want to update it to be undefined (null)? If the value is deleted in the front end, the PATCH data is:
{"length":""}
At the controller, the model binding interprets this as null.
So this:
if (item.Length.HasValue)
cause it to do nothing.
For a moment I considered removing the if condition, because then it would update it to null if an empty string was passed, but of course, if any other property such as Name is patched, the Length would also be set to null which would be bad. The problem is that there is no way (that I can see) in the controller to determine that Length was PATCHED but is null
You are facing a classical Nullable value types problem. The problem will Nullable objects is how do you determine if the value is really intended to be null and therefore supplied, versus when it's Null simplify because it was omitted because the caller assumed that would mean "not modified".
When defining these kind of API or interfaces, I think it goes a little beyond just simply offering a way to interact with your code. Rather than being declarative about it, which can be interpreted in anyway, in these instances I opt to be imperative and rather ask the user to be specific about their intentions.
So in this case I would add an annoying boolean member that can be set on or off and I can just read them up with the DTO ending up looking like
public class Movie
{
public long Id { get; set; }
public string Name { get; set; }
public long? Length { get; set; }
public Boolean IsLengthUpdated{get; set;} = false;//or something that makes sense to describe the length prop has been set
}
Then I can probe my model as such that I don't have to ask myself too many questions about their intention
So in your Patch action you can just do something like
Your API caller would have either set the IsLengthUpdated to true or left it as false, in which case you would ignore evaluating the Length property all together. Your controller would then be something like
[HttpPatch("{id}")]
public IActionResult Patch(long id, [FromBody] Movie item)
{
Movie movie = (....
if(item.IsLengthUpdated)
{
movie.Length = item.Length;//here either movie.Length is supplied one of it's three states {Null,Updated to new value or left the same}
_context.Entry<Movie>(movie).Property(w => w.Length).IsModified = true;
}
... the rest of your code here
}
They would rather come back and say they have been updating values without being persisted then I can rather as them to turn on the relevant field switch otherwise they can leave it as is because either way it will always default to false.
You can try to use id
property to get some movie item in the database first, then assigning new value to the property.
var movie = _context.Movies.SingleOrDefault(x => x.Id == id);
if (movie != null)
{
// if "item.length" is null, "movie.Length" would be null, too
// Otherwise, "movie.Length" will update new value based on "item.Length"
movie.Length = item.Length;
// or only if "item.Length" is null
if (item.Length == null)
{
movie.Length = null;
}
// update database synchronously
_context.SaveChanges();
// or update database asynchronously
// await _context.SaveChangesAsync();
}
if (movie != null)
{
if (!string.IsNullOrEmpty(item.Name) && item.Length?.HasValue)
{
movie.Name = item.Name;
movie.Length = item.Length;
}
else
{
movie.Name = string.Empty;
movie.Length = null;
}
// update database synchronously
_context.SaveChanges();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With