Monday, 15 August 2011

Saving foreign keys in Entity Framework 4.1

Problem

I've recently experienced a few issues using the Entity Framework. I'm not ashamed to admit I swore in it's general direction on several occasions. This particular problem was updating foreign key fields (called navigation properties in the Entity Framework). I still haven't entirely decided whether Entity Framework is the problem or I'm the problem, but regardless it confused me so I thought I'd write about it.

Example

public ActionResult Edit(Car car, int manufacturerId){     
car.Manufacturer = carService.GetManufacturer(manufacturerId);     
carService.Save(car);
}

Here I'm passing in a complete Car entity except for the foreign key Manufacturer which has to be passed in as a foreign key value and looked up. I assumed that this would work fine, but it doesn't. All the scalar fields are saved, but the navigation property is not. A little research turned up a lot of blogs and stackoverflow questions asking about this problem. It turns out there are two ways of dealing with foreign keys in the Entity Framework. Unfortunately to my mind, they're both rubbish. Firstly there is foreign key association, for which you specify the foreign key column. E.g. ManufacturerId. This means you have to manually load all navigation properties. I don't know about you, but I like less work, not more. The second way of using foreign keys is independent associations. This makes more sense because you reference the foreign key object (e.g. Manufacturer) and these objects can be lazy loaded. Now that's more like it. Unfortunately this seems a little flawed as well. Other people can explain this much better than me, but using an independent association the Car is not aware of any changes to it's navigation properties unless it's attached first. This is why the above example would successfully update all the scalar properties of the Car object but not Manufacturer. 

Less than perfect solution

I found a few solutions to this problem. One was to manually tell the Entity Framework that the navigation property has changed. I really don't like that solution because it seems like unnecessary effort. That, and the fact that I've put hours into an application I've written using generic repositories and it would be a massive pain in the ass to change it all now. Another solution I found was to load the object first, then make the changes to the navigation property. Because the entity is attached, the Entity Framework tracks the changes to the navigation property. There's still a small problem, you have to copy all your changed properties submitted by the user to the object loaded from the database. What?!? I can't be bothered doing that for all my entities! They could have loads of properties. Fortunately I found a few posts mentioning a .NET MVC method called TryUpdateModel. It's pretty poorly documented but what it seems to do is use some kind of magic to copy all matching properties from your view model to the object passed into the method.

Below is my updated example code:

public ActionResult Edit(Car car, int manufacturerId) {     
Car carToSave =  carService.GetCar(car.Id);     
TryUpdateModel(car); // Copy all from car to carToSave          
car.Manufacturer = carService.GetManufacturer(manufacturerId);     
carService.Save(car);
}

As a side note if you view model contains the object you what to save (e.g. MyViewModel.Car) you can pass a "prefix" into the TryUpdateModel method which tells it where to look for matching properties. E.g.

public class MyViewModel {
  public Car Car { get; set; }     
  public int ManfacturerId { get; set; }
}
TryUpdateModel(car, "Car")




No comments:

Post a comment