I am currently using EF4.3 and Code First. Creation of my objects works (via my views - just using the auto-generated Create), but when I attempt to edit an object, it does not save any changes that, utlimately, tie back to my navigation properties. I have been reading on relationships, but I don't understand how to tell my context that the relationship has changed.
Here is some example code of my implementation.
@* Snippet from my view where I link into my ViewModel. *@
<div class="row">
<div class="editor-label">
@Html.LabelFor(model => model.ManagerID)
</div>
<div class="editor-field">
@Html.DropDownListFor(model => model.ManagerID, ViewBag.Manager as SelectList, String.Empty)
@Html.ValidationMessageFor(model => model.ManagerID)
</div>
</div>
Here is my Controller implementation (POST of my Edit):
[HttpPost]
public ActionResult Edit(ProjectViewModel projectViewModel)
{
if (ModelState.IsValid)
{
Project project = new Project();
project.ProjectID = projectViewModel.ProjectID;
project.Name = projectViewModel.Name;
project.ProjectManager = repository.GetUser(projectViewModel.ManagerID);
repository.InsertOrUpdateProject(project);
repository.Save();
return RedirectToAction("Index");
}
ViewBag.Manager = new SelectList(repository.GetUsers(), "UserID", "FullName", projectViewModel.ManagerID);
return View(projectViewModel);
}
Within my Project object:
public class Project
{
public int ProjectID { get; set; }
[Required]
public string Name { get; set; }
// Navigation Properties
public virtual User Manager { get; set; }
}
Here is the corresponding method from the repository (where my context resides):
public void InsertOrUpdateProject(Project project)
{
if (program.ProjectID == default(int))
{
context.Projects.Add(project);
}
else
{
context.Entry(project).State = EntityState.Modified;
}
}
Just to be clear, this does work to update my properties, but it does not update my navigation properties (in this case, Manager). Appreciate any help.
Setting the state to Modified
only marks scalar properties as modified, not navigation properties. You have several options:
A hack (you won't like it)
//...
else
{
var manager = project.Manager;
project.Manager = null;
context.Entry(project).State = EntityState.Modified;
// the line before did attach the object to the context
// with project.Manager == null
project.Manager = manager;
// this "fakes" a change of the relationship, EF will detect this
// and update the relatonship
}
Reload the project from the database including (eager loading) the current manager. Then set the properties. Change tracking will detect a change of the manager again and write an UPDATE.
Expose a foreign key property for the Manager
navigation property in your model:
public class Project
{
public int ProjectID { get; set; }
[Required]
public string Name { get; set; }
public int ManagerID { get; set; }
public virtual User Manager { get; set; }
}
Now ManagerID
is a scalar property and setting the state to Modified
will include this property. Moreover you don't need to load the Manager user from the database, you can just assign the ID you get from your view:
Project project = new Project();
project.ProjectID = projectViewModel.ProjectID;
project.Name = projectViewModel.Name;
project.ManagerID = projectViewModel.ManagerID;
repository.InsertOrUpdateProject(project);
repository.Save();
There are several options here, I will list 3 of them:
*This needs the Configuration.AutoDetectChangesEnabled
of your context set to true.
Just install GraphDiff with NuGet
Install-Package RefactorThis.GraphDiff
Then
using (var context = new Context())
{
var customer = new Customer()
{
Id = 12503,
Name = "Jhon Doe",
City = new City() { Id = 8, Name = "abc" }
};
context.UpdateGraph(customer, map => map.AssociatedEntity(p => p.City));
context.Configuration.AutoDetectChangesEnabled = true;
context.SaveChanges();
}
For more details about GraphDiff look here.
Searching your entity with EF to track it to the context. Then edit the properties.
*This needs the Configuration.AutoDetectChangesEnabled
of your context set to true.
var customer = new Customer()
{
Id = 12503,
Name = "Jhon Doe",
City = new City() { Id = 8, Name = "abc" }
};
using (var context = new Contexto())
{
var customerFromDatabase = context.Customers
.Include(x => x.City)
.FirstOrDefault(x => x.Id == customer.Id);
var cityFromDataBase = context.Cities.FirstOrDefault(x => x.Id == customer.City.Id);
customerFromDatabase.Name = customer.Name;
customerFromDatabase.City = cityFromDataBase;
context.Configuration.AutoDetectChangesEnabled = true;
context.SaveChanges();
}
In a matter of performance this is the best way, but it mess your class with database concerns. Because you will need to create a scalar (primitive type) property to map the Id.
*In this way there is no need to set the Configuration.AutoDetectChangesEnabled
to true. And also you won't need to do a query to the database to retrieve the entities (as the first two options would - yes GraphDiff does it behind the scenes!).
var customer = new Customer()
{
Id = 12503,
Name = "Jhon Doe",
City_Id = 8,
City = null
};
using (var contexto = new Contexto())
{
contexto.Entry(customer).State = EntityState.Modified;
contexto.SaveChanges();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With