Common method for saving and updating on Entity Framework

This problem has been bugging me for some time now. One of the things that I miss the most from NHibernate when I'm working with EF is the SaveOrUpdate methods. Once you lose that, you realize just how much you loved it in the first place. So, I set out to make my EF repositories to use one of those. My initial approach was rather simple and really close to what you can find here or here, so I basically came out with this:

public T SaveOrUpdate(T item)
{
 if (item == null)
  return default(T);

 var entry = _internalDataContext.Entry(item);

 if (entry.State == EntityState.Detached)
  if (item.Id != null)
   TypeDbSet.Attach(item);
  else 
   TypeDbSet.Add(item);
 
 _internalDataContext.SaveChanges();
 return item;
}
This is a neat idea and it works for most of the cases, with one tiny issue. I was working with an external API and I was caching the objects received on my calls and since these objects had their own keys, I was using those keys on my DB. So, I had a Customer class, but the Id property was set when I was about to insert and since our method uses the convention that if it has an Id, it was already saved, then the repo would just attach it to the change tracker but the object was never saved! Boo! Well, no panic, my repo also has a method called GetOne which receives an Id and returns that object, so I added that into the soup and got this:
public T SaveOrUpdate(T item)
{
 if (item == null)
  return default(T);

 var entry = _internalDataContext.Entry(item);

 if (entry.State == EntityState.Detached)
 {
  if (item.Id != null)
  {
   var exists = GetOne(item.Id) != null;

   if (exists)
    TypeDbSet.Attach(item);
   else
    TypeDbSet.Add(item);
  }
  else 
   TypeDbSet.Add(item);
 }
 
 _internalDataContext.SaveChanges();

 return item;
}
Now, if you think about it, how would you update an object?

  • Check if the object already exists on the DB
  • If it's there.. update it!
  • If it's not there.. insert it!

As you can see, Check involves GetOne. Now, if you are thinking that you don't want an extra DB call, there is always a solution...

public T SaveOrUpdate(T item, bool enforceInsert = false)
{
 if (item == null)
  return default(T);

 var entry = _internalDataContext.Entry(item);

 if (entry.State == EntityState.Detached)
 {
  if (item.Id != null)
  {
   var exists = enforceInsert || GetOne(item.Id) != null;

   if (exists)
    TypeDbSet.Attach(item);
   else
    TypeDbSet.Add(item);
  }
  else 
   TypeDbSet.Add(item);
 }
 
 _internalDataContext.SaveChanges();

 return item;
}

Granted, is not fancy, but gets the job done and doesn't requires many changes. If you pass the enforceInsert flag, means you are certain that the object you're saving requires an insert, so it will have an Id, but you know is not there. Just what I was doing!

Do you have any other way of doing this? Do you think this is wrong? Feel free to comment and let me know!

1 comment:

  1. But, unfortunately it won't work with all entities. Which has different primary keys or may be even unique keys. It is worth to say that with the newly released "EntityGraphOperations for Entity Framework Code First" you can save yourself from writing some repetitive codes for defining the states of all entities in the graph. It will automatically set the state of the entities to Added or Modified. And you will manually choose which entities must be deleted if it is not exist anymore.
    Github Page: https://github.com/FarhadJabiyev/EntityGraphOperations

    ReplyDelete

Commenting is allowed!