Some time ago, I wanted to implement a method that was able to determine whether do an insert or an update on a given entity, so I didn't have to expose "Insert" and "Update" methods, but just a simple "InsertOrUpdate".
The part of code that finds out if the entity is new or not, is this:
public virtual T GetEntityByPrimaryKey<T>(T entity) where T : class
{
var entityType = entity.GetType();
var objectSet = ((IObjectContextAdapter)this.DatabaseContext).ObjectContext.CreateObjectSet<T>();
var keyNames = objectSet.EntitySet.ElementType.KeyMembers.Select(edmMember => edmMember.Name);
var keyValues = keyNames.Select(name => entityType.GetProperty(name).GetValue(entity, null)).ToArray();
return this.DatabaseContext.Set<T>().Find(keyValues);
}
And the InsertOrUpdate method is this:
public virtual T InsertOrUpdate<T>(T entity) where T : class
{
var databaseEntity = this.GetEntityByPrimaryKey(entity);
if (databaseEntity == null)
{
var entry = this.DatabaseContext.Entry(entity);
entry.State = EntityState.Added;
databaseEntity = entry.Entity;
}
else
{
this.DatabaseContext.Entry(databaseEntity).CurrentValues.SetValues(entity);
}
return databaseEntity;
}
Now, this approach works wonders as long as the "primary key" of the object is determined by the code. Valid examples are GUIDs, HI-LO algorithms, natural keys, etc.
This, however, is horribly broken for the "database generated identity" scenario, and the reason is quite simple: since my "Id" in the code will be 0 for all the objects I'm going to insert, the method will consider them the same. If I'm adding 10 objects, the first will result "new", but the next nine will result "already existing". This is due to the fact that the "Find" method of EF reads data from the objectcontext and only if it's not present it goes down to the database to make the query.
After the first object, an entity of the given type with Id 0 will be tracked. Successive calls will result in an "update", and this is just wrong.
Now, I know that database generated ids are evil and absolutely not good for any ORM, but I'm stuck with those and I need to either fix this method or remove it entirely and fall back to separate "Insert" and "Update" methods and delegate to the caller the task to determine what to do. Since we have a highly decoupled solution I'd rather avoid to do this.
If anyone can help and find a way to fix the GetEntityByPrimaryKey method, that would be awesome.
Thanks.