Reputation: 2300
I have a Customer
model with a MailingAddress
property - this is a navigation property to an Address
class:
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Address MailingAddress { get; set; }
}
I have a form that posts a viewmodel back which contains a customer
, along with the mailing address. Here is the save method in the controller:
public ActionResult Save(CustomerFormViewModel viewModel)
{
if (!ModelState.IsValid)
{
return View("CustomerForm", viewModel);
}
if (viewModel.Customer.Id == 0)
{
_context.Customers.Add(viewModel.Customer);
}
else
{
var customerInDb = _context.Customers
.Single(c => c.Id == viewModel.Customer.Id);
_context.Entry(customerInDb).CurrentValues.SetValues(viewModel.Customer);
}
_context.SaveChanges();
return RedirectToAction("Index", "Customers");
}
When posting a new customer, everything works fine (mostly, see note below) and the customer is created along with a corresponding address record. However, when I edit an existing entry, the customer is updated but the address is not. I verified the updated address is being passed in the customer object. If I add a line like this:
_context.Entry(customerInDb.MailingAddress).CurrentValues.SetValues(viewModel.Customer.MailingAddress);
Then it is updated.
Is the child here still considered a detached entity? I assumed since it is a property of the Customer
I am fetching it would be automatically saved with the parent. Why does this work with a new record and not with an update?
One note about the new record creation - a Customer
record is created and has a MailingAddress_Id
pointing to the address. The Address
record is also created, but its Customer_Id
is null...an idea why EF is not adding the key on that side of the relationship? Address model and view model code in case it helps:
public class Address
{
public int Id { get; set; }
public string Street1 { get; set; }
// Snip a bunch of address data properties
public virtual Customer Customer { get; set; }
}
public class CustomerFormViewModel
{
// Snip irrelevant properties
public Customer Customer { get; set; }
}
Upvotes: 1
Views: 270
Reputation: 8676
First of all, if your Customer
and Address
are in one-to-one relationship, then no foreign key is needed. Actually, in one-to-one relatioonships primary key on dependant side of relationship is also foreign key to principal side. Secondly, when you create new Customer
you use context.Customers.Add(viewModel.Customer);
and it adds model with all of its child models, but when you try to update using _context.Entry(customerInDb).CurrentValues.SetValues(viewModel.Customer);
it does not add all child navigation properties, to do so, you have to tell it to EntityFramework explicitly:
var customerInDb = _context.Customers
.Single(c => c.Id == viewModel.Customer.Id);
_context.Entry(customerInDb)
.CurrentValues
.SetValues(viewModel.Customer);
var mailingAddressInDb = _context.Addresses
.Single(m => m.Id = viewModel.Customer.MailingAddress.Id);
_context.Entry(mailingAddressInDb)
.CurrentValues
.SetValues(viewModel.Customer.MailingAddress);
It should work for you. But it is a bit awkward. When you have dozens of models, you would not even want to imagine it.
The good news is that, there is an API to solve this problem from its roots. Your problem will be solved in just a few steps. You install it from NuGet using Install-Package Ma.EntityFramework.GraphManager
, configure your models to meet prerequisites (which are so easy) and handle whole graph using single line of code:
public ActionResult Save(CustomerFormViewModel viewModel)
{
if (!ModelState.IsValid)
{
return View("CustomerForm", viewModel);
}
// Define state of whole graph with single line
_context.AddOrUpdate(viewModel.Customer);
_context.SaveChanges();
return RedirectToAction("Index", "Customers");
}
Please, have a look at CodeProject article for a quick wallktrough. It has example code, so you can download and examine it. I am the owner of this API and I am ready to answer to your questions.
Upvotes: 3