Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to save combined (new+modified) detached entities in Entity Framework?

What is the proper and fast way to save combined new and modified detached POCO entities?

I was thinking about these methods:

    private void Method_2(IList<Entity> entities) //detached entities
    {
        //This method is using SELECT to check if entity exist
        using (var context = new ModelContainer())
        {
            foreach (Entity entity in entities)
            {
                var foundEntity = context.CreateObjectSet<Entity>().SingleOrDefault(t => t.Id == entity.Id);
                context.Detach(foundEntity);    //Remove it from ObjectStateManager

                if (foundEntity != null)//It is modified entity
                {
                    context.AttachTo("EntitySet", entity); //Attach our entity
                    context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified); //We know it exists
                }
                else//It is new entity
                {
                    context.CreateObjectSet<Entity>().AddObject(entity);
                }
            }
            context.SaveChanges();
        }
    }

    private void Method_1(IList<Entity> entities) //detached entities
    {
        //This method doesn't select anything from DB, but i have ta call Savechanges after each object
        using (var context = new ModelContainer())
        {
            foreach (Entity entity in entities)
            {
                try
                {
                    context.AttachTo("EntitySet", entity);
                    context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
                    context.SaveChanges();
                }
                catch (OptimisticConcurrencyException)
                {
                    context.ObjectStateManager.ChangeObjectState(entity, EntityState.Added);
                    context.SaveChanges();
                }
            }
        }
    }

When you are working in detached environment you have to know which entity was added and which is modified - it is your responsibility to keep this information and provide it to ObjectContext.

Well i agree with this statement if you found yourself in situation when you need to use EF code like this in EF definitely something is wrong with you decision. I have chosen wrong tool for this job.

like image 989
Alex Burtsev Avatar asked Dec 13 '10 14:12

Alex Burtsev


2 Answers

When you are working in detached environment you have to know which entity was added and which is modified - it is your responsibility to keep this information and provide it to ObjectContext.

The very easy way is:

foreach (var entity in entities)
{
  if (entity.Id == 0) // 0 = default value: means new entity
  {
    // Add object
  }
  else
  {
    // Attach object and set state to modified
  }
}

The example requires that you have some db auto-generated primary key (Id).

Your Method 2 is possible with some modifications. It is not needed to detach entity when you load it. Instead use ApplyCurrentValues. The approach with loading entity first is very usefull when you decide to work with object graphs instead of single entity. But in the case of object graph you have to do synchronization manually. ApplyCurrentValues works only for scalar (non navigation) properties. You can try to futher optimize your method to load needed enitites in single roundtrip to database instead of loading entities one by one.

Your Method 1 is terrible solution. Using exceptions raised on database server to control program flow is bad approach.

like image 146
Ladislav Mrnka Avatar answered Oct 22 '22 01:10

Ladislav Mrnka


I agree with @Ladislav - Method_1 is a bad approach. Let the database raise exceptions which are caught by EF - don't try and swallow these exceptions yourself.

Your on the right track with Method 1.

Here is how i do it - as i also have a detached context (POCO's, no change tracking, ASP.NET MVC).

BLL Interface: (note i have TPT in my model, hence generics. "Post" is abstract)

void Add(Post post);
void Update<TPost>(TPost post) where TPost : Post, new();

The new() constraint is crucial - you'll see why shortly.

I won't show how i do "Add", because it's simple as you think - AddObject(entity);

The "Update" is the tricky part:

public class GenericRepository<T> : IRepository<T> where T : class
{
   public void Update<T2>(T2 entity) where T2: class, new()
   {
      var stub = new T2(); // create stub, now you see why we need new() constraint

      object entityKey = null;
      // ..snip code to get entity key via attribute on all domain entities
      // once we have key, set on stub.

      // check if entity is already attached..
      ObjectStateEntry entry;
      bool attach;

      if (CurrentContext.ObjectStateManager.TryGetObjectStateEntry(CurrentContext.CreateEntityKey(CurrentContext.GetEntityName<T>(), stub), out entry))
      {
         // Re-attach if necessary.
         attach = entry.State == EntityState.Detached;
      }
      else
      {
         // Attach for first time.
         attach = true;
      }

      if (attach)
         CurrentEntitySet.Attach(stub as T);

      // Update Model. (override stub values attached to graph)
      CurrentContext.ApplyCurrentValues(CurrentContext.GetEntityName<T>(), entity);
   }
}

And that works for me.

As for the entity key, i have used attributes on my domain classes. An alternative (which i'm about to move to), is have all my domain entities implement an interface, which specifies that all domain entities must have a property called "EntityKey". Then i'll use that interface on my constraints. Basically, i needed a dynamic way to create stub entities in a generic repository.

I don't personally like the idea of "checking the ID, if its > 0 then it's an update". Because i'm working with ASP.NET MVC, if i (or another developer) forgets to bind the ID to the View, it won't be passed through, so even though it may be an update, because the ID == 0 it will be added.

I like to be explicit about the operations. This way, i can perform Add/Update seperate validation logic.

like image 23
RPM1984 Avatar answered Oct 21 '22 23:10

RPM1984