0

A majority of the examples I see now are either using the Code First Approach or using an older version of MVC and the Entity Framework.

Assume I have a movie to update and I get to the Edit View, in the Edit method with the Post verb, what is the proper way to update a Movie? The first Edit Method below gets me to the Edit View with the populated Movie values and the second one is the one I want to use to update, I have tried some things, but nothing updates the data.

 public ActionResult Edit(int id)
        {
            var movie = (from m in _db.Movies1
                         where m.Id == id
                         select m).First();

            return View(movie);
        }

    [HttpPost]
    public ActionResult Edit(Movie movie)
    {
        try
        {
            // TODO: Add update logic here

           //What do I need to call to update the entity?

            _db.SaveChanges();
            return RedirectToAction("Index");
        }
        catch
        {
            return View();
        }
    }
3
  • 1
    What is _db? Is it derived from DbContext or from ObjectContext? What EF version are you using? Commented Jan 11, 2012 at 22:06
  • How id _db being created? Are you using dependency injection? If not, you should be creating _db in your method, not at the class level. And what exactly is _db anyways? Commented Jan 11, 2012 at 23:14
  • @Slauma - Sorry for getting back late. I believe it is Derived from ObjectContext. I don't have the code in front of me, but what I do is generate the edmx model from the database. _db is an instance of the MovieDBEntities I create when generating the edmx file. In the Code first approach, I derived from DbContext. Commented Jan 12, 2012 at 3:41

2 Answers 2

4

Assuming that _db is derived from ObjectContext you have two options:

  • Change the state of the entity to Modified:

    _db.Movies1.Attach(movie);
    _db.ObjectStateManager.ChangeObjectState(movie, EntityState.Modified);
    _db.SaveChanges();
    

    This marks all properties of movie as modified and will send an UPDATE statement to the database which includes all column values, no matter if the values really changed or not.

  • Reload the original entity from the database and apply the changes to it:

    var originalMovie = (from m in _db.Movies1
                         where m.Id == movie.Id
                         select m).First();
    // You actually don't need to assign to a variable.
    // Loading the entity into the context is sufficient.
    
    _db.Movies1.ApplyCurrentValues(movie);
    _db.SaveChanges();
    

    ApplyCurrentValues will mark only those properties as modified which really did change compared to the original and the UPDATE statement which will be sent to the database only includes the changed column values. So, the UPDATE statement is potentially smaller than in the first example but you have to pay the price to reload the original entity from the database.

Edit

How does the second code example work?

  • When you run a query using the context (_db) Entity Framework does not only retrieve the entity from the database and assign it to the left side of the query (originalMovie) but it actually stores a second reference internally. You can think of this internal context "cache" as a dictionary of key-value pairs - the key is the entity primary key and the value is the entity itself, the same object as originalMovie refers to.

  • ApplyCurrentValues(movie) looks up this entity in the context's internal dictionary: It takes the key property value Id of the passed in movie, searches for an entity with that key in the internal dictionary and then copies property by property from the passed in ("detached") movie to the internal ("attached") entity with the same key. EF's change tracking mechanism marks the properties as Modified which were actually different to create later the appropriate UPDATE statement.

Because of this internal reference to the original entity you do not need to hold your own reference: That's the reason why originalEntity is not used in the code. You can in fact remove the assignment to the local variable altogether.

The example would not work if you disable change tracking when you load the original entity - for example by setting _db.Movies1.MergeOption = MergeOption.NoTracking;. The example relies on enabled change tracking (which is the default setting when entities are loaded from the database).

I cannot say which of the two examples has better performance. That might depend on details like size of the entities, number of properties which have been changed, etc.

It's worth to note though that both approaches do not work if related entities are involved (for example movie refers to a category entity) and if the relationship or the related entity itself could have been changed. Setting the state to Modified and using ApplyCurrentValues both affect only scalar and complex properties of movie but not navigation properties.

Sign up to request clarification or add additional context in comments.

12 Comments

I will give this a shot and let you know. I appreciate the explanations with each approach.
With your first example, I get an error message: The ObjectStateManager does not contain an ObjectStateEntry with a reference to an object of type 'DBFirstMVC.Models.Movie'.
In the second example with ApplyCurrentValues, it does not take 1 parameter only, I can't figure out what to pass to it.
When I do _db.Movies1.ApplyCurrentValues("Movie",movie), it works.
@Xaisoft: In the first example an Attach is missing, sorry. I've edited the code example. For ApplyCurrentChanges there are two methods: one of ObjectContext (= _db) which indeed needs two parameters, and a second for ObjectSet<T> (= _db.Movies1) which only needs one parameter: msdn.microsoft.com/en-us/library/dd466205.aspx . I don't understand how _db.Movies1.ApplyCurrentValues("Movie",movie) could work, or isn't _db.Movies1 of type ObjectSet<Movie>?
|
0

Your second edit method should look something like this:

[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
    var movie = (from m in _db.Movies1
                     where m.Id == id
                     select m).First();
     if (TryUpdateModel(movie))
        {
            _db.SaveChanges();
            return (RedirectToAction("Index"));
        }

        return View(movie);
}

3 Comments

No, it shouldn't. That's the MVC1 way of doing things. Nowadays, we use models passed as a parameter.
I'm still fairly new to MVC3 myself, but one of the first tutorials (written for MVC3) I worked through passed the model as a parameter for the Post Create function, but passed the primary key and a form collection to the Post Edit function. I assumed this was for clarity, since to use the TryUpdateModel you must retrieve the existing record before attempting to merge the changes. I can see now that passing the movie as a parameter works fine, as long as you keep your two resulting Movie variables straight.
@Danimal37 - This is the actual way I have seen it. I know that FormCollection is passed here just to have a different method signature than the Get Edit method, but other than that it serves no purpose. This actually works, but I am curious to what the new way is. Because it does in fact work, I will upvote it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.