Reputation: 60
can you please tell me if it is possible to exclude some columns from editing a record using EF Core. Context: I have a table in which there is a file stored as an array of bytes, but when editing records, I get null, I thought to solve this issue with an additional query to the database, where I take the old field and insert it into the edited one, but it seems to me this is a clumsy solution. I will add: You will still need to edit the field one day, but not for typical editing.
public async Task<IActionResult> Edit(int id, [Bind("id,Name,Price,Minimum_Pages,Requirements,Responsible,Start_Registration,Start_Conference,ResponsibleID,File,File_Name,File_Descriptor,Priority")] Registry_Conference registry_Conference)
{
if (id != registry_Conference.id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(registry_Conference);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!Registry_ConferenceExists(registry_Conference.id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View("~/Views/NPR_Module/Registry/Registry_Conference/Edit.cshtml", registry_Conference);
}
Upvotes: 1
Views: 1845
Reputation: 34698
The "crutch" in your example is expecting the passed in value to be an entity. It is declared using the Entity type, but it isn't an entity, it's not even a detached entity. It is a bit of de-serialized JSON made to look like an entity.
When you call Update
, EF is expecting a complete, detached entity. You cannot provide it that, so the simple answer is you cannot use Update
reliably as a crutch to get the desired data across. When you give it de-serialized JSON, the entity look-alike object will only have the data that was passed into the call. For very simple objects where you pass everything to and from your client you can get away with that, but this does introduce the risk of unintended tampering with data you don't otherwise expect to change.
The simplest way to accomplish what you need is to use Automapper. At its most basic:
var mapper = new MapperConfiguration(cfg => {
cfg.CreateMap<Registry_Conference, Registry_Conference>()
.ForMemeber(x => x.FileData, opt => opt.Ignore())
// Add any other properties that should not be copied...
}).CreateMapper();
var dataRegistryConference = _context.Registry_Conferences.Single(x => x.Id == registry_Conference.Id);
// TODO: Check Row Version here.
// As well as other validations.
mapper.Map(registry_Conference, dataRegistryConference);
_context.SaveChanges();
Alternatively you can manually copy over the allowed values from the passed in data to the live data. The typical question/argument against this approach is "I want to avoid the extra call to the DB". The simple answer to that is that you really don't want to, or at least there are really good reasons to check the DB.
The // Check Row Version
is if you are tracking a row version stamp for row modification tracking. This can help prevent stale data overwrites if someone else has modified this row since the caller passing back a modified object had fetched the copy of data they based their changes on. Other checks that you should make include things like ownership/permissions. Your method ultimately passes an ID that you should ensure exists, handles stale data, and verifies that the current user can, and should be allowed to update it in it's current state. (I.e. does this user have access to see/edit it? is the data in a state that allows editing? etc.) To do that, you should always check existing data state as the source of truth, never trusting what is passed in.
The other benefit of using Automapper, or manual copying over Update
is that EF will only generate an UPDATE
SQL statement if any of the values actually changed and only for the values that changed. Using Update
will perform an UPDATE
SQL for all columns whether they changed or not, updating row versions and firing triggers etc.
Better is to avoid masking the passed in data as an Entity, and declaring a view model that contains only the data that should be ever edited. This way you don't need to configure any Ignore()
settings in the mapper. It also saves confusion if later you have common code that can accept an Entity and you think to call that code from somewhere like this. Code accepting an entity should always get a complete or complete-able entity where this method only provides an incomplete shell of an entity.
Upvotes: 2