Damian
Damian

Reputation: 66

How do I determine if a record is new or not before adding it to the DbSet

I have a prepare function for saving records to the database that is probably a little overkill, but the idea was that I could add on to it at a later date.

public void Prepare<T>(T model) where T : class {
    var key = ReflectionHelper.GetAttribute<T, KeyAttribute>();
    if(null == key) { return; }

    SetContext<T>();
    var set = DbManager.Context.Set<T>();

    object id = key.GetValue(model);
    object def = key.PropertyType.GetDefaultValue();
    if(id == def) { set.Add(model); }
}

The current implementation is just checking that the primary key of the record is a default value (typically 0) and then adds it to the dataset. This works for 90% of cases where tables would be built with an auto-incrementing key, however, I'm running into an issue for a table where the key is generated manually for each record, which means that it is set before inserting it into the DB.

This is obviously not ideal with the above function, which is failing the check and not actually saving it to the DB. I know that Entity Framework must have some sort of internal test to check whether a record is new or not to determine whether it needs to do an UPDATE or an INSERT and AFAIK it doesn't rely on the ID being set beforehand or I'd be running into the same issue with EF's code that I am with the above function. Is there a way that I can pull the result from that check instead of the way I'm currently doing it?

Upvotes: 1

Views: 554

Answers (1)

Steve Py
Steve Py

Reputation: 34698

This is where Generic "one size fits all" approaches start to fall down. They work efficiently so long as the implementations are identical. As soon as you have an exceptional case it means introducing complexity.

In situations where the key cannot reflect whether an entity is new or existing (I.e. 0 / null = new) then the typical approach would be to attempt to load the entity to perform the Update, otherwise insert.

var existingEntity = set.SingleOrDefault(x => x.Id == id);
if (existingEntity != null)
{
    Mapper.Map(model, existingEntity);
}
else
{
    existingEntity = set.Add(model);
}

The issue that can come up with "Upsert" implementations is that the application can start accidentally inserting records that you expect to exist, and should have probably handled if they don't. (Stale data, tampering, etc.) My recommendation with systems is to be explicit with dedicated Add/Insert vs. Update method call chains.

DbSet.Update can also work to manage update or insert scenarios but this is a less optimal compared to using EF's change tracker as it will generate an UPDATE SQL statement for all columns whether they changed or not. If you manually update all of the columns or use Automapper's Map method to copy across the values, the change tracker will only generate a statement for the columns that changed. This also gives you control over ensuring that in update scenarios that only allowed values can be changed. For instance the UI is only expected to change some fields, worst case if you are passing full entities back from the client that other values in the model cannot be tampered with when your manual copy over or Automapper mappings only transfer expected field values.

Upvotes: 2

Related Questions