user3430328
user3430328

Reputation:

How to handle updating entity without knowing which fields were updated?

I have a view in my MVC app which has some (but not all) properties of my entity exposed for editing. There are other entity properties which are not exposed to the user. Right now, the form is posted to my controller in the form of a parameter of my entity type. The entity has valid values for the fields exposed in the view, but the rest of the unexposed fields are null. If I go to my repository and try to update the existing entity with this one, many fields will be overwritten with null values.

How would I update my entity with just the fields the user changed in the view?

I can think of two approaches. Are there more?

  1. ​You include ALL fields of the model as hidden fields within the form. That way when a post happens, the entire model is updated.

  2. You only post the fields the user can change and you have specific routes, controller methods, and repository methods to handle this approach. You can have methods like updateEntityAddress, where the new address values are received and they then overwrite the existing values. You know that this method is only for updating that specific type of field, so you know which ones have changed, and can write code to just update those fields.

Upvotes: 2

Views: 1520

Answers (5)

myst1c
myst1c

Reputation: 603

I believe the best solution is to just map your new object (with your properties fulfilled with new values from the frontend) and map to you db context object. All values will be then mapped and you can control which ones you want to update.

        var context = await asyncDbContext;
        using var locker = await context.Lock();

        var dbo = await context.Settings.FindAsync(SettingsDbo.SettingsId);
        if (dbo == null)
        {
            dbo = mapper.Map<SettingsDbo>(newSettings);
            context.Settings.Add(dbo);
        }

Upvotes: 0

Paul
Paul

Reputation: 176

I realize that this has an answer, and a good one for an MVC web page, but I want to post a different approach for some future web surfer.

In the event you have an integration end-point that is providing you partial updates to your API, and you only want to restrict changes to values that are provided:

In your DB context class, override the SaveChanges method and apply the necessary business logic to the entity attributes before you conduct the actual save operation.

In my case, I know that certain properties will be null but I don't want to have them overwritten in the data store by the end-point accessing the API invokes a save operation.

A web application does a direct data change to maintain the properties I don't want the end-point to change.

This approach also has the benefit of not having to refactor your entire application to solve what may be an edge-case.

The snippet below will work as well.

    public override int SaveChanges()
    {

        var selectedEntityList = ChangeTracker.Entries()
                                .Where(x => x.Entity is ctTerminalTimeZone &&
                                (x.State == EntityState.Added || x.State == EntityState.Modified));
        foreach (var entity in selectedEntityList)
        {
            this.ctTerminalTimeZoneChangeLogEntities.Add(new ctTerminalTimeZoneChangeLog()
            {
                DateModified = DateTime.Now.ToShortDateString(),
                TerminalLocationCode = ((ctTerminalTimeZone)entity.Entity).TerminalLocationCode,
                TimeModified = DateTime.Now.ToLongTimeString()

            });
            if (((ctTerminalTimeZone)entity.Entity).HiddenValue == null)
            {
                this.Entry(((ctTerminalTimeZone)entity.Entity)).Property(x => x.HiddenValue).IsModified = false;
            }
            //and so on and so on
        }
        return base.SaveChanges();
    }

Upvotes: 0

Mike Nakis
Mike Nakis

Reputation: 62129

As Stephen Muecke has already alluded to in a comment, you need to borrow from the concept of a "ViewModel". I am not saying that you need to have an actual full-blown ViewModel, because you have stated that you are doing MVC, you are not doing MVVM, but these concepts are related, since MVVM derives from MVC.

So, you need to have a clear concept of what subset of your entity is exposed to (and editable by) the view, so that you know what part of your original entity needs updating and what part must be left "as-is".

A quick and dirty solution could be to:

  • Have a table somewhere (perhaps a dictionary mapping each entity-type to a list-of-field names) which lists which fields of an entity are to be sent to the view and therefore which fields are expected to be received back and applied to the entity.

But a better approach would be to:

  • Introduce a whole bunch of new classes, one for each entity, where each class:

    • is filled with a subset of the fields that make up the entity
    • is sent to the view and presented to the user
    • is received back in the POST request.
    • is then applied to the original entity.

On the long run, having a separate set of ViewModel classes pays, because what you will inevitably discover is that your model entities are sometimes not presentable to the user, nor editable by the user, in their original form, but they need to undergo transformations before the user sees them, and these transformations may need to be reversed before changes can be persisted. In other words, the forms that the user sees do not need to (should not be limited to) have a one-to-one correspondence with model entities.

Upvotes: 1

Thomas
Thomas

Reputation: 29756

You should have a look at OData with Web API:

Create an OData-v4 Endpoint.

I've found this code in the article, this allows you to update a Delta of your entity that means you post only the properties you want to update :

public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Product> product)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    var entity = await db.Products.FindAsync(key);
    if (entity == null)
    {
        return NotFound();
    }
    product.Patch(entity);
    try
    {
        await db.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!ProductExists(key))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }
    return Updated(entity);
}

Upvotes: 0

Ryan Mann
Ryan Mann

Reputation: 5357

I didn't put a lot of thought into this, but there are 2 ways I thought of.

  1. When you post the entity, look up the same entity. Then use reflection to set the values of the null fields on the posted entity to the values of the entity you looked up on those same fields. Then your entity won't have any null fields that were not already null and you won't have to put them all in the form as hidden fields.

(update)If you know what entity is being posted and what fields are on it, then you don't need to use reflection.

  1. Write a stored procedure to update the entity, called something like "sp_AddOrUpdate_EntityName". Make the first parameter "@ignoreNullParameters BIT = 0", then add parameters for all the fields on your entity. Then write Transac SQL to Update the Entity if it exists and insert it if it does not. On an Update if IgnoreNullParameters = 1 then it should not overwrite values with nulls. That would look something like

    Update [sometable] Set [someField] = CASE WHEN @ignoreNullParameters = 1 THEN ISNULL(@someField, [someField]) ELSE @someField END WHERE [id] @id

Then modify EF to call the stored procedure to add/update the entity (pretty sure there is a way to do that) as opposed to letting EF make it's own queries to update/insert that entity.

Upvotes: 0

Related Questions