12

I have an MVC project and using Entity Framework Code First and POCO objects for the database. For example:

public class ClassA
{
  public int? Id {get; set;}
  public string Name { get; set;}
  public virtual ClassB B {get; set;}
}

public class ClassB
{
  public int? Id {get;set;}
  public string Description {get;set;}
}

I have an ActionResult that create or edit a model. The problem is when I call this ActionResult to update the model, and model.B has been changed, the relation is not saved in the database. When the ActionResult is called to create a new object it works as expected. How do I solve this?

public ActionResult Save(ClassA model)
{
  model.B = GetBFromDb(model.B.Id.Value);

  if(ModelState.IsValid)
  {
    if (id.HasValue)
    {
      context.Entry(model).State = System.Data.EntityState.Modified;
    }
    else
    {
      context.ClassAs.Add(model);
    }
    context.SaveChanges();
    // redirect to index etc.
  }
  return View("EditForm", model);
}
Marthijn
  • 3,292
  • 2
  • 31
  • 48

3 Answers3

4

I solved it by changing ClassA with a foreign key to ClassB, and setting the value of BId:

public class ClassA
{
  public int? Id {get; set;}
  public string Name { get; set;}
  public int BId {get;set;} // foreign key to B
  public virtual ClassB B {get; set;}
}

Why does this foreign key property does the job instead of the generated foreign ky of EF?

Marthijn
  • 3,292
  • 2
  • 31
  • 48
  • 3
    in disconnected, when you don't have a foreign key there are some gaps that EF can't work around & you have to proved extra code. Otherwise it can't see the relationship. With the FK property, it's a scalar value and easier for EF to keep track of across processes. I actually wrote my Jan 2012 Data Points column about this exact topic! EF is much easier to use when you have an FK scalar property. Otherwise many functions you expect to "just work" can't & itf forces you to have a better understanding of what's going on and how to provide the needed info. Sorry I didn't see the missing FK before – Julie Lerman Dec 10 '11 at 15:22
2

You can't simply call:

context.Entry(model).State = System.Data.EntityState.Modified;

The entity has to be retrieved from the context first so that EF can begin tracking it. Then you'll want to apply any changes to that entity before calling context.SaveChanges().

var entity = context.ClassAs.Find(model.Id);

// set properties you want to modify on entity
entity.Name = model.Name;
entity.ClassB = context.ClassBs.Find(model.ClassB.Id);
// other changes to entity as required...

context.SaveChanges();

This way EF is tracking entity and knows to apply an update against it.

Yuck
  • 49,664
  • 13
  • 105
  • 135
  • Thanks this works! And how do I set `entity.ClassB = null;`? It seems just setting it to null doesn't work. – Marthijn Dec 08 '11 at 13:39
  • 2
    Yuck... context.Entry(model).State will cause the context to see if model is being tracked and if it's not, it will begin tracking it. You don't have to execute another db query to get the model instance tracked. The problem Henkie is having with that ocde is that Entry that method will only change set the state of the entity passed in. The "modified" state setting won't be applied to the rest of the graph. – Julie Lerman Dec 08 '11 at 14:10
  • @JulieLerman Thanks I've found some tutorials that also set the State to Modified. But then I still have the problem my relations won't update. – Marthijn Dec 09 '11 at 07:30
  • 1
    I misread the comment by @JulieLerman, thinking that she didn't care for the context.Entry(model).State approach because of the word Yuck at the start, with a little more attention I realized Yuck is the name of the answerer! Rereading, I believe using context.Entry(model).State is the desired approach. Perhaps it'd be a good idea to add @ before Yuck. – A. Murray Jan 07 '14 at 09:37
  • 4
    Lol! I just read it the same way because two years later, it's out of context for me. Haha! Good catch – Julie Lerman Jan 07 '14 at 13:27
  • Marthijn, the state change only applies to an object not a graph. You have to iterate through the graph. There are other patterns for fixing state more generically. If you ou don't have my DbContext book or a Pluralsight subscription, look for Rowan Miller blog posts on disconnected state (he co authored the DbContext book with me) – Julie Lerman Jan 07 '14 at 13:35
0

If you attach and then set the state to modified entity framework will send all properties for update. You don't have to take the otter approach here below as that causes a whole separate load to occur. If you do it that way theres no sense in passing in a model, only an id and then you call TryUpdateModel to fill in the properties from the form.

Personally the attach as modified here is a bit cleaner as it doesn't require a complete round trip because of loading data

http://geekswithblogs.net/michelotti/archive/2009/11/27/attaching-modified-entities-in-ef-4.aspx

Adam Tuliper
  • 29,982
  • 4
  • 53
  • 71