Reputation: 187
Looking for a way in which to update a single column in a single row in a table with EF Core 3. Here is my scenario I'm looking for. Today I have class defined like the below:
public partial class Records
{
public int Id { get; set; }
public int CustomerId { get; set; }
public int UserId { get; set; }
public string UserText { get; set; }
public DateTime? UserDate { get; set; }
public bool? NotificationSent { get; set; }
}
And have a PUT command like this:
[HttpPut("{customerId}/{id}")]
public async Task<IActionResult> PutRecords([FromHeader(Name = "Token")] string token, [FromHeader(Name = "Secret")] string secret, int id, Records record)
{
if (id != record.Id)
{
return BadRequest();
}
_context.Entry(record).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!RecordsExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return Ok("Success"); ;
}
When I send in my client, I was hoping that I could simply send in the ID and a single column, like this:
{
"Id": 140225,
"UserText": "Test"
}
And it would then only update that single text. But what is happening is the "record" value that is being passed in contains the names of every object in the Class, set to null or 0 and then updates all of the columns in that row with those values.
Been doing some research and I know that I can use something like:
var Record = await _context.Records.FindAsync(id);
Record.UserText = record.UserText;
_context.Entry(Record).Property("UserText").IsModified = true;
and then it only updates that single column. What I can't seem to figure out is how to get from A to B with the knowledge I've laid out. Just curious if anyone else has run into this or has a cool solution. My current through was maybe to loop through record looking for values to be able to set the IsModified but haven't had luck in getting that to work right.
Upvotes: 0
Views: 540
Reputation: 34908
This is the expected behaviour when passing entities between client and server. The better approach if you want to update values is to load the entity in the request, set the applicable values (after validating) and then save the changes.
When a tracked entity is updated it will generate the appropriate update statements for just the columns that change.
For instance, to update a entity's description:
[HttpPut("{customerId}/{id}")]
public async Task<IActionResult> UpdateDescription([FromHeader(Name = "Token")] string token, [FromHeader(Name = "Secret")] string secret, int id, string description, int rowVersion)
{
var record = _context.Records.Single(x => x.Id == id);
// TODO: Check the user associated to this session/request and assert they have permissions to this record.
if (rowVersion != record.RowVersion)
{
// Consider Refusing the update, notify user that data has changed & refresh.
}
record.Description = description;
_context.SaveChanges();
}
It doesn't have to be as specific as that (1 field) it could be updating via a view model of allowed fields that could be changed, which you can validate and copy over individually or use Automapper to map across to the loaded entity. As a general rule though, actions taken should be as specific as possible, accepting only the data needed to identify what to update, and the values to be updated. Definitely not a whole entity as tampering could result in updating values that should not ever change, or change them in ways that are not intended.
This approach helps avoid stale data changes by checking for a row version or last modified timestamp, etc. It should also validate the current user's session against the rows retrieved to verify that the provided ID can be validated. For instance, passing an ID and a detatched entity and then checking id == model.Id
is pointless. Tampering can be done to both values by the client or man in the middle.
Upvotes: 1