I made a small project with Northwind database to illustrate the problematic.
Here is the action of the controller :
[HttpPost]
public ActionResult Edit(Product productFromForm)
{
try
{
context.Products.Attach(productFromForm);
var fromBD = context.Categories.Find(productFromForm.Category.CategoryID);
productFromForm.Category = fromBD;
context.Entry(productFromForm).State = EntityState.Modified;
context.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
context is instanced in the constructor of the Controller as new DatabaseContext().
public class DatabaseContext:DbContext
{
public DatabaseContext()
: base("ApplicationServices") {
base.Configuration.ProxyCreationEnabled = false;
base.Configuration.LazyLoadingEnabled = false;
}
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder){
modelBuilder.Configurations.Add(new ProductConfiguration());
modelBuilder.Configurations.Add(new CategoriesConfiguration());
}
private class ProductConfiguration : EntityTypeConfiguration<Product> {
public ProductConfiguration() {
ToTable("Products");
HasKey(p => p.ProductID);
HasOptional(p => p.Category).WithMany(x=>x.Products).Map(c => c.MapKey("CategoryID"));
Property(p => p.UnitPrice).HasColumnType("Money");
}
}
private class CategoriesConfiguration : EntityTypeConfiguration<Category> {
public CategoriesConfiguration() {
ToTable("Categories");
HasKey(p => p.CategoryID);
}
}
}
public class Category {
public int CategoryID { get; set; }
public string CategoryName { get; set; }
public string Description { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
public class Product {
public int ProductID { get; set; }
public string ProductName { get; set; }
public string QuantityPerUnit { get; set; }
public decimal UnitPrice { get; set; }
public Int16 UnitsInStock { get; set; }
public Int16 UnitsOnOrder { get; set; }
public Int16 ReorderLevel { get; set; }
public bool Discontinued { get; set; }
public virtual Category Category { get; set; }
}
The problem is that I can save anything from the Product but not the change of the category.
The object productFromForm contains the new CategoryID inside productFromForm.Product.ProductID without problem. But, when I Find() the category to retrieve the object from the context I have an object without Name and Description (both stay to NULL) and the SaveChanges() doesn’t modify the reference even if the ID has changed for the property Category.
Any idea why?
Your (apparently) changed relationship doesn’t get saved because you don’t really change the relationship:
This line attaches
productFromFormANDproductFromForm.Categoryto the context.This line returns the attached object
productFromForm.Category, NOT the object from the database.This line assigns the same object, so it does nothing.
This line only affects the scalar properties of
productFromForm, not any navigation properties.Better approach would be:
It becomes a lot easier if you expose foreign keys as properties in the model – as already said in @Leniency’s answer and in the answer to your previous question. With FK properties (and assuming that you bind
Product.CategoryIDdirectly to a view and notProduct.Category.CategoryID) the code above reduces to:Alternatively you can set the state to
Modifiedwhich would work with FK properties: