Reputation: 38109
I need to update only one or two properties of an Entity. In other words, I have an entity with an Id, ParentId, Name, and Description.
The issue is that when the name is updated, the Description is wiped out if it already existed in the database.
This is the code:
internal void Update(ItemInfo itemInfo)
{
var item = new Item { Id = itemInfo.Id, ParentId = itemInfo.ParentId, Name = itemInfo.Name };
var entry = this.DbContext.Items.Attach(item);
if (item.ParentId != null) entry.Property(x => x.ParentId).IsModified = true;
if (!(String.IsNullOrWhiteSpace(item.Name))) entry.Property(x => x.Name).IsModified = true;
this.SaveChanges();;
}
I thought that since I was setting the particular property as modified, only that property would be updated.
Or should I be getting the entity from the database first, and then just setting the property and saving. I wanted to avoid two trips to the database for a single save.
Upvotes: 7
Views: 17963
Reputation: 3591
The thing you're really after is Concurrency Handling. When multiple users are editting the same object at the same time, the second user will overwrite the changes of the first user.
Or should I be getting the entity from the database first, and then just setting the property and saving.
Yes, but having a controller method for each property of your object would be very tedious. That's why concurrency handling is the best option.
Also, your domain model should be entirely seperated from your database model. Never use entities in your web application directly. You are already doing so by having the Item
entity (database model) and the ItemInfo
class (domain model, used to handle the post-request).
First add a Timestamp
column to your entity:
internal class Item
{
public int Id { get; set; }
public int ParentId { get; set; }
public string Name { get; set; }
[Timestamp]
[ConcurrencyCheck]
public byte[] ConcurrencyStamp { get; set; }
}
Then, at the place where you update your entity:
[Controller]
public class ItemController : Controller
{
private readonly DbContext dbContext;
public ItemController(DbContext dbContext)
{
this.dbContext = dbContext;
}
[HttpPost]
//[Authorize]
//[ValidateAntiForgeryToken]
public async Task<ActionResult<ItemInfo>> Update([FromBody] ItemInfo item)
{
var existingItem = dbContext.Items.SingleOrDefaultAsync(i => i.Id == item.Id);
if (Convert.ToBase64String(existingItem.ConcurrencyStamp) != item.ConcurrencyStamp)
{
var databaseValue = new ItemInfo
{
Id = existingItem.Id,
ParentId = existingItem.ParentId,
Name = existingItem.Name,
};
return StatusCode(Microsoft.AspNetCore.Http.StatusCodes.Status409Conflict, databaseValue);
}
// Set new properties
existingItem.Id = item.Id;
existingItem.ParentId = item.ParentId;
existingItem.Name = item.Name;
// Save changes
await dbContext.SaveChangesAsync();
// Now return the updated item
// Now you should map the entity properties back to a new domain model...
var result = new ItemInfo
{
Id = existingItem.Id,
ParentId = existingItem.ParentId,
Name = existingItem.Name,
ConcurrencyStamp = Convert.ToBase64String(existingItem.ConcurrencyStamp),
};
return Ok(item);
}
}
Now when you try to update your item from the client-side, and receive a 409Conflict statuscode, you should decide how you want to handle this. I've chosen to display the database values below the respective input boxes.
You can find an entire implementation of this here
Upvotes: 3
Reputation: 1598
You can do following in order to update a single property:
internal void Update(ItemInfo itemInfo)
{
if (itemInfo == null) { throw new Exception($"item info was not supplied"); }
var itemInfoEntity = new ItemInfo()
{
Id = itemInfo.Id,
ParentId = itemInfo.ParentId,
Name = itemInfo.Name
};
dbContext.ItemInfo.Attach(itemInfoEntity);
dbContext.Entry(itemInfoEntity).Property(x => x.Id).IsModified = true;
dbContext.Entry(itemInfoEntity).Property(x => x.ParentId).IsModified = true;
dbContext.Entry(itemInfoEntity).Property(x => x.Name).IsModified = true;
dbContext.SaveChanges();
}
But if you only are updating few properties, then I think you should not send the whole object as parameter, just send the properties that needs to be updated something like this:
internal void Update(id, parentId, name)
{
...
}
Upvotes: 22